【问题标题】:Reusing a UITableView with different view model types重用具有不同视图模型类型的 UITableView
【发布时间】:2020-08-07 12:54:17
【问题描述】:

问题:

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

我的尝试:

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

我尝试将泛型声明添加到我的 Coordinator 类(参见下面的 CustomTableView)的 init 函数中,但是,我得到了如下错误: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)
            }
    }
}

我已经尝试了所有方法,但似乎一直在绕圈子。我曾想过拥有一个引用我所有其他视图模型的视图模型,然后将其传递到我的自定义表格视图中,但我没有意识到也许我遗漏了一些东西,也许我解决这一切的尝试都有缺陷.

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

提前致谢。

【问题讨论】:

    标签: ios swift uitableview cocoa-touch swiftui


    【解决方案1】:

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

    我相信这个错误: SwiftUI: UIRepresentable view error states a reference to a generic type requires arguments in <...>

    仅表示在这部分:

      struct CustomTableView<T: CustomTableViewDelegate>: UIViewRepresentable {
    
        var viewModel: T
    
        class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource {
    
          var customTableView: CustomTableView
    

    而不是这个:

    var customTableView: CustomTableView
    

    你应该有类似的东西:

    var customTableView: CustomTableView<SomeDelagateYouHaveSomewhere>
    

    当使用泛型类型时,请确保您提供了与它一起使用的类型。 虽然我真的认为这完全不是真正的问题。

    基本上你在结构内部有一个类定义,它引用了这个结构......为什么? 这个类只能在这个结构的范围内使用重要吗?

    我认为您想要的只是继承 UITableView 类来创建您的自定义类,然后在需要时使用或覆盖它。

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

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

    【讨论】:

    • 不幸的是,这对我不起作用。我的问题和描述末尾都提到了我试图做的事情。简而言之,我想重用我的自定义表格视图,而不是为每个视图模型创建一个新的。有了接受的答案,我现在可以将任何视图模型与我创建的单个自定义表视图一起使用。谢谢你的努力。在 SwiftUI 中,在自定义视图结构中使用了 Coordinator 类:hackingwithswift.com/books/ios-swiftui/…
    【解决方案2】:

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

    如果您将所有UITablView 委托/数据源回调移动/复制到视图模型中,那么实际上您根本不需要上下文协调器,因此通用实体可以是

    // 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 分开,但为了简化演示,上面的方向应该足够了。

    【讨论】:

    • 这解决了我的问题。我现在可以简单地让我的任何视图模型使用我的协议。你能解释一下为什么在 CustomTableView 中需要“& CustomTableViewModel>”吗?我删除了那部分,一切仍然正常。
    • @LondonGuy,我使用configure(tableView:),你可能不会。
    • 刚刚注意到我确实使用了它,但实际上我删除了ObservableObject &amp;,因为我的协议看起来像这个协议CustomTableViewDelegate: ObservableObject, UITableViewDataSource, UITableViewDelegate { 我还将重命名我的协议,因为我猜这可能会让我感到困惑未来。
    猜你喜欢
    • 1970-01-01
    • 2013-03-21
    • 2020-07-21
    • 1970-01-01
    • 1970-01-01
    • 2018-12-21
    • 2016-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多