SwiftUI中Sheet与Menu组件冲突问题及解决方案问询
在以下SwiftUI示例中,页面包含一个Menu组件和一个触发Sheet的按钮。当Menu处于展开状态时点击该按钮,会触发如下错误日志(可能需要多次尝试复现),随后按钮会失效:
2023-03-02 12:01:59.850609+0100 SheetButtonDemo[50295:6802586] [Presentation] Attempt to present <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x124815200> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x10c809200> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x10c809200>) which is already presenting <_UIContextMenuActionsOnlyViewController: 0x107d0bf20>.
这是SwiftUI的已知Bug,已上报为FB12026200,与社区中「SwiftUI: popover + sheet in different hierarchies problem」问题类似。
示例代码
struct ContentView: View { @State var showSheet = false var body: some View { VStack(spacing: 100) { Button("Show Sheet") { self.showSheet = true } Menu { Button("Action") { print("Hello") } } label: { Text("Menu") } } .sheet(isPresented: $showSheet, content: { Text("Sheet") }) .padding() } }
问题演示

可行规避方案
方案1:延迟触发Sheet,让Menu先关闭
通过短暂延迟给系统留出关闭Menu的时间,再触发Sheet展示,对用户体验影响极小:
Button("Show Sheet") { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.showSheet = true } }
方案2:借助UIKit层级展示Sheet
绕过SwiftUI的presentation逻辑,直接用UIKit的视图控制器来呈现Sheet,彻底避免层级冲突:
先添加UIViewController扩展用于获取顶层控制器:
extension UIViewController { static func topViewController() -> UIViewController? { let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first var topController = keyWindow?.rootViewController while let presentedViewController = topController?.presentedViewController { topController = presentedViewController } return topController } }
再修改按钮点击逻辑:
Button("Show Sheet") { let sheetVC = UIHostingController(rootView: Text("Sheet")) UIViewController.topViewController()?.present(sheetVC, animated: true) }
方案3:使用自定义Menu组件
完全替换系统Menu,用SwiftUI原生组件实现下拉菜单,避免系统内部的视图控制器参与层级:
struct CustomMenu: View { @State private var isMenuOpen = false var body: some View { Button("Menu") { isMenuOpen.toggle() } .overlay( Group { if isMenuOpen { VStack(spacing: 0) { Button("Action") { print("Hello") isMenuOpen = false } .padding() .frame(maxWidth: .infinity) .background(Color.white) } .border(Color.gray) .offset(y: 40) .onTapGesture { isMenuOpen = false } } }, alignment: .top ) } }
在ContentView中替换原Menu:
VStack(spacing: 100) { Button("Show Sheet") { self.showSheet = true } CustomMenu() } .sheet(isPresented: $showSheet, content: { Text("Sheet") })
内容的提问来源于stack exchange,提问作者Ralf Ebert
相关产品推荐
相关产品推荐

