DTTableViewManager
supports registering closures instead of delegate methods for 4 delegate protocols:
UITableViewDelegate
UITableViewDataSource
(partially)UITableViewDragDelegate
UITableViewDropDelegate
With those closures big improvement over delegate methods is that closures are actually strongly typed:
manager.register(PostCell.self) { mapping in
mapping.didSelect { cell, model, indexPath in
// cell is of type PostCell
// model is of type Post
}
}
With such huge amount of delegate methods it felt important to not invent new glossary of methods over existing delegate methods. Therefore, all event methods closely follow naming of delegate methods they replace, for example:
// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
mapping.willDisplay { cell, model, indexPath in }
// func tableView(_ tableView: UITableView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool
mapping.shouldBeginMultipleSelectionInteraction { cell, model, indexPath in true }
Whenever cell or reusable view delegate method is referring to can be provided, it will be, and closure accepts three arguments: (View, Model, IndexPath) -> ReturnType
. However, for some delegate methods cell or view is not created yet. Such delegate methods are called with signature (Model, IndexPath) -> ReturnType. For example:
// `UITableViewDelegate.tableView(_:heightForRowAt:)`
mapping.heightForCell { model, indexPath in
return 44
}
You can also register event closures on DTTableViewManager
instance directly, but in this case you need to provide Cell/View type again:
manager.register(PostCell.self)
manager.didSelect(PostCell.self) { cell, model, indexPath in }
Keep in mind, that in order for this to work, cell needs to be registered prior to registering event closures, because they attach to specific mapping instance(s). Also, this kind of event registration only works for
ModelTransfer
compatible views.
This may be beneficial for example if you show same type of cell in different section with different appearances, but selection needs to work the same way. Registering event closure through DTTableViewManager
attaches this event to all compatible mappings.
If delegate method is unrelated to cells or views, it is available on DTTableViewManager
instance directly:
func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext)
// `UITableViewDelegate.tableView(_:shouldUpdateFocusIn:)`
manager.shouldUpdateFocus { context in true }
With DTTableViewManager
implementing ALL delegate methods, you may wonder about perfomance of all of this. Also you might be concerned, that there are some methods that you don't want to be implemented because they have perfomance implications. Great example of this is func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)
method. When used for collections with large amounts of data, it may severely impact performance if implemented. It's much better to use estimated sizes instead.
Well, I'm happy to report that performance is absolutely not an issue with DTTableViewManager
. If you don't register closure for a delegate method, UITableView
will think that this delegate method is not implemented and will never call it.
Wait, how is that possible? It's actually pretty simple: DTTableViewManager
uses dynamic method dispatch, and specifically responds(to selector:)
method. If no event closures are registered, DTTableViewManager
simply answers that this delegate method is not implemented, and UITableView
will never call the implementation.
This means, that even though all delegate methods are implemented, for UITableView
implemented methods - are methods you register closures with, which is exactly what you want.
Absolutely. Simply declare protocol conformance and implement method in your view controller:
class PostsViewController: UIViewController, DTTableViewManageable, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
manager.register(PostCell.self)
manager.memoryStorage.setItems(posts)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// React to cell selection
}
}
If your controller implements delegate method, DTTableViewManager
detects it and attempts to redirect delegate method to you. It's important to understand priorities on which this is made, generally following happens:
- Try to execute event, if cell and model type satisfy requirements
- Try to call delegate or datasource method on
DTTableViewManageable
instance - If two previous scenarios fail, fallback to whatever default
UITableView
has for this delegate or datasource method
Because event closures are stored on DTTableViewManager
instance, referencing self
in those closures will cause reference cycle, so make sure to capture self weakly in those closures:
manager.register(PostCell.self) { [weak self] mapping in
mapping.didSelect { cell, model, indexPath in
// self?.didSelect ...
}
}
There is also one subtle case, that you need to watch for. When you capture self weakly, don't try to make it non-optional in mapping
closure, because it will cause a retain cycle:
manager.register(PostCell.self) { [weak self] mapping in
// Here self is captured weakly
guard let self = self else { return }
// Now self is captured strongly for closures below
mapping.didSelect { cell, model, indexPath in
// Retain cycle:
// self.didSelect ...
}
}
If you want to make self non-optional, you can check this in event closure itself:
manager.register(PostCell.self) { [weak self] mapping in
// Here self is captured weakly
mapping.didSelect { cell, model, indexPath in
guard let self = self else { return }
// For this closure self is captured weakly, no retain cycle.
// self.didSelect ...
}
}
While it's possible to register multiple closures for a single event, only first closure will be called once event is fired. This means that if the same event has two closures for the same view/model type, last one will be ignored. You can still register multiple event handlers for a single event and different view/model types.
DTTableViewManager
considers most of datasource methods to be handled by provided Storage
, and therefore does not provide closure replacement for those. Some of the methods are available though, such as for moving cells, and prodiving index titles.