重用具有不同视图模型类型的 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
的分离,但为了简化上述方向的演示应该足够了。
问题:
我的项目中有一个特定的 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
的分离,但为了简化上述方向的演示应该足够了。