问题
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 Cell
s could be responsible of ValueType
of Int
and others could be String
.
Anyways, my all Cell
s 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 Cell
s 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 Cell
s, as the app heavily depends on Form
s and those Cell
s 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