You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 08:10:21