I think I fully understand the concept of delegation, my question is that when we do:
class someViewController : UIViewController, UITableViewDelega
would it ever be possible that we wouldn't want to set tableView.delegate to self
Yes, if you would implement datasource and delegate in it's own class.
why is Xcode forcing us to do some extra work here?
to allow as much freedom in our architectures as possible.
An example with separate classes for datasource and delegate.
class TableViewDatasource: NSObject, UITableViewDataSource {
// implement datasource
}
class TableViewDelegate : NSObject, UITableViewDelegate {
// implement delegate
}
class ViewController: UIViewController {
IBOutlet weak var tableView: UITableView! {
didSet {
tableView.dataSource = tableViewDatasource
tableView.delegate = tableViewDelegate
}
}
let tableViewDatasource = TableViewDatasource()
let tableViewDelegate = TableViewDelegate()
}
This allows higher reusability and favours composition over inheritance, if you'd allow view controllers to hold different implementations of delegate and datasource.
You deal with smaller classes and those are easier to test and maintain.
It is even possible to design complete apps, that don't need any view controller subclasses.
datasource/delegate design can be as sophisticated as you like.
As an example I want to show you a new project of mine: TaCoPopulator. It is a framework to populate table view and collection views transparently by splitting it up in distinct task to follow the SOLID Principles:
import UIKit
class CollectionViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
private var datasource: ViewControllerDataSource?
override func viewDidLoad() {
super.viewDidLoad()
self.datasource = ViewControllerDataSource(with: collectionView)
}
}
import UIKit
class TableViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private var datasource: ViewControllerDataSource?
override func viewDidLoad() {
super.viewDidLoad()
self.datasource = ViewControllerDataSource(with: tableView)
}
}
import TaCoPopulator
class IntDataProvider: SectionDataProvider {
override init(reuseIdentifer: @escaping (Int, IndexPath) -> String) {
super.init(reuseIdentifer: reuseIdentifer)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
self.provideElements([1,2,3,4,5,6,7,8,9])
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
self.provideElements(self.elements() + [10, 11, 12, 13, 14, 15])
}
}
}
}
import TaCoPopulator
class ViewControllerDataSource {
init(with populatorView: PopulatorView) {
self.populatorView = populatorView
setup()
}
var intSelected: ((Int, IndexPath) -> Void)?
var stringSelected: ((String, IndexPath) -> Void)?
let dp1 = IntDataProvider {
_ in return "Cell"
}
let dp2 = StringDataProvider {
_ in return "Cell"
}
weak var populatorView: PopulatorView?
var populator: Populator?
func setup(){
dp1.selected = {
[weak self] element, indexPath in
self?.intSelected?(element, indexPath)
}
dp2.selected = {
[weak self] element, indexPath in
self?.stringSelected?(element, indexPath)
}
let collectionViewCellConfig: (Any, TextCollectionViewCell, IndexPath) -> TextCollectionViewCell = {
element, cell, _ in
cell.textLabel?.text = "\(element)"
return cell
}
let tableViewViewCellConfig: (Any, UITableViewCell, IndexPath) -> UITableViewCell = {
element, cell, _ in
cell.textLabel?.text = "\(element)"
return cell
}
if let populatorView = populatorView as? UICollectionView {
let section1factory = SectionCellsFactory(parentView: populatorView, provider: dp1, cellConfigurator: collectionViewCellConfig)
let section2factory = SectionCellsFactory(parentView: populatorView, provider: dp2, cellConfigurator: collectionViewCellConfig)
self.populator = Populator(with: populatorView, sectionCellModelsFactories: [section1factory, section2factory])
}
if let populatorView = populatorView as? UITableView {
let section1factory = SectionCellsFactory(parentView: populatorView, provider: dp1, cellConfigurator: tableViewViewCellConfig)
let section2factory = SectionCellsFactory(parentView: populatorView, provider: dp2, cellConfigurator: tableViewViewCellConfig)
self.populator = Populator(with: populatorView, sectionCellModelsFactories: [section1factory, section2factory])
}
}
}