在 SwiftUI 中实例化自定义视图时,如何动态声明变量类型然后将其用作参数?

Posted

技术标签:

【中文标题】在 SwiftUI 中实例化自定义视图时,如何动态声明变量类型然后将其用作参数?【英文标题】:How can I dynamically declare a variable type then use it as an argument when instantiating a custom view in SwiftUI? 【发布时间】:2020-04-08 23:40:21 【问题描述】:

我在做什么:

我正在制作一个可重复使用的 tableView 并且即将完成,但我无法在我的视图模型中引用属性,因为我已在表视图结构中将我的模型类型声明为 AnyObject

我想在实例化tableView时动态声明变量类型。

例如,这是我使用自定义表格视图的方式:

struct MyView: View 
    @EnvironmentObject var myViewModel: MyViewModel

    var body: some View 

        return

          CustomTableView(model: myViewModel as MyViewModel)
    

如您所见,我必须在视图中声明类型,因为我在表视图结构中使用AnyObject 类型。这是因为模型会根据我使用自定义表格视图的位置而有所不同,因此我需要灵活性。

这是我的表格视图结构:

struct CustomTableView: UIViewRepresentable 

    var model: AnyObject
    class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource 

        var customTableView: CustomTableView
        let cellIdentifier = "MyCell"

        init(_ customTableView: customTableView) 
            self.customTableView = customTableView
        

        func numberOfSections(in tableView: UITableView) -> Int 
            return 1
        

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
            return 7
        

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

            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MyTableViewCell
            return cell
        
    

    func makeCoordinator() -> DabbleTableView.Coordinator 
        Coordinator(self)
    

    func makeUIView(context: Context) -> UITableView   
        let cellIdentifier = "MyCell"
        let tableView = UITableView()
        tableView.delegate = context.coordinator
        tableView.dataSource = context.coordinator
        tableView.register(MyTableViewCell.self, forCellReuseIdentifier: cellIdentifier)
        tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
        return tableView    
    

    func updateUIView(_ uiViewController: UITableView, context: Context)            
    

我想做的事:

要么让表格视图自动使用在实例化表格视图时声明的ViewModel 类型名称,要么在实例化表格视图时传入正确的ViewModel 类型。

我不清楚如何做到这一点。我在想类型别名可能会起作用,但我越读越觉得这不是正确的解决方案。

将类型作为字符串传递不起作用,因为 Swift 会将参数识别为字符串。

最干净的方法是什么?

提前致谢。

【问题讨论】:

【参考方案1】:

可能的方法是使用泛型(如果/需要时也符合任何自定义协议),因此您不需要转换类型并将其用作

struct MyView: View 
    @EnvironmentObject var myViewModel: MyViewModel

    var body: some View 
        CustomTableView(model: myViewModel)
    

所以CustomTableView 应该声明为

struct CustomTableView<Model:ObservableObject>: UIViewRepresentable 

    var model: Model

    ...

使用 Xcode 11.4 / ios 13.4 测试

更新:以下是可编译的示例

// base protocol for CustomTableView model
protocol CustomTableViewModel 
    func numberOfSections() -> Int
    func numberOfRows(in section: Int) -> Int


// some specific model
class MyViewModel: ObservableObject, CustomTableViewModel 
    func numberOfSections() -> Int  1 
    func numberOfRows(in section: Int) -> Int  7 


// usage
struct MyView: View 
    @EnvironmentObject var myViewModel: MyViewModel

    var body: some View 
        CustomTableView(model: myViewModel)
    


// generic table view
struct CustomTableView<Model:ObservableObject & CustomTableViewModel>: UIViewRepresentable 

    var model: Model

    class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource 

        var customTableView: CustomTableView
        let cellIdentifier = "MyCell"

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

        func numberOfSections(in tableView: UITableView) -> Int 
            customTableView.model.numberOfSections()
        

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
            customTableView.model.numberOfRows(in: section)
        

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

            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MyTableViewCell
            return cell
        
    

    func makeCoordinator() -> Coordinator 
        Coordinator(self)
    

    func makeUIView(context: Context) -> UITableView 
        let cellIdentifier = "MyCell"
        let tableView = UITableView()
        tableView.delegate = context.coordinator
        tableView.dataSource = context.coordinator
        tableView.register(MyTableViewCell.self, forCellReuseIdentifier: cellIdentifier)
        tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
        return tableView
    

    func updateUIView(_ uiViewController: UITableView, context: Context) 
    


class MyTableViewCell: UITableViewCell  

【讨论】:

嗨,这部分有效。但我仍然无法在我的视图模型中引用属性和函数。例如,在 numberOfRowsInSection 委托方法中,我做了这个 self.customTableView.model.nameOfFunction() 并且我仍然无法访问该函数。我有大约 5 个不同的 ViewModel 将用于这个表视图。表视图中声明的模型名称不能是静态的。它需要能够与我在表格视图中使用的任何视图模型一起工作。我应该能够访问使用的模型中的属性和函数,并且不需要手动编辑 tableview 文件 @LondonGuy,是的,我展示了共同的方向。您需要为预期模型创建自定义协议,并将其添加到泛型中。 您能给我一个基本示例,说明您的视图模型在自定义协议下的外观吗?之后我可以做进一步的研究。 @LondonGuy,请参阅更新部分

以上是关于在 SwiftUI 中实例化自定义视图时,如何动态声明变量类型然后将其用作参数?的主要内容,如果未能解决你的问题,请参考以下文章

使用核心数据实例化自定义对象

SwiftUI当总内容宽度小于屏幕宽度时,如何在动态水平滚动视图中居中内容

子类化自定义 UICollectionViewLayout 实现一定的删除和插入效果

子类化自定义 UIView - 如何初始化

导航时如何在 SwiftUI 中使用自定义视图转换?

从 NSDictionary 实例化自定义类