14

I have the following class hierarchy:

class ScrollableViewController: UIViewController, UITableViewDelegate { // ... }

That implements one UITableViewDelegate protocol method, e.g. tableView:willDisplayCellAt:

In my class SpecificScrollableViewController, which inherits from ScrollableViewController, new optional protocol methods don't get called any more, e.g. tableView(_:heightForRowAt:)

Alex Popov
  • 2,509
  • 1
  • 19
  • 30

2 Answers2

38

tl;dr you need to prefix the function declaration with its Objective-C declaration, e.g.

@objc(tableView:heightForRowAtIndexPath:)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  // return stuff
}

I was tipped off to this being a solution thanks to the Swift 3 Migration Guide which states:

If you implement an optional Objective-C protocol requirement in a subclass of a class that declares conformance, you’ll see a warning, “Instance method ‘…’ nearly matches optional requirement ‘…’ of protocol ‘…’”

• Workaround: Add an @objc(objectiveC:name:) attribute before the implementation of the optional requirement with the original Objective-C selector inside.

I'm fairly certain this is a bug: it appears that the the runtime dynamism that allows checking for selector capability does not get properly bridged in the Grand Swift Renaming when the protocol method is in the subclass. Prefixing the function declaration with the Objective-C name properly bridges the Swift to Objective-C and allows Swift 3 renamed methods to be queried with canPerformAction:withSender: from within Objective-C

Community
  • 1
  • 1
Alex Popov
  • 2,509
  • 1
  • 19
  • 30
  • 1
    @cgossain my pleasure! I struggled with this for 6 hours before I found it :( – Alex Popov Oct 19 '16 at 03:09
  • you can also use private before the function to silence the warning – rashfmnb Dec 06 '16 at 17:56
  • 2
    @rashfmnb then you'll get an `unrecognized selector sent to instance` crash. – Slayter Jan 11 '17 at 17:47
  • Hi @AlexPopov, I am using as prescribed solution, but it didn't work for Xcode 8.3. `@optional - (void)qb_imagePickerController:(QBImagePickerController *)imagePickerController didFinishPickingAssets:(NSArray *)assets;` is the objective-c protocol. I had implemented the protocol as `@objc(qb_imagePickerController:didFinishPickingAssets:) func qb_imagePickerController(_ imagePickerController: QBImagePickerController, didFinishPickingAssets assets: [AnyObject])`. But, the warning still coming. – Milan Kamilya Jun 22 '17 at 08:09
  • 2
    This appears to be still present for optional protocol methods as of Xcode 9.0.1 iOS 11 – Damo Nov 01 '17 at 15:22
3

This appears to be fixed in Swift 3.0.1 for normal subclasses, but is not fixed for generic subclasses:

class A: NSObject, UITableViewDelegate {}

class B: A {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
}

class C<T>: A {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
}

print(#selector(B.tableView(_:didSelectRowAt:))) // tableView:didSelectRowAtIndexPath:
print(#selector(C<Int>.tableView(_:didSelectRowAt:))) // tableView:didSelectRowAt:

See: https://bugs.swift.org/browse/SR-2817

To fix it: https://stackoverflow.com/a/39416386/1109892

@objc(tableView:heightForRowAtIndexPath:)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  // return stuff
}
Community
  • 1
  • 1
Adam Studenic
  • 2,115
  • 2
  • 25
  • 22