SwiftUI中Picker未选值时Sheet无法正常弹出的问题咨询
SwiftUI Picker未选择值时Sheet无法弹出的问题及解决方案
问题说明
在ContentView中包含Picker组件和两个按钮:
- 第一个按钮直接将
show变量设为true - 第二个按钮通过延迟操作
show变量
操作步骤:点击Picker但不选择任何值,之后点击按钮尝试弹出Sheet。此时会出现异常:只有带延迟的按钮能正常弹出Sheet,无延迟按钮触发错误,且后续Sheet无法再弹出。
错误信息
[Presentation] Attempt to present <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x14e80e400> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14c819600> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x14c819600>) which is already presenting <_UIContextMenuActionsOnlyViewController: 0x14d63be90>.
代码示例
import SwiftUI struct ContentView: View { @State var show = false @State var picked = "1" let values = ["1", "2", "3"] var body: some View { VStack { Picker(selection: $picked, label: Text("")) { ForEach (values, id: \.self) { value in Text(value) } } Text("⬆️ Tap me but don't select") .padding(.bottom, 120) Text("1. Tap Picker") Text("2. Tap show sheet button") Button("show sheet") { show = false show = true } .padding([.top, .bottom], 20) Button("show sheet with delay") { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { show = true } } } .padding() .sheet(isPresented: $show, content: { Button("close") { show = false } }) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
解决方案
这是SwiftUI的已知bug:Picker弹出的上下文菜单关闭后,SwiftUI未及时更新宿主视图控制器的present状态,导致直接触发Sheet时出现"已存在弹窗"的冲突。除了添加延迟,还有以下两种更可靠的解决方法:
方法1:使用主队列异步触发
无需延迟,只需将show = true放到主队列的下一个循环执行,让Picker的上下文菜单先完成关闭流程:
Button("show sheet") { DispatchQueue.main.async { show = true } }
方法2:改用sheet(item:)绑定
使用item绑定替代isPresented,这种方式SwiftUI内部会更妥善地处理弹窗状态:
import SwiftUI struct ContentView: View { @State var picked = "1" @State var sheetItem: String? // 替换原有的show变量 let values = ["1", "2", "3"] var body: some View { VStack { Picker(selection: $picked, label: Text("")) { ForEach(values, id: \.self) { value in Text(value) } } Text("⬆️ Tap me but don't select") .padding(.bottom, 120) Text("1. Tap Picker") Text("2. Tap show sheet button") Button("show sheet") { sheetItem = "present" // 设置非nil值触发Sheet } .padding([.top, .bottom], 20) Button("show sheet with delay") { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { sheetItem = "present" } } } .padding() .sheet(item: $sheetItem) { _ in Button("close") { sheetItem = nil // 设置nil关闭Sheet } } } } // 让String遵循Identifiable协议,满足sheet(item:)的要求 extension String: Identifiable { public var id: String { self } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
说明
方法1实现简单,改动最小;方法2更符合SwiftUI的声明式设计理念,也能避免类似的弹窗冲突问题。
内容的提问来源于stack exchange,提问作者js-john
相关产品推荐
相关产品推荐

