SwiftUI iPad端.sheet/.fullScreenCover初次关闭失败后无法再关闭
解决iPad上SwiftUI Sheet含ColorPicker时关闭按钮失效问题
在iPad的SwiftUI应用中,当Sheet内包含ColorPicker和工具栏关闭按钮时,会出现以下异常:
- 当ColorPicker的调色板处于显示状态时,若误触背景中的关闭按钮,Sheet不会关闭,且后续点击关闭按钮也无法生效,只能通过点击Sheet外部区域关闭(若使用
.fullScreenCover则彻底无法关闭)。 - 该问题仅出现在iPad上,iPhone上第二次点击关闭按钮即可正常关闭Sheet。
问题根源是ColorPicker激活状态下调用dismiss()或设置绑定的isPresenting为false时,Sheet的关闭流程被中断,导致状态异常,后续关闭操作失效。
方案一:延迟执行Sheet关闭操作
通过延迟设置Sheet的绑定状态,确保ColorPicker的调色板先完成关闭,再处理Sheet的关闭。这种方式无需自定义组件,实现简单:
import SwiftUI struct SomePickerView: View { @State var colourBackground: Color = .clear @Binding var isPresented: Bool var body: some View { ColorPicker("", selection: $colourBackground) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Dismiss") { // 延迟0.1秒执行关闭,给ColorPicker弹窗足够的关闭时间 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { isPresented = false } } } } } } struct SomeParentView: View { @State var showSomePickerView: Bool = false var body: some View { Button("Pick a colour") { showSomePickerView = true } .sheet(isPresented: $showSomePickerView) { NavigationStack { SomePickerView(isPresented: $showSomePickerView) } } } }
方案二:自定义ColorPicker并禁用激活状态下的关闭按钮
通过封装UIKit的UIColorPickerViewController,获取调色板的激活状态,在激活时禁用关闭按钮,从根源避免误触:
1. 封装自定义ColorPicker
import SwiftUI import UIKit struct CustomColorPicker: UIViewControllerRepresentable { @Binding var selection: Color @Binding var isPickerActive: Bool func makeUIViewController(context: Context) -> UIColorPickerViewController { let picker = UIColorPickerViewController() picker.delegate = context.coordinator picker.selectedColor = UIColor(selection) return picker } func updateUIViewController(_ uiViewController: UIColorPickerViewController, context: Context) { uiViewController.selectedColor = UIColor(selection) } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UIColorPickerViewControllerDelegate { let parent: CustomColorPicker init(_ parent: CustomColorPicker) { self.parent = parent } func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) { parent.selection = Color(viewController.selectedColor) } func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) { parent.isPickerActive = false } func colorPickerViewControllerWillPresent(_ viewController: UIColorPickerViewController) { parent.isPickerActive = true } } }
2. 在视图中使用自定义Picker并禁用按钮
struct SomePickerView: View { @State var colourBackground: Color = .clear @Binding var isPresented: Bool @State private var isColorPickerActive = false var body: some View { CustomColorPicker(selection: $colourBackground, isPickerActive: $isColorPickerActive) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Dismiss") { isPresented = false } .disabled(isColorPickerActive) // 调色板激活时禁用关闭按钮 } } } } struct SomeParentView: View { @State var showSomePickerView: Bool = false var body: some View { Button("Pick a colour") { showSomePickerView = true } .sheet(isPresented: $showSomePickerView) { NavigationStack { SomePickerView(isPresented: $showSomePickerView) } } } }
方案对比
- 方案一:实现成本低,仅需添加延迟逻辑,但属于临时 workaround,依赖时间延迟的稳定性。
- 方案二:从根源解决问题,通过状态控制避免误操作,但需要封装UIKit组件,代码量稍大。
内容的提问来源于stack exchange,提问作者djmlewis
相关产品推荐
相关产品推荐

