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

如何实现苹果文件应用风格的Menu自定义视图?

自定义SwiftUI导航工具栏Overflow Menu(类似原生文件App的toolbarTitleMenu)

核心实现思路

苹果原生的这种菜单并非直接用Menu组件嵌套复杂视图实现,而是依托自定义菜单内容视图+原生UIMenu能力完成。在SwiftUI中,有两种简便方案可以实现需求:

方法1:用Menu+Section区分头部与菜单选项

虽然Menu默认仅支持按钮类控件,但可以通过Section包裹自定义头部视图,配合样式调整模拟原生效果:

Menu {
    // 自定义头部区域
    Section {
        HStack(spacing: 12) {
            Image(systemName: "folder")
                .resizable()
                .frame(width: 40, height: 40)
                .foregroundColor(.blue)
            VStack(alignment: .leading, spacing: 4) {
                Text("Downloads")
                    .font(.headline)
                Text("Folder")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
            Spacer()
            Button(action: {
                // 分享逻辑
            }) {
                Image(systemName: "square.and.arrow.up")
                    .padding(8)
                    .background(Color(.systemGray5))
                    .cornerRadius(12)
            }
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 12)
    }
    .listRowSeparator(.hidden)
    
    // 常规菜单选项
    Button("New Folder") {
        // 新建文件夹逻辑
    }
    Button("Rename") {
        // 重命名逻辑
    }
    Button("Delete") {
        // 删除逻辑
    }
} label: {
    Text("Downloads")
}

关键是用Section分隔自定义头部和普通菜单选项,隐藏分隔线让头部融入菜单,同时保留后续选项的原生样式。

方法2:自定义弹出视图替代Menu

如果需要完全自由的布局,可通过@State控制自定义视图的显隐,模拟菜单弹出效果,还能隐藏popover箭头:

@State private var showCustomMenu = false

var body: some View {
    NavigationStack {
        Text("Content")
            .toolbar {
                Button(action: { showCustomMenu.toggle() }) {
                    Text("Downloads")
                }
                .popover(isPresented: $showCustomMenu, arrowEdge: .top) {
                    VStack(spacing: 0) {
                        // 自定义头部
                        HStack(spacing: 12) {
                            Image(systemName: "folder")
                                .resizable()
                                .frame(width: 40, height: 40)
                                .foregroundColor(.blue)
                            VStack(alignment: .leading, spacing: 4) {
                                Text("Downloads")
                                    .font(.headline)
                                Text("Folder")
                                    .font(.subheadline)
                                    .foregroundColor(.secondary)
                            }
                            Spacer()
                            Button(action: {
                                showCustomMenu.toggle()
                                // 分享逻辑
                            }) {
                                Image(systemName: "square.and.arrow.up")
                                    .padding(8)
                                    .background(Color(.systemGray5))
                                    .cornerRadius(12)
                            }
                        }
                        .padding(.horizontal, 16)
                        .padding(.vertical, 12)
                        
                        Divider()
                        
                        // 菜单选项
                        Button("New Folder") {
                            showCustomMenu.toggle()
                            // 新建逻辑
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.horizontal, 16)
                        .padding(.vertical, 12)
                        
                        Button("Rename") {
                            showCustomMenu.toggle()
                            // 重命名逻辑
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.horizontal, 16)
                        .padding(.vertical, 12)
                    }
                    .frame(width: 300)
                    .background(Color(.systemBackground))
                    .cornerRadius(12)
                    .shadow(radius: 10)
                }
                .onChange(of: showCustomMenu) { newValue in
                    // 隐藏popover箭头
                    if newValue {
                        UIPopoverPresentationController.appearance().arrowDirection = .none
                    }
                }
            }
    }
}

通过修改UIPopoverPresentationController外观隐藏箭头,完全自定义菜单的布局与样式,效果和原生文件App的toolbarTitleMenu一致。

原生实现说明

苹果原生的toolbarTitleMenu基于UIKit的UIMenu和UIContextMenuConfiguration实现,SwiftUI的Menu是对UIMenu的封装。原生支持通过自定义UIMenuElement添加复杂视图,若需更贴近底层实现,可通过UIViewRepresentable桥接UIKit能力,但上述两种SwiftUI原生方案已足够满足需求。

内容的提问来源于stack exchange,提问作者ScoobyDooku

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 22:03:22