Menu

概述

SwiftUI菜单提供了一种直观的方式,让用户能够选择操作,访问次级界面,或者查看额外的选项。菜单可以包含一系列菜单项,每个菜单项可以是一个按钮、切换开关、复选框,或者其他SwiftUI控件。

菜单非常适合用于导航、设置和应用程序级操作,特别是在移动设备上,这些设备空间有限。

基本菜单创建

使用 String 创建菜单

1import SwiftUI
2struct ContentView: View {
3    var body: some View {
4        Menu("菜单") { 
5            // 菜单内容
6        }
7    }
8}

使用 Label 创建菜单

1Menu {
2    // 菜单内容
3} label: {
4    Label("菜单", systemImage: "menu")
5}
6
7Menu("菜单", systemImage: "doc.text") {
8    // 菜单内容                
9}

菜单内容类型

Tip

除了用 Button 作为 Menu 的内容外, 还可以用 Toggle、Divider、Label、Text、Section、Picker、 Stepper 等控件。

按钮菜单

1Menu("菜单") {
2    // 普通按钮
3    Button("操作1") { ... }
4    // 带图标的按钮
5    Button("偏好设置", systemImage: "gearshape") { ... }
6    // 角色按钮
7    Button("Delete…", systemImage: "trash", role: .destructive) { ... }
8}

切换菜单

 1@State private var isDarkMode = false
 2@State private var isOpen = false
 3
 4Menu("外观") {
 5    // 默认
 6    Toggle("深色模式", isOn: $isDarkMode)
 7    // 自定义
 8    Toggle(isOn: $isOpen) {
 9        Label("开关", systemImage: "suit.heart")
10        Text("标题、图标、子标题")
11    }
12}

单选菜单

 1@State private var selectedOption = "选项1"
 2let options = ["选项1", "选项2", "选项3"]
 3
 4Menu("单选选项") {
 5    ForEach(options, id: \.self) { option in
 6        Button {
 7            selectedOption = option
 8        } label: {
 9            Label(option, systemImage: selectedOption == option ? "checkmark.circle.fill" : "circle")
10        }
11    }
12}

复选菜单

 1@State private var selectedOptions = ["选项1", "选项3"]
 2let options = ["选项1", "选项2", "选项3"]
 3
 4Menu("复选选项") {
 5    ForEach(options, id: \.self) { option in
 6        Button {
 7            if selectedOptions.contains(option) {
 8                selectedOptions.removeAll { $0 == option }
 9            } else {
10                selectedOptions.append(option)
11            }
12        } label: {
13            Label(option, systemImage: selectedOptions.contains(option) ? "checkmark.circle.fill" : "circle")
14        }
15    }
16}

带标签的菜单

1Menu("工具") {
2    Button("打开") {
3        // 打开记事本
4    }
5    Button("新建") {
6        // 新建记事本
7    }
8    Label("记事本", systemImage: "doc.text")
9}

自适应控制菜单

条件性菜单项

 1struct ContentView: View {
 2    @State private var isLoggedIn = false
 3    @State private var userRole: UserRole = .guest
 4    
 5    var body: some View {
 6        Menu("账户") {
 7            if isLoggedIn {
 8                Button("个人资料") {
 9                    // 打开个人资料
10                }
11                
12                Button("设置") {
13                    // 打开设置
14                }
15                
16                Button("登出", action: {
17                    isLoggedIn = false
18                })
19                
20                Divider()
21                
22                Text("已登录")
23                    .foregroundColor(.secondary)
24            } else {
25                Button("登录") {
26                    isLoggedIn = true
27                }
28                
29                Button("注册") {
30                    // 打开注册
31                }
32            }
33            
34            if userRole == .admin {
35                Divider()
36                Button("管理") {
37                    // 打开管理面板
38                }
39            }
40        }
41    }
42}

使用标签来组织菜单项

分组菜单项

 1Menu("格式") {
 2    Text("字体")
 3        .font(.headline)
 4        .foregroundColor(.secondary)
 5    
 6    Button("标题1") {
 7        // 设置字体大小
 8    }
 9    
10    Button("标题2") {
11        // 设置字体大小
12    }
13    
14    Divider()
15    
16    Text("对齐")
17        .font(.headline)
18        .foregroundColor(.secondary)
19    
20    Button("左对齐") {
21        // 左对齐
22    }
23    
24    Button("居中") {
25        // 居中
26    }
27    
28    Button("右对齐") {
29        // 右对齐
30    }
31}

高级菜单功能

菜单状态控制

 1struct ContentView: View {
 2    @State private var isMenuOpen = false
 3    
 4    var body: some View {
 5        Menu("操作", isOpen: $isMenuOpen) {
 6            Button("操作1", action: {
 7                // 执行操作
 8            })
 9            
10            Button("操作2", action: {
11                // 执行操作
12            })
13        }
14        
15        Button("打开菜单") {
16            isMenuOpen = true
17        }
18    }
19}

菜单内容绑定

 1struct ContentView: View {
 2    @State private var menuItems: [MenuItem] = [
 3        MenuItem(title: "新建", icon: "doc.badge.plus", action: createNew),
 4        MenuItem(title: "打开", icon: "folder", action: openFile),
 5        MenuItem(title: "保存", icon: "square.and.arrow.down", action: saveFile),
 6    ]
 7    
 8    var body: some View {
 9        Menu("文件") {
10            ForEach(menuItems, id: \.self) { item in
11                Button(action: item.action) {
12                    Label(item.title, systemImage: item.icon)
13                }
14            }
15        }
16    }
17}
18
19struct MenuItem: Identifiable {
20    let id = UUID()
21    let title: String
22    let icon: String
23    let action: () -> Void
24}

响应式菜单

根据数据动态生成菜单项

 1struct ContentView: View {
 2    @State private var availableFeatures: [Feature] = [
 3        Feature(title: "翻译", icon: "globe", isAvailable: true),
 4        Feature(title: "语音输入", icon: "mic", isAvailable: false),
 5        Feature(title: "屏幕录制", icon: "screen.recording", isAvailable: true),
 6    ]
 7    
 8    var body: some View {
 9        Menu("功能") {
10            ForEach(availableFeatures) { feature in
11                if feature.isAvailable {
12                    Button(action: {
13                        // 使用该功能
14                    }) {
15                        Label(feature.title, systemImage: feature.icon)
16                    }
17                } else {
18                    Label(feature.title, systemImage: feature.icon)
19                        .foregroundColor(.secondary)
20                }
21            }
22        }
23    }
24}
25
26struct Feature {
27    let title: String
28    let icon: String
29    let isAvailable: Bool
30}

最佳实践

1. 菜单项的组织

  • 使用文本和图标来为菜单项创建一个清晰的标签
  • 使用分隔线来分隔不同的菜单组
  • 将相关的菜单项放在一起

2. 条件性菜单项

  • 根据应用状态(如用户登录状态)显示或隐藏菜单项
  • 使用设备特征(如尺寸类)来调整菜单项
  • 动态生成菜单项以适应不同的数据集

3. 无障碍访问

  • 为菜单项提供描述性的标签
  • 使用合适的系统图标来表示菜单项的功能
  • 确保菜单项的内容可读性和足够的点击区域

4. 菜单性能

  • 避免在菜单内部创建复杂的视图层次
  • 对于动态生成的菜单项,考虑使用ForEach而不是硬编码
  • 限制菜单项的数量,以避免布局问题

完整示例:设置菜单

 1import SwiftUI
 2
 3struct SettingsView: View {
 4    @AppStorage("notificationEnabled") private var notificationEnabled = true
 5    @AppStorage("darkMode") private var darkMode = false
 6    @AppStorage("language") private var language = Locale.current.identifier
 7    @State private var showAbout = false
 8    
 9    var body: some View {
10        NavigationView {
11            List {
12                Section("通知") {
13                    Toggle("启用通知", isOn: $notificationEnabled)
14                }
15                
16                Section("外观") {
17                    Toggle("深色模式", isOn: $darkMode)
18                }
19                
20                Section("语言") {
21                    Menu("语言") {
22                        ForEach(Array(Locale.autoupdating.currentIdentifiers), id: \.self) { locale in
23                            Button(locale.capitalized) {
24                                language = locale
25                            }
26                        }
27                    }
28                }
29                
30                Section("关于") {
31                    Button("关于应用", action: {
32                        showAbout = true
33                    })
34                }
35            }
36            .navigationTitle("设置")
37        }
38        .sheet(isPresented: $showAbout) {
39            AboutView()
40        }
41    }
42}
43
44struct AboutView: View {
45    @Environment(\,.dismiss) var dismiss
46    
47    var body: some View {
48        NavigationView {
49            VStack {
50                Image(systemName: "app")
51                    .font(.largeTitle)
52                    .padding()
53                
54                Text("我的应用")
55                    .font(.title)
56                
57                Text("版本 1.0.0")
58                    .foregroundColor(.secondary)
59                
60                Spacer()
61            }
62            .navigationTitle("关于")
63            .navigationBarTitleDisplayMode(.inline)
64            .toolbar {
65                ToolbarItem(placement: .navigationBarTrailing) {
66                    Button("完成", action: {
67                        dismiss()
68                    })
69                }
70            }
71        }
72    }
73}

总结

菜单是SwiftUI中一个强大的UI组件,可以让用户访问和执行各种操作。通过使用标签、图标、分隔线和条件性菜单项,您可以创建出丰富且用户友好的菜单体验。记住保持菜单的简洁性,并根据应用状态和设备特征来动态生成菜单项。

菜单是构建现代化iOS应用的基石之一。