iOS16弹出模态视图时GeometryReader异常:Bug还是新特性?
iOS 16下SwiftUI GeometryReader异常问题及解决思路
问题概述
升级iOS 16后,SwiftUI中的GeometryReader出现异常:按特定操作步骤后,它返回的全局frame值会停留在模态视图弹出时的3D效果对应尺寸,无法自动恢复,导致依赖该值的视图变形。iOS 15中无此问题,已提交Bug报告。
环境信息
- Xcode 14
- iOS 15.5和16的模拟器/真机
复现步骤
- 打开模态视图
- 将应用切换至后台
- 重新打开应用
- 关闭模态视图
最小复现代码
struct ContentView: View { @State private var isPresented: Bool = false var body: some View { GeometryReader { reader in VStack(spacing: 36) { Text("Screen frame:\n\(String(describing: reader.frame(in: .global)))") .multilineTextAlignment(.center) Button { isPresented.toggle() } label: { Text("Open modal") } } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() .onReceive(NotificationCenter.default.appDidBecomeActive()) { _ in print(reader.frame(in: .global)) } .onReceive(NotificationCenter.default.appDidEnterBackground()) { _ in print(reader.frame(in: .global)) } } .sheet(isPresented: $isPresented) { modalView } } private var modalView: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") } .padding() } } extension NotificationCenter { func appDidBecomeActive() -> AnyPublisher<Notification, Never> { publisher(for: UIApplication.didBecomeActiveNotification).eraseToAnyPublisher() } func appDidEnterBackground() -> AnyPublisher<Notification, Never> { publisher(for: UIApplication.didEnterBackgroundNotification).eraseToAnyPublisher() } }
强制GeometryReader重新计算的可行方案
方案1:用状态变量触发视图重建
新增一个状态变量,在模态关闭、应用激活等关键时机更新它,通过绑定id让GeometryReader所在视图重新构建,从而触发frame重新计算:
struct ContentView: View { @State private var isPresented: Bool = false @State private var refreshTrigger = 0 // 用于触发刷新的状态变量 var body: some View { GeometryReader { reader in VStack(spacing: 36) { Text("Screen frame:\n\(String(describing: reader.frame(in: .global)))") .multilineTextAlignment(.center) Button { isPresented.toggle() } label: { Text("Open modal") } } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() .onReceive(NotificationCenter.default.appDidBecomeActive()) { _ in print(reader.frame(in: .global)) } .onReceive(NotificationCenter.default.appDidEnterBackground()) { _ in print(reader.frame(in: .global)) } } .sheet(isPresented: $isPresented) { modalView .onDisappear { // 模态关闭时触发刷新 refreshTrigger += 1 } } .onReceive(NotificationCenter.default.appDidBecomeActive()) { _ in // 应用激活时也触发刷新 refreshTrigger += 1 } .id(refreshTrigger) // 值变化时,视图会完全重建 } private var modalView: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") } .padding() } }
方案2:用UUID作为视图标识触发刷新
和方案1逻辑类似,改用UUID作为唯一标识,每次需要刷新时生成新的UUID,确保视图完全重建:
struct ContentView: View { @State private var isPresented: Bool = false @State private var viewID = UUID() var body: some View { GeometryReader { reader in VStack(spacing: 36) { Text("Screen frame:\n\(String(describing: reader.frame(in: .global)))") .multilineTextAlignment(.center) Button { isPresented.toggle() } label: { Text("Open modal") } } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() .onReceive(NotificationCenter.default.appDidBecomeActive()) { _ in print(reader.frame(in: .global)) } .onReceive(NotificationCenter.default.appDidEnterBackground()) { _ in print(reader.frame(in: .global)) } } .sheet(isPresented: $isPresented) { modalView .onDisappear { refreshView() } } .onReceive(NotificationCenter.default.appDidBecomeActive()) { _ in refreshView() } .id(viewID) } private func refreshView() { viewID = UUID() } private var modalView: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") } .padding() } }
方案3:替换全局frame获取方式(如果场景允许)
如果你的视图不需要依赖GeometryReader的动态布局,直接用UIScreen.main.bounds获取屏幕尺寸,避开GeometryReader的异常:
// 替换原有Text中的frame获取逻辑 Text("Screen frame:\n\(String(describing: UIScreen.main.bounds))")
内容的提问来源于stack exchange,提问作者bguidolim
相关产品推荐
相关产品推荐

