iOS 15下SwiftUI的fullScreenCover与sheet异常行为问题
iOS15 SwiftUI 多弹窗修饰符冲突问题修复方案
该问题为iOS 15系统版本的SwiftUI已知Bug,iOS 14不受影响,iOS 16及更高版本苹果已完成修复。
异常表现
- 选中Identifiable类型的item触发sheet弹窗,弹窗正常弹出
- 通过手势下滑关闭该sheet后,再打开普通Bool绑定的sheet,弹出的内容依然是之前Identifiable item对应的sheet内容
- 关闭上述异常sheet后打开fullScreenCover,会以sheet的弹窗样式展示fullScreenCover的内容
问题根源
iOS 15的SwiftUI对同视图层级下多个并列的sheet、fullScreenCover修饰符的状态管理存在缺陷:当Identifiable绑定的sheet被手势下滑关闭后,系统内部的弹窗状态栈没有正确重置,即使手动在onDismiss回调中将绑定的可选Identifiable变量置为nil,系统依然会优先识别旧的Identifiable弹窗配置,导致后续触发的其他弹窗展示异常。
修复方案
方案1:调整修饰符顺序
将fullScreenCover、普通Bool绑定的sheet修饰符放在Identifiable绑定的sheet修饰符之前,让系统优先判断普通弹窗的状态,可快速解决问题:
var body: some View { VStack { // 原有视图内容保持不变 HStack { Button(action: {isFullScreenPresented.toggle()}, label: { Text("fullScreen") .padding() .foregroundColor(.red) .background(Rectangle()) }) Button(action: {isSheetPresented.toggle()}, label: { Text("sheet") .padding() .foregroundColor(.red) .background(Rectangle()) }) } HStack { Button(action: {isItemSheetPresented = .one}, label: { Text("one") .padding() .foregroundColor(.red) .background(Rectangle()) }) Button(action: {isItemSheetPresented = .two}, label: { Text("two") .padding() .foregroundColor(.red) .background(Rectangle()) }) } HStack { Text(isFullScreenPresented.description) Text(isSheetPresented.description) } Text(isItemSheetPresented.debugDescription) Spacer() } // 调整顺序:fullScreenCover放最前 .fullScreenCover(isPresented: $isFullScreenPresented, onDismiss: {isFullScreenPresented = false }, content: {FullScreenContent()}) // 其次是普通Bool绑定的sheet .sheet(isPresented: $isSheetPresented, onDismiss: { isSheetPresented = false}, content: { Text("sheet") }) // Identifiable绑定的sheet放最后 .sheet(item: $isItemSheetPresented, onDismiss: {isItemSheetPresented = nil}, content: {item in Text(item.id) }) }
方案2:统一弹窗管理
把所有弹窗类型都归到同一个Identifiable枚举中管理,整个视图只保留一个弹窗修饰符,从根源上避免多修饰符冲突:
// 统一管理所有弹窗类型 enum Destination: String, Identifiable { var id: String { rawValue } case sheetOne, sheetTwo, normalSheet, fullScreen } struct ContentView: View { // 仅保留一个状态变量控制所有弹窗 @State var destination: Destination? @Environment(\.presentationMode) var presentationMode var body: some View { VStack { Button("fullScreen") { destination = .fullScreen } Button("normal sheet") { destination = .normalSheet } Button("sheet one") { destination = .sheetOne } Button("sheet two") { destination = .sheetTwo } } .sheet(item: $destination) { dest in switch dest { case .sheetOne: Text("one") case .sheetTwo: Text("two") case .normalSheet: Text("sheet") case .fullScreen: FullScreenContent() // 配置为全屏弹窗样式 .presentationMode(.fullScreen) } } } }
方案3:拆分修饰符绑定视图
不要将所有弹窗修饰符都绑定到根视图上,分别绑定到对应的触发按钮的父视图上,避免同层级多修饰符状态冲突。
内容的提问来源于stack exchange,提问作者drucelweisse
相关产品推荐
相关产品推荐

