封装按钮与Popover的自定义控件在Menu内无法弹出Popover
我封装了一个结合Button和Popover的自定义View PopoverButton,代码如下:
struct PopoverButton<DestinationView: View>: View { let label: LocalizedStringKey let image: String let destinationView: DestinationView @State private var isVisible = false init(label: LocalizedStringKey, image: String, @ViewBuilder _ destinationView: () -> DestinationView) { self.label = label self.image = image self.destinationView = destinationView() } var body: some View { Button(action: { isVisible = true }, label: { Label(label, systemImage: image) }) .popover(isPresented: $isVisible, content: { destinationView }) } }
当这个控件直接放在Toolbar中时可以正常工作,但将其置于Menu组件内时,按钮可以显示但无法弹出Popover——即使isVisible已设为true,Popover的回调也不会触发:
.toolbar { ToolbarItem(placement: .automatic) { PopoverButton(label: "working", image: "pencil") { WorkingView() } } ToolbarItem(placement: .automatic) { Menu { PopoverButton(label: "not.working", image: "arrow.down.right") { NotWorkingView() } } label: { Image(systemName: "plus") } } }
问题原因
核心问题在于Menu的交互机制与视图生命周期冲突:点击Menu内的选项时,系统会自动关闭Menu,此时Menu的弹出内容(包含PopoverButton的临时视图)会被从视图树中移除。PopoverButton内部的@State是视图级别的状态,当承载它的视图被销毁时,状态变化无法触发Popover展示——因为父视图已经不存在了。
解决方案
方案1:将状态提升到父视图层级
把控制Popover显示的状态从PopoverButton内部移到包含Menu的父视图中,确保状态不受Menu视图销毁的影响:
- 修改
PopoverButton,将内部状态改为外部绑定:
struct PopoverButton<DestinationView: View>: View { let label: LocalizedStringKey let image: String let destinationView: DestinationView @Binding var isVisible: Bool // 改为外部传入的绑定 init(label: LocalizedStringKey, image: String, isVisible: Binding<Bool>, @ViewBuilder _ destinationView: () -> DestinationView) { self.label = label self.image = image self._isVisible = isVisible self.destinationView = destinationView() } var body: some View { Button(action: { isVisible = true }, label: { Label(label, systemImage: image) }) .popover(isPresented: $isVisible, content: { destinationView }) } }
- 在父视图中声明状态变量,传递给Menu内的
PopoverButton:
struct YourParentView: View { @State private var showNotWorkingPopover = false // 父视图级别的状态 var body: some View { // ... 你的视图内容 .toolbar { ToolbarItem(placement: .automatic) { // 正常使用的按钮,用constant绑定即可 PopoverButton(label: "working", image: "pencil", isVisible: .constant(false)) { WorkingView() } } ToolbarItem(placement: .automatic) { Menu { PopoverButton(label: "not.working", image: "arrow.down.right", isVisible: $showNotWorkingPopover) { NotWorkingView() } } label: { Image(systemName: "plus") } } } } }
方案2:绕过PopoverButton,在父视图直接管理Popover
如果不想修改PopoverButton的结构,可以直接在Menu内使用普通Button,由父视图控制Popover的显示:
struct YourParentView: View { @State private var showNotWorkingPopover = false var body: some View { // ... 你的视图内容 .toolbar { ToolbarItem(placement: .automatic) { PopoverButton(label: "working", image: "pencil") { WorkingView() } } ToolbarItem(placement: .automatic) { Menu { Button(action: { showNotWorkingPopover = true }, label: { Label("not.working", systemImage: "arrow.down.right") }) } label: { Image(systemName: "plus") } } } .popover(isPresented: $showNotWorkingPopover) { NotWorkingView() } } }
内容的提问来源于stack exchange,提问作者Jerzy Piechowiak
相关产品推荐
相关产品推荐

