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")
}
}
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
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()
}
}
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")
}
}