重用具有不同视图模型类型的 UITableView

Reusing a UITableView with different view model types

问题:

我的项目中有一个特定的 UITableView,我发现自己从这个 UITableView 复制和粘贴代码并将代码用于类似的屏幕;这种处理事情的方法真的让我很烦恼。

我试过的:

我试图找到解决这个问题的方法。例如,我尝试使用委托和泛型,但一直出错。

我尝试将通用声明添加到我的 Coordinator class(请参阅下面的 CustomTableView)初始化函数,但是,我只是遇到了如下错误:Protocol can only be used as a generic constraint because it has Self or associated type requirements,所以我只是将它移回了结构.

我考虑过在 UITableView 的结构中将我的视图模型列表声明为 @ObservedObject,然后继续我的项目。然而,这似乎是一个简单的解决方法,这让我认为这是解决这个问题的错误方法。

我错过了什么?

必须有一种方法可以重用相同的 tableView,只需传入其关联的 viewModel,而无需在 tableView 结构中声明 ObservedObjects。

我有一个如下所示的协议:

protocol CustomTableViewDelegate: ObservableObject {
    // ...
}

该协议中的所有闭包基本上都是 UITableViewDelegates 方法的克隆。我为什么要这样做?我这样做是为了在任何时候我的任何视图模型需要使用我的自定义 UITableView,我都可以简单地遵循这个委托。

就像我在我的视图模型中所做的一样:

class CustomViewModel: ObservableObject, CustomTableViewDelegate {
   // ...
}

这是我的习惯UITableView(我删除了一些功能以减少代码):

struct CustomTableView<T: CustomTableViewDelegate>: UIViewRepresentable {

    var viewModel: T

    class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource {

        var customTableView: CustomTableView

        init(_ customTableView: CustomTableView) {
            self.customTableView = customTableView
        }

        func numberOfSections(in tableView: UITableView) -> Int {
            self.customTableView.viewModel.numberOfSections(in: tableView)
        }

        func makeCoordinator() -> CustomTableView.Coordinator {
            Coordinator(self)
        }

        func makeUIView(context: Context) -> UITableView {
            let coordinator = context.coordinator
            return context.coordinator.customTableView.viewModel.makeUIView(coordinator: coordinator)
        }

        func updateUIView(_ uiView: UITableView, context: Context) {
            context.coordinator.customTableView.viewModel.updateUIView(uiView, coordinator: context.coordinator)
        }
    }
}

我的主要观点是:

struct MyMainView: View {
    @EnvironmentObject var customViewModel: CustomViewModel

    var body: some View {

        return

            VStack {
                CustomTableView<CustomViewModel>(viewModel: customViewModel)
            }
    }
}

我已经尝试了所有方法,但似乎一直在兜圈子。我考虑过拥有一个引用我所有其他视图模型的视图模型,然后将其传递到我的自定义 table 视图中,但我没有意识到也许我遗漏了什么,也许是我试图解决的问题这都有缺陷。

那么,我怎样才能简单地设置我的 CustomTableView 以便它可以与任何具有 ObservableObject 类型的 ViewModel 一起工作?

提前致谢。

老实说,我很难理解你想做什么。

我相信这个错误: SwiftUI:UIRepresentable 视图错误声明对泛型类型的引用需要 <...>

中的参数

简单的表示在这部分:

  struct CustomTableView<T: CustomTableViewDelegate>: UIViewRepresentable {

    var viewModel: T

    class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource {

      var customTableView: CustomTableView

而不是这个:

var customTableView: CustomTableView

你应该有这样的东西:

var customTableView: CustomTableView<SomeDelagateYouHaveSomewhere>

使用泛型类型时,只需确保提供要使用的类型即可。 虽然我真的认为这完全不是这里的真正问题。

基本上你所拥有的是引用该结构的结构内部的 class 定义...为什么? 这个 class 只能在这个结构的范围内使用很重要吗?

我想你想要的只是继承 UITableView class 来创建你的自定义视图,然后在需要时使用它或覆盖它。

另外请注意 struct 和 class 之间的区别(比如继承)。您可以在这里找到一些有用的信息:https://learnappmaking.com/struct-vs-class-swift-how-to/

这个答案可能不是您想要的,但我希望它能帮助您走上正轨。

这是可能的解决方案。使用 Xcode 11.4 / iOS 13.4

测试

如果你 move/duplicate 所有 UITablView delegate/datasource 回调到视图模型,那么实际上你根本不需要上下文协调器,所以通用实体可以是

// generic table view model protocol
protocol CustomTableViewModel: UITableViewDataSource, UITableViewDelegate {
    func configure(tableView: UITableView)
}

// generic table view that depends only on generic view model
struct CustomTableView<ViewModel:ObservableObject & CustomTableViewModel>: UIViewRepresentable {
    @ObservedObject var viewModel: ViewModel

    func makeUIView(context: Context) -> UITableView {
        let tableView = UITableView()
        viewModel.configure(tableView: tableView)
        return tableView
    }

    func updateUIView(_ tableView: UITableView, context: Context) {
        tableView.reloadData()
    }
}

这是用法示例

// some specific model
class MyViewModel: NSObject, ObservableObject, CustomTableViewModel {
    let items = ["one", "two", "three"]
    let cellIdentifier = "MyCell"

    func configure(tableView: UITableView) {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(MyTableViewCell.self, forCellReuseIdentifier: cellIdentifier)
        tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count }

    func numberOfRows(in section: Int) -> Int { 1 }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MyTableViewCell
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
}

struct MyView: View {
    @EnvironmentObject var myViewModel: MyViewModel

    var body: some View {
        CustomTableView(viewModel: myViewModel)
    }
}

注意:实际上在下一个分解步骤中,它可能是 Presenter 概念与 ViewModel 的分离,但为了简化上述方向的演示应该足够了。