简体   繁体   中英

How can I dynamically declare a variable type then use it as an argument when instantiating a custom view in SwiftUI?

What I'm doing:

I'm making a reusable tableView and have just about finished, but I'm unable to reference properties in my view model because I have declared my model type as AnyObject inside the table view struct.

I want to dynamically declare the variable type when instantiating the tableView .

For example, this is how I use my custom table view:

struct MyView: View {
    @EnvironmentObject var myViewModel: MyViewModel

    var body: some View {

        return

          CustomTableView(model: myViewModel as MyViewModel)
    }
}

As you can see, I have to declare the type in my view because I'm using the AnyObject type in my table view struct. This is because the model will vary depending on where I'm using the custom table view, and so I need the flexibility.

This is my table view Struct:

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) {           
    }
}

What I want to do:

Either have the table view automatically use the ViewModel type name declared when instantiating the table view, or pass in the correct ViewModel type when instantiating the table view.

It's not clear to me how this could be done. I was thinking type alias might work, but the more I read into it, the more I realised it wasn't the right solution.

Passing in the type as a string doesn't work because Swift will recognise the argument as a string.

What would be the cleanest way to do this?

Thanks in advance.

The possible approach is to use generics (conforming also to any custom protocol if/when needed), so you don't need to cast types and use it as

struct MyView: View {
    @EnvironmentObject var myViewModel: MyViewModel

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

so CustomTableView should be declared as

struct CustomTableView<Model:ObservableObject>: UIViewRepresentable {

    var model: Model

    ...

Tested with Xcode 11.4 / iOS 13.4

Update: below is compilable example

// 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 { }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM