I created test project to reproduce the issue https://github.com/msnazarow/DiffarableTest
Simply you need to pass several steps
- Create new target
- Create
Base
class that inheritUITableViewDiffableDataSource
and alsoUITableViewDelegate
but do not implementUITableViewDelegate
methods - In another target(main for simplify) inherit
Base
class and implement any ofUITableViewDelegate
method (for exampledidSelectRowAt
) - None of implementing methods would work
CodePudding user response:
You have to configure the delegate funcs in your BaseDataSource
class to be overridden in subclasses.
So, first step, comment-out your didSelectRowAt
func in extension MyDataSource
:
extension MyDataSource {
// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// print("CELL SELECTED")
// }
}
and implement didSelectRowAt
in BaseDataSource
:
open class BaseDataSource<T: Model & Hashable>: UITableViewDiffableDataSource<Int, T>, UITableViewDelegate {
public init(tableView: UITableView) {
super.init(tableView: tableView) { tableView, indexPath, model in
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! Configurable
cell.configure(with: model)
return cell as? UITableViewCell
}
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Base Cell Selected", indexPath)
}
}
When you run the app and tap on the 3rd row, you should get debug output:
Base Cell Selected [0, 2]
To implement that in your subclass, you can override it:
extension MyDataSource {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("MyDataSource Cell Selected", indexPath)
}
}
You'll get an error: Cannot override a non-dynamic class declaration from an extension ... so make it dynamic in BaseDataSource
:
open class BaseDataSource<T: Model & Hashable>: UITableViewDiffableDataSource<Int, T>, UITableViewDelegate {
public init(tableView: UITableView) {
super.init(tableView: tableView) { tableView, indexPath, model in
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! Configurable
cell.configure(with: model)
return cell as? UITableViewCell
}
}
public dynamic func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Base Cell Selected", indexPath)
}
}
Tapping on the 3rd row now should output:
MyDataSource Cell Selected [0, 2]
Note that didSelectRowAt
in BaseDataSource
will be called if there is no sub-classed delegate. Plus, if you want some code to also run in didSelectRowAt
in BaseDataSource
, you can call super
from the subclass:
extension MyDataSource {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
super.tableView(tableView, didSelectRowAt: indexPath)
print("MyDataSource Cell Selected", indexPath)
}
}
and selecting the 3rd row outputs:
Base Cell Selected [0, 2]
MyDataSource Cell Selected [0, 2]
Edit
After a little more research, this is considered by some to be a "bug" as it seems to have changed between Swift versions.
However, one big change along the way is that Swift originally did, but no longer, infers @objc
. Everyone ran into that when it became necessary to add @objc
to funcs such as when used in selectors.
So, two ways to handle this...
First, as shown above, implement "do nothing" funcs for any table view delegate funcs you want to override in your subclass.
The second option, which sounds like you would prefer, is to declare the @objc
method inside your subclass (or its extension):
extension MyDataSource {
// add this line
@objc (tableView:didSelectRowAtIndexPath:)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("MyDataSource extension Cell Selected", indexPath)
}
}
Now you can go back to your original code, and you only need to add that one line to get the functionality needed.