Post

Replies

Boosts

Views

Activity

SwiftUI App change Status Bar color for single view
The main view of my application is dark and full screen, so I need a status bar that is always white for this screen (rather than white or black depending on dark mode set by the user). I can apply .preferredColorScheme(.dark) to this mainContent view but in doing so, any views that are presented from this view are also forced to be in dark mode. Is it possible to force the dark mode status bar for the mainContent, but then allow for the content presented via the sheet to use whatever mode the user has set? @main struct statusbarApp: App { @State var showSheet: Bool = false @ViewBuilder var mainContent: some View { VStack { Button("Show Sheet") { showSheet.toggle() } .frame(maxWidth: .infinity) Spacer() } .foregroundColor(.white) // .preferredColorScheme(.dark) // <- This applies to all views. .background(Color.black, ignoresSafeAreaEdges: .all) } var body: some Scene { WindowGroup { mainContent .sheet(isPresented: $showSheet) { Text("Dark or Light mode") } } } }
1
0
1.7k
Sep ’23
ScrollViewReader doesn't scroll on small changes until after user interaction
I'm running into an odd issue where on iOS 16, the ScrollViewReaderProxy does not always scroll. It seems as though small content changes won't cause the view to scroll unless the user interacts with the view, or if there was a large content change first. I'm wondering if this is just a bug that exists in iOS 16 (as it works fine in iOS 17) or if there's something I've overlooked. I want to create a view that can be pinned to the trailing content as more text is added but I also want to have a fading mask on the left and right of the view (hence the off -15 and 15 padding). TLDR; I have a struct PinnableScrollView<EquatableView: View & Equatable>: View which takes a @ViewBuilder public let content: EquatableView so that when the content changes it calls scrollViewReaderProxy.scrollTo. Here's the full class that I have: struct PinnableScrollView<EquatableView: View & Equatable, GenericView: View>: View { public enum Pinning { case leading case trailing var alignment: Alignment { switch self { case .leading: return .leading case .trailing: return .trailing } } } public let pinning: Pinning public let debugViews: Bool @ViewBuilder public let content: EquatableView @ViewBuilder public let transformer: (EquatableView) -> GenericView @State private var contentWidth: CGFloat = .zero @State private var scrollWidth: CGFloat = .zero @State private var offsetValue: CGFloat = .zero @Namespace var element var canScroll: Bool { scrollWidth > contentWidth } public var body: some View { if debugViews { VStack(alignment: .leading, spacing: 10) { Text("contentWidth: \(contentWidth)").font(.caption2) Text("scrollWidth: \(scrollWidth)").font(.caption2) Text("offsetValue: \(offsetValue)").font(.caption2) scrollContent } } else { scrollContent } } var scrollContent: some View { ScrollViewReader { scrollViewReaderProxy in ScrollView(.horizontal) { HStack(spacing: 0) { if pinning == .trailing { Spacer(minLength: 0) } transformer(content) .padding(.horizontal, canScroll ? 15 : 0) .id(element) if pinning == .leading { Spacer(minLength: 0) } } .frame(minWidth: contentWidth) .background(GeometryReader { proxy in Color.clear .preference( key: ScrollSizePreferenceKey.self, value: proxy.size.width ).onPreferenceChange(ScrollSizePreferenceKey.self, perform: { value in self.scrollWidth = value }).preference( key: OffsetPreferenceKey.self, value: proxy.frame(in: .named("scrollViewCoordinateSpaceLayer")).minX ).onPreferenceChange(OffsetPreferenceKey.self, perform: { value in self.offsetValue = value }) }) } .padding(.horizontal, canScroll ? -15 : 0) .background(GeometryReader { proxy in Color.clear .preference( key: WidthPreferenceKey.self, value: proxy.size.width ).onPreferenceChange(WidthPreferenceKey.self, perform: { value in self.contentWidth = value }) }) .mask( HStack(spacing: 0) { LinearGradient(gradient: Gradient(colors: [.black.opacity(0.0), .black]), startPoint: .leading, endPoint: .trailing) .frame(width: 15.0) Color.black .frame(width: contentWidth) LinearGradient(gradient: Gradient(colors: [.black, .black.opacity(0.0)]), startPoint: .leading, endPoint: .trailing) .frame(width: 15.0) } ) .coordinateSpace(name: "scrollViewCoordinateSpaceLayer") .onAppear(perform: { if pinning == .trailing { scrollViewReaderProxy.scrollTo(element, anchor: .trailing) } }) .onChange(of: content, perform: { _ in if pinning == .trailing { print("attempting to scroll: \(Date.now)") withAnimation { scrollViewReaderProxy.scrollTo(element, anchor: .trailing) } } }) } } } private struct ScrollSizePreferenceKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } private struct OffsetPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } private struct WidthPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } And here's a basic preview provider: struct PinnableScrollView_Previews: PreviewProvider { struct TestView: View { @State var string: String = "Hello" var body: some View { VStack(alignment: .leading, spacing: 10) { Button("Add Longer Text") { string = string + " world, how are you? Are doing well??" } Button("Add Short Text") { string = string + " hello!" } PinnableScrollView(pinning: .trailing, debugViews: true) { Text(string) } transformer: { view in view.padding() } } } } static var previews: some View { TestView() .padding() } }
0
0
593
Aug ’23
SwiftUI `.popover` isn't working within UIHostingConfiguration cell displayed in a UITableViewController
I'm trying to use SwiftUI cells using the .contentConfiguration APIs. However, the SwiftUI view that I'd like to use also needs to be able to display a popover. I'm able to present an alert, but not able to present a popover. The following code uses a SwiftUIView as the cell view that works to present an .alert but does not work to present a .popover. Note the .popover does work in both Previews and when presented within a UIHostingController. import UIKit import SwiftUI class ViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 3 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! cell.contentConfiguration = UIHostingConfiguration { SwiftUIView(text: "Hello \(indexPath.row)") } return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { present(UIHostingController(rootView: SwiftUIView(text: "full screen")), animated: true) } } struct SwiftUIView: View { @State var presentAlert: Bool = false @State var showPopover: Bool = false let text: String var body: some View { HStack { Text(text) Button("Present Alert") { presentAlert = true } Button("Present Popover") { showPopover = true } }.popover(isPresented: $showPopover) { Button("Hide Popover") { showPopover = false } }.alert(isPresented: $presentAlert) { Alert(title: Text("Alert")) } } } struct SwiftUIView_Previews: PreviewProvider { static var previews: some View { SwiftUIView(text: "Hello") } }
1
0
1.6k
Jun ’23