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

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

疑问

  1. 该方案是否可实现?若可以,遗漏了什么?
  2. 该方案是否根本不可行?若如此,原因是什么?

解决方案

这个方案完全可行,问题出在未给泛型视图添加与SingleSelectionManager关联类型匹配的约束,且直接使用any SingleSelectionManager导致类型不匹配。

核心修改点

  1. 绑定泛型与协议关联类型:让视图的泛型参数与SingleSelectionManager的关联类型统一,确保编译器能识别具体类型。
  2. 替换存在类型擦除的协议声明:将any SingleSelectionManager改为泛型约束类型,解决方法调用的类型歧义问题。
  3. 调整视图层级:原代码中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 12:25:14