如何复刻SwiftUI Picker的选择绑定逻辑,实现自定义Menu的选中态管理
实现方案
SwiftUI不支持直接遍历@ViewBuilder传入的不透明子视图读取tag属性,系统Picker依赖内部私有能力实现该逻辑,以下是两种可落地的实现方案:
方案1:模拟系统Picker的Tag声明风格
通过环境变量传递选中状态、自定义MenuItem视图来模拟系统API使用体验,和你期望的调用方式基本一致:
1. 定义环境键与MenuItem
// 定义选中状态的环境键 private struct MenuSelectionKey: EnvironmentKey { static let defaultValue: AnyHashable? = nil } extension EnvironmentValues { var menuSelection: AnyHashable? { get { self[MenuSelectionKey.self] } set { self[MenuSelectionKey.self] = newValue } } } // 自定义MenuItem视图,自动读取环境中的选中状态匹配自身tag struct MenuItem<Content: View, Tag: Hashable>: View { let tag: Tag let content: Content @Environment(\.menuSelection) private var selection init(tag: Tag, @ViewBuilder content: () -> Content) { self.tag = tag self.content = content() } var body: some View { content .foregroundColor(selection as? Tag == tag ? .accentColor : .primary) } } // 给View加扩展,方便类似系统.tag()的调用 extension View { func menuTag<Tag: Hashable>(_ tag: Tag) -> some View { MenuItem(tag: tag) { self } } }
2. 实现Menu容器
struct Menu<Selection: Hashable, Content: View>: View { @Binding var selection: Selection @ViewBuilder var content: () -> Content var body: some View { VStack(content: content) .environment(\.menuSelection, AnyHashable(selection)) } }
3. 调用示例
struct ScreenView: View { @State private var selectedMenu: String = "Home" var body: some View { Menu(selection: $selectedMenu) { Text("Home") .menuTag("Home") Text("Settings") .menuTag("Settings") Text("Profile") .menuTag("Profile") } } }
方案2:数据源驱动实现(更简洁易维护)
如果不需要完全模拟系统Picker的声明风格,直接传入数据源给Menu内部用ForEach生成子视图是更简单的方案:
struct Menu<Item: Hashable, Content: View>: View { @Binding var selection: Item let items: [Item] @ViewBuilder var itemContent: (Item) -> Content var body: some View { VStack { ForEach(items, id: \.self) { item in itemContent(item) .foregroundColor(selection == item ? .accentColor : .primary) .onTapGesture { selection = item } } } } } // 调用示例 struct ScreenView: View { @State private var selectedMenu: String = "Home" private let menuItems = ["Home", "Settings", "Profile"] var body: some View { Menu(selection: $selectedMenu, items: menuItems) { item in Text(item) } } }
内容的提问来源于stack exchange,提问作者robertsan
相关产品推荐
相关产品推荐

