OutlineGroup
我最近在 iOS 14 中发现了 SwiftUI (我使用的是 Xcode 12 beta 6 )。无论是单独使用还是使用时,它都非常有效,List
可以根据需要从树形结构、已识别数据的底层集合中计算视图和披露组。
也就是说,如果您有一个递归定义,那么这对于构建元素struct
来说非常有效。DisclosureGroup
然而,我正在寻找一些稍微不同的东西,让我能够构建一个“下拉”(或汉堡包)菜单。
在 iOS 14 中,还有另一个名为 的控件Menu
,它可以完全按照我想要的方式呈现“下拉”(或汉堡)菜单:
然而,我似乎无法将两者一起使用来构建Menu
基于递归表示的数据的动态,例如:
struct Tree<Value: Hashable>: Hashable {
let value: Value
var children: [Tree]? = nil
}
Run Code Online (Sandbox Code Playgroud)
菜单按以下方式构建:
struct SideMenu: View {
var body: some View {
Menu {
Button(action: {}) {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Profile")
.foregroundColor(.gray)
.font(.headline)
}
Button(action: {}) {
Image(systemName: "person.3")
.foregroundColor(.gray)
.imageScale(.large)
Text("Family Members")
.foregroundColor(.gray)
.font(.headline)
}
Button(action: {}) {
Image(systemName: "calendar")
.foregroundColor(.gray)
.imageScale(.large)
Text("Events")
.foregroundColor(.gray)
.font(.headline)
}
} label: {
Image(systemName: "line.horizontal.3")
}
}
}
Run Code Online (Sandbox Code Playgroud)
有没有一种方法可以Menu
从递归数据构建,类似于使用 所做的事情OutlineGroup
?
小智 5
我喜欢用枚举来表示树,以避免不可能或不一致的状态。此外,您需要递归 UI 函数调用,但使用方法会使编译器对我来说失败(Xcode 12 beta 6),因此我将菜单部分分离在不同的视图中,这似乎可行。现在您有了一个可以从 ViewModel 构建的完全动态的菜单。
import SwiftUI
enum ViewEvent {
case profileTapped
case familyMembersTapped
case eventsTapped
case foldersTapped
case deletedItemsTapped
}
struct MenuItem: Identifiable {
var id: String { return text }
let text: String
let systemImage: String?
let action: ViewEvent?
}
enum MenuContent: Identifiable {
var id: String {
switch self {
case let .item(item): return item.id
case let .submenu(text, _): return text
}
}
case item(MenuItem)
indirect case submenu(text: String, content: [MenuContent])
}
struct ViewState {
let menu: [MenuContent]
let content: String
static var `default`: ViewState {
.init(
menu: [
.item(MenuItem(text: "Profile", systemImage: "person", action: .profileTapped)),
.item(MenuItem(text: "Family Members", systemImage: "person.3", action: .familyMembersTapped)),
.item(MenuItem(text: "Events", systemImage: "calendar", action: .familyMembersTapped)),
.submenu(text: "More", content: [
.item(MenuItem(text: "Folders", systemImage: "folder.fill", action: .foldersTapped)),
.item(MenuItem(text: "Deleted", systemImage: "trash.fill", action: .deletedItemsTapped))
])
],
content: "Content")
}
}
struct ContentView: View {
@State var viewState: ViewState
var body: some View {
HStack(alignment: .top, spacing: 16) {
AppMenu(contents: viewState.menu) {
Image(systemName: "line.horizontal.3")
}
Text("Content")
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding()
}
}
struct AppMenuItem: View {
let item: MenuItem
func dispatch(_ action: ViewEvent) {
// todo: call viewModel.dispatch
print("Sending action \(action)")
}
init(item: MenuItem) {
self.item = item
}
var body: some View {
Button(action: {
item.action.map { action in dispatch(action) }
}) {
item.systemImage.map { systemImage in
Image(systemName: systemImage)
.foregroundColor(.gray)
.imageScale(.large)
}
Text(item.text)
.foregroundColor(.gray)
.font(.headline)
}
}
}
struct AppSubmenu: View {
let text: String
let contents: [MenuContent]
var body: some View {
AppMenu(contents: contents) {
HStack {
Text(text)
Image(systemName: "chevron.right")
}
}
}
}
struct AppMenu<Label: View>: View {
let label: () -> Label
let contents: [MenuContent]
init(contents: [MenuContent], @ViewBuilder label: @escaping () -> Label) {
self.contents = contents
self.label = label
}
var body: some View {
Menu {
ForEach(contents) { content in
// In case this is an item
if case let .item(item) = content {
AppMenuItem(item: item)
}
// In case this is a submenu
if case let .submenu(text, contents) = content {
AppSubmenu(text: text, contents: contents)
}
}
} label: { label() }
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1552 次 |
最近记录: |