Generics was quite a headache for me when I started learning it in the beginning. Though after some dedicated research in this topic I came across [this] 1 nice tutorial which helped me understanding this topic little deeper. Here I'm sharing the demo code which I'd prepared while learning, hope that help someone.
Demo contains UITableview with different type of cells, each UITableview represents single UITableViewCell with associated Model. I've also added one Hybrid Tableview in order to mix different types of cell in single tableview.
Here is the code.
Creating Generic UITableViewCells first
protocol ProtocolCell {
associatedtype U
static var cellIdentifier:String { get }
func configure(item:U,indexPath:IndexPath)
}
class BaseCell<U>: UITableViewCell, ProtocolCell {
var item:U!
static var cellIdentifier: String {
return String(describing: self)
}
func configure(item:U,indexPath: IndexPath) {
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self) OVERRIDE THIS !!"
backgroundColor = .red
}
//MARK:- INIT
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func commonInit(){
selectionStyle = .none
}
}
class StringCell: BaseCell<String> {
override func configure(item: String, indexPath: IndexPath) {
super.configure(item: item, indexPath: indexPath)
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self), \tData: \(item)"
backgroundColor = UIColor.yellow.withAlphaComponent(0.3)
}
}
class DogCell: BaseCell<Dog> {
override func configure(item: Dog, indexPath: IndexPath) {
super.configure(item: item, indexPath: indexPath)
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self), \tData: \(item.name)"
backgroundColor = UIColor.green.withAlphaComponent(0.3)
}
}
class CountryCell: BaseCell<Country> {
override func configure(item: Country, indexPath: IndexPath) {
super.configure(item: item, indexPath: indexPath)
textLabel?.text = "Row: \(indexPath.row), \tClass: \(U.self), \tData: \(item.name)"
backgroundColor = UIColor.magenta.withAlphaComponent(0.3)
}
}
Then creating Generic UITableViews
class BaseTableView<T_Cell:BaseCell<U_Model>,U_Model>: UITableView, UITableViewDelegate, UITableViewDataSource {
var arrDataSource = [U_Model]()
var blockDidSelectRowAt:((IndexPath) -> Void)?
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addIn(view:UIView) {
view.addSubview(self)
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
leftAnchor.constraint(equalTo: view.readableContentGuide.leftAnchor),
rightAnchor.constraint(equalTo: view.readableContentGuide.rightAnchor),
bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
func commonInit() {
delegate = self
dataSource = self
backgroundColor = .gray
layer.borderWidth = 2
layer.borderColor = UIColor.red.cgColor
register(T_Cell.self, forCellReuseIdentifier: T_Cell.cellIdentifier)
}
//MARK:- DATA SOURCE
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrDataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: T_Cell.cellIdentifier, for: indexPath) as! BaseCell<U_Model>
cell.configure(item: arrDataSource[indexPath.row], indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
blockDidSelectRowAt?(indexPath)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
class DogTableView: BaseTableView<DogCell, Dog> {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
}
class StringTableView: BaseTableView<StringCell, String> {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
class CountryTableView: BaseTableView<CountryCell, Country> {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
class HybridTableView: BaseTableView<BaseCell<Any>, Any> {
// OVERRIDING DEFAULT BEHAVIOUR
override func commonInit() {
super.commonInit()
register(DogCell.self, forCellReuseIdentifier: DogCell.cellIdentifier)
register(StringCell.self, forCellReuseIdentifier: StringCell.cellIdentifier)
register(CountryCell.self, forCellReuseIdentifier: CountryCell.cellIdentifier)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let hybridItem = arrDataSource[indexPath.row]
switch hybridItem {
case is Dog:
let cell = tableView.dequeueReusableCell(withIdentifier: DogCell.cellIdentifier) as! DogCell
cell.configure(item: hybridItem as! Dog, indexPath: indexPath)
return cell
case is String:
let cell = tableView.dequeueReusableCell(withIdentifier: StringCell.cellIdentifier) as! StringCell
cell.configure(item: hybridItem as! String, indexPath: indexPath)
return cell
case is Country:
let cell = tableView.dequeueReusableCell(withIdentifier: CountryCell.cellIdentifier) as! CountryCell
cell.configure(item: hybridItem as! Country, indexPath: indexPath)
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: BaseCell<Any>.cellIdentifier) as! BaseCell<Any>
cell.configure(item: hybridItem, indexPath: indexPath)
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let hybridItem = arrDataSource[indexPath.row]
switch hybridItem {
case is Dog: return 70
case is String: return 140
case is Country: return 210
default: return 50
}
}
}
Models used in the tableview
struct Dog {
let name : String
}
struct Country {
let name : String
}
ViewController
class ViewController: UIViewController {
//MARK:- OUTLETS
@IBOutlet private weak var btn: UIButton!
private let tableViewDog = DogTableView()
private let tableViewString = StringTableView()
private let tableViewCountry = CountryTableView()
private let tableViewHybrid = HybridTableView()
//MARK:- VIEW LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
// doSetupUI()
doSetupUIHybrid()
}
//MARK:- UI SETUP
private func doSetupUI(){
tableViewDog.addIn(view: view)
tableViewDog.arrDataSource = [Dog(name: "Dog 1"),Dog(name: "Dog 2")]
// tableView.arrDataSource = ["First","Second"]
// tableView.arrDataSource = [Country(name: "India"),Country(name: "Nepal")]
tableViewDog.reloadData()
tableViewDog.blockDidSelectRowAt = { [unowned selff = self] indexPath in
print("DID SELECT ROW : \(indexPath.row), VALUE : \(selff.tableViewDog.arrDataSource[indexPath.row].name)")
}
}
private func doSetupUIHybrid(){
tableViewHybrid.addIn(view: view)
tableViewHybrid.arrDataSource = [Dog(name: "Dog1"),
"String1",
Country(name: "India"),
Dog(name: "Dog2"),
"String2",
Country(name: "Nepal")]
tableViewHybrid.reloadData()
tableViewHybrid.blockDidSelectRowAt = { [unowned selff = self] indexPath in
var itemToPrint = ""
let hybridItem = selff.tableViewHybrid.arrDataSource[indexPath.row]
switch hybridItem {
case is Dog: itemToPrint = (hybridItem as! Dog).name
case is Country: itemToPrint = (hybridItem as! Country).name
case is String: itemToPrint = (hybridItem as! String)
default: break
}
print("DID SELECT ROW : \(indexPath.row), VALUE : \(itemToPrint)")
}
}
}