我遇到了一个奇怪的 SwiftUI bug。我有一张工作表,应该根据其内容自行调整大小(使用 PreferenceKey + GeometryReader)。一切正常,直到我尝试使用 overrideUserInterfaceStyle 更改用户界面样式。
当我像这样改变主题时,工作表大小突然变成0,甚至没有调用onPreferenceChange。这只发生在 iOS 16 上。
您可以在屏幕录像中看到问题。
import SwiftUI
fileprivate struct IntrinsicContentSizePreferenceKey: PreferenceKey {
static let defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
fileprivate struct FittedPresentationDetentModifier: ViewModifier {
@State private var contentSize: CGSize = .zero
func body(content: Content) -> some View {
content
.overlay(
GeometryReader { proxy in
Color.clear.preference(
key: IntrinsicContentSizePreferenceKey.self,
value: proxy.size
)
}
)
.onPreferenceChange(IntrinsicContentSizePreferenceKey.self) { newSize in
print("→ current size: \(contentSize)")
print("→ new size: \(newSize)")
guard newSize != .zero else { return }
contentSize = newSize
}
.presentationDetents([.height(contentSize.height)])
.presentationCornerRadius(32.0)
}
}
fileprivate extension View {
func fittedPresentationDetent() -> some View {
modifier(FittedPresentationDetentModifier())
}
}
extension View {
func bottomSheet<Content: View>(
isPresented: Binding<Bool>,
@ViewBuilder content: @escaping () -> Content
) -> some View {
self.sheet(isPresented: isPresented) {
content()
.fittedPresentationDetent()
}
}
}
我创建了一个示例项目用于测试。
import SwiftUI
struct ContentView: View {
@State private var showSheet = false
@Environment(\.colorScheme) var colorScheme
var isDarkMode: Bool {
colorScheme == .dark
}
var body: some View {
NavigationView {
VStack(spacing: 20) {
Image(systemName: isDarkMode ? "moon.fill" : "sun.max.fill")
.font(.system(size: 60))
.foregroundColor(isDarkMode ? .white : .yellow)
.padding()
Text("Current Theme: \(isDarkMode ? "Dark" : "Light")")
.font(.headline)
Button(action: {
showSheet.toggle()
}) {
Text("Change Theme")
.font(.headline)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.navigationTitle("Theme Switcher")
.bottomSheet(isPresented: $showSheet, content: {
ThemeSettingsSheet(isDarkMode: isDarkMode)
})
}
}
}
struct ThemeSettingsSheet: View {
let isDarkMode: Bool
@Environment(\.dismiss) var dismiss
var body: some View {
Toggle(isOn: Binding(
get: { isDarkMode },
set: { newValue in
changeTheme(isDarkMode: newValue)
}
)) {
HStack {
Image(systemName: isDarkMode ? "moon.fill" : "sun.max.fill")
.foregroundColor(isDarkMode ? .white : .yellow)
Text("Dark Mode")
}
}
.padding()
}
private func changeTheme(isDarkMode: Bool) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else { return }
UIView.transition(with: window,
duration: 0.3,
options: .transitionCrossDissolve,
animations: {
window.overrideUserInterfaceStyle = isDarkMode ? .dark : .light
}, completion: nil)
}
}
#Preview {
ContentView()
}
有人遇到过这种情况吗?有什么解决方法吗?谢谢!
您可以将
.presentationDetents
设置为 .presentationBackground
,而不是设置 Color.clear
。然后让您的内容找到自己的大小,并为其指定 UnevenRoundedRectangle
作为背景。
这样做,既不需要
GeometryReader
也不需要PreferenceKey
。
fileprivate struct FittedPresentationDetentModifier: ViewModifier {
func body(content: Content) -> some View {
content
.background {
UnevenRoundedRectangle(topLeadingRadius: 32, topTrailingRadius: 32)
.fill(.background)
.ignoresSafeArea(edges: .bottom)
}
.frame(maxHeight: .infinity, alignment: .bottom)
.presentationBackground(.clear)
}
}
顺便说一句,可能会有更简单的方法来更改配色方案,请参阅Sweeper的评论。