Variable of array which holds objects conform to protocol with associated type

非 Y 不嫁゛ 提交于 2021-02-10 07:37:06

问题


I am trying to build my own Form builder. It was going well until the point when I wanted to get a Value from a Cell. I wanted to make it generic as possible, for instance some Cells could be responsible of ValueType of Int and others could be String.

Anyways, my all Cells basically conforms to protocol called BaseCell. I assigned a associatedType to this protocol and added a variable value which would return different types for each Cell. After some tryings I come with with the following code which was thrown Protocol 'BaseCell' can only be used as a generic constraint because it has Self or associated type requirements four different places, two of them being functions, I changed the functions to be generic to protocols associatedType and it now throws the error two places.

protocol BaseCell where Self: UITableViewCell {
    associatedtype ValueType
    var value: ValueType { get }
}

class FieldCell: UITableViewCell, BaseCell {
    typealias ValueType = String
    var field : UITextField = { return UITextField() }()
    var value: String {
        return self.field.text ?? String()
    }
}

I even tried to make these Section and Form to be generics, however it led cells variable to one type of subclass of BaseCell. Whole point of Section struct is to hold different kind Cells that will be in a Section of UITableView.

struct Section {
    //throws "Protocol 'BaseCell' can only be used as a generic constraint because it has Self or associated type requirements"
    var cells: [BaseCell] = []
    
    //it was throwing same error above however after applying generics, it has gone away
    mutating func append<T: BaseCell>(_ cell: T) {
        self.cells.append(cell)
    }
}

And class of Form is basically holds array of Sections to represented in the UI and I use its methods to ease up the building the UITableView and usage of it delegates.

class Form {
    
    private var sections: [Section] = [Section]()
    
    subscript(_ section: Int) -> Section {
        get {
            return self.sections[section]
        }
    }
    
    //throws "Protocol 'BaseCell' can only be used as a generic constraint because it has Self or associated type requirements"
    subscript(_ indexPath: IndexPath) -> BaseCell {
        get{
            return self.sections[indexPath.section].cells[indexPath.row]
        }
    }
    
    @discardableResult
    func section(_ section: Section = Section()) -> Form {
        self.sections.append(section)
        return self
    }
    
    //it was throwing same error above however after applying generics, it has gone away
    @discardableResult
    func cell<T: BaseCell>(_ cell: T) -> Form {
        self.sections.indices.last.map { self.sections[$0].cells.append(cell) }
        return self
    }
}

Final reminder here is I thought using Type Erasure to somehow get around this issue, again however, It would not be the best as another point of this attempt is to not have to make any castings. I just want to build a form using UITableView as this simple and it was working before the associatedType thing. Here a piece of code from the project.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //optional
    return self.form[indexPath]
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    //optional
    return self.form[indexPath].height
}

UPDATE

Based on the last comment, This how Form class actually looks like in the project.

class Form {
    
    private var sections: [Section] = [Section]()
    
    subscript(_ section: Int) -> Section {
        get {
            return self.sections[section]
        }
    }
    
    subscript(_ indexPath: IndexPath) -> BaseCell {
        get{
            return self.sections[indexPath.section].cells[indexPath.row]
        }
    }
    
    func numberOfRows(inSection section: Int) -> Int {
        self.sections[section].cells.count
    }
    
    func numberOfSections() -> Int {
        self.sections.count
    }
    
    func titleForHeader(inSection section: Int) -> String {
        self.sections[section].header
    }
    
    func titleForFooter(inSection section: Int) -> String {
        self.sections[section].footer
    }
    
    @discardableResult
    func section(_ section: Section = Section()) -> Form {
        self.sections.append(section)
        return self
    }
    
    @discardableResult
    func cell<T: BaseCell>(_ cell: T) -> Form {
        self.sections.indices.last.map { self.sections[$0].cells.append(cell) }
        return self
    }
}

And I use an instance of Form in FormViewController: UITableViewController to easily build up a UITableView as such,

class FormViewController: UITableViewController {
    
    var form: Form = Form()
    
    override func loadView() {
        self.tableView = UITableView(style: .grouped)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
       
        self.tableView.delegate = self
        self.tableView.dataSource = self
        
        self.tableView.separatorStyle = .singleLine
        self.tableView.allowsSelection = false
        self.tableView.backgroundColor = .systemGroupedBackground
        
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        //optional self.form.sections
        return self.form.numberOfSections()
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        //optional
        return self.form.numberOfRows(inSection: section)
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return self.form.titleForHeader(inSection: section)
    }
    
    override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return self.form.titleForFooter(inSection: section)
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //optional
        // And at this part returns any kind of `Cell` that conforms to `BaseCell` and `UITableViewCell` based on the `indexPath` 
        return self.form[indexPath]
    }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        //optional
        return self.form[indexPath].height
    }
}

Finally in a ViewController I plan to use different types of Cells, as the app heavily depends on Forms and those Cells could be responsible for name and surname as String and others examples which can be Int, Double, Date or custom enum value.

class ViewController: FormViewController {
    
    var fieldCell: FieldCell = {
        let cell = FieldCell()
        return cell
    }()
    
    var buttonCell: ButtonCell = {
        let cell = ButtonCell()
        return cell
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.form
            .section()
            .cell(fieldCell)
            .cell(buttonCell)

    }
    
    func doStuff() {
        fieldCell.value // I expect String here
        buttonCell.value // I know buttoncell does not make sense but lets say I expect Int here
    }
}

来源:https://stackoverflow.com/questions/63017827/variable-of-array-which-holds-objects-conform-to-protocol-with-associated-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!