SwiftUI中如何实现基于SingleSelectionManager的通用单选视图?
问题描述
尝试实现一个通用View,用于在ScrollView中渲染一组任意的、遵循Identifiable协议的项目。视图希望使用遵循SingleSelectionManager协议的ViewModel对象来提供Item类型的数据并处理其他操作。
协议定义
protocol SingleSelectionManager : AnyObject { associatedtype Item : Identifiable var items : Array<Item> { get } func isThisSelected(item: Item) -> Bool func userDidTapOn(item : Item) }
存在错误的通用View代码
struct GenericSingleSelectionView<Item, Content: View> : View { let selectionManager : any SingleSelectionManager var rowContent: (Item, Bool) -> Content var body: some View { LazyVStack { ScrollView(.vertical, showsIndicators: false) { ForEach(selectionManager.items) { item in rowContent(item, selectionManager.isThisSelected(item: item)) .contentShape(Rectangle()) .onTapGesture { selectionManager.userDidTapOn(item: item) } } } } } }
已知可以使用SwiftUI的List,但更倾向当前方案。对泛型协议和关联类型存在困惑,当前代码在ForEach视图中出现多个错误:
- Cannot convert value of type 'Array' to expected argument type 'Binding'
- Member 'userDidTapOn' cannot be used on value of type 'any SingleSelectionManager'; consider using a generic constraint instead
疑问
- 该方案是否可实现?若可以,遗漏了什么?
- 该方案是否根本不可行?若如此,原因是什么?
解决方案
这个方案完全可行,问题出在未给泛型视图添加与SingleSelectionManager关联类型匹配的约束,且直接使用any SingleSelectionManager导致类型不匹配。
核心修改点
- 绑定泛型与协议关联类型:让视图的泛型参数与
SingleSelectionManager的关联类型统一,确保编译器能识别具体类型。 - 替换存在类型擦除的协议声明:将
any SingleSelectionManager改为泛型约束类型,解决方法调用的类型歧义问题。 - 调整视图层级:原代码中
LazyVStack包裹ScrollView的层级错误,会导致滚动异常,需改为ScrollView包裹LazyVStack。
修改后的完整代码
protocol SingleSelectionManager : AnyObject { associatedtype Item : Identifiable var items : Array<Item> { get } func isThisSelected(item: Item) -> Bool func userDidTapOn(item : Item) } struct GenericSingleSelectionView<Manager: SingleSelectionManager, Content: View> : View { let selectionManager: Manager var rowContent: (Manager.Item, Bool) -> Content var body: some View { ScrollView(.vertical, showsIndicators: false) { LazyVStack { ForEach(selectionManager.items) { item in rowContent(item, selectionManager.isThisSelected(item: item)) .contentShape(Rectangle()) .onTapGesture { selectionManager.userDidTapOn(item: item) } } } } } }
使用示例
// 示例ViewModel class SampleSelectionManager: SingleSelectionManager { typealias Item = String var items: [String] = ["选项1", "选项2", "选项3"] private var selectedItem: String? func isThisSelected(item: String) -> Bool { item == selectedItem } func userDidTapOn(item: String) { selectedItem = item } } // 调用通用视图 struct ContentView: View { let manager = SampleSelectionManager() var body: some View { GenericSingleSelectionView(selectionManager: manager) { item, isSelected in Text(item) .padding() .background(isSelected ? Color.blue : Color.gray) .foregroundColor(.white) .cornerRadius(8) } } }
内容的提问来源于stack exchange,提问作者LukeSideWalker
相关产品推荐
相关产品推荐

