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

封装按钮与Popover的自定义控件在Menu内无法弹出Popover

解决Menu组件内PopoverButton无法弹出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视图销毁的影响:

  1. 修改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
        })
    }
}
  1. 在父视图中声明状态变量,传递给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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:17:38