Can I swizzle didSelectRowAtIndexPath: of UITableViewDelegate?
Asked Answered
E

1

5

The problem is this:

I need to be able to get analytics on didSelectRowAtIndexPath throughout a big existing app with lots of tableViews.

My first thought of this is doing method swizzling on didSelectRowAtIndexPath: but my app crashes with "unrecognized selector sent to instance" message depending on the stuff is accessed in the original didSelectRowAtIndexPath implementation.

Here is how I try to achieve this in a UIViewController category:

#import "UIViewController+Swizzle.h"

@implementation UIViewController (Swizzle)

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPathSwizzled:(NSIndexPath *)indexPath {
    NSLog(@"Log something here");
    [self tableView:tableView didSelectRowAtIndexPathSwizzled:indexPath];
}

+ (void) initialize {
    BOOL conformsToTableViewDelegate = class_conformsToProtocol(self, @protocol(UITableViewDelegate));
    if(conformsToTableViewDelegate) {
        Method method1 = class_getInstanceMethod(self, @selector(tableView:didSelectRowAtIndexPath:));
        Method method2 = class_getInstanceMethod(self, @selector(tableView:didSelectRowAtIndexPathSwizzled:));
        method_exchangeImplementations(method1, method2);
    }
}


@end

Can this be accomplished? If so, what am I doing wrong?

Thanks!

Edlin answered 4/10, 2013 at 22:34 Comment(7)
That'll cause all data source methods to be swizzled, including ones used by the UIKit to implement UIKit functionality in your application... Do you really have so many implementations of that method that you can't just do a global search and paste in a line of metrics gathering code in each?Dolt
Thank you for the input however I don't understand the comment. How would method_exchangeImplementations(method1, method2); cause all data source methods to be swizzled, including ones used by the UIKit? I did this for UIButtons' sendAction: and it works pretty well for the purpose. I guess there isn't a way to do this for methods in protocolsEdlin
There are some other issues here; using +initialize like this will override the +initialize in UIViewController, if any. And this code will result in every single instance of UIViewController or subclass to have that method swizzled. This has nothing to do with "methods in protocols" and everything to do with changing the internal behavior of the UIKit.Dolt
Well this is exactly my intention to have the implementation of didSelectRowAtIndexPath (method of UITableViewDelegate protocol, no internal behavior in the UIKit) swizzled. The swizzling will take place only on the UIViewController instances that conformsToTableViewDelegate and therefore implement the didSelectRowAtIndexPath. True that the method is optional and I should make another check but the problem is that I get a crash and don't understand why. Also the swizzling will just add a line of code to gather metrics and not alter in any other way the original implementation, if any.Edlin
You can't do it from +initialize in a category; you'll be overriding UIViewController's +initialize (if it has one now or at any time in the future). Modifying system provided classes is verboten for a reason; it causes mysterious behaviors and crashes, even when you think what you are doing is harmless. Post the crash.Dolt
I try to swizzle a method in UIApplicationDelegate and it doesn't work... I try to swizzle it in initialize to swizzle didFinishLaunching. It not crashes but methods don't swizzle... I read that bbum said that modifying is verboten, but I believe swizzling is authorized by Apple for market app. I have already swizzled method in interface, but I never do that in a protocol...Coreencorel
Hi Horatiu, Am facing the same issue. Hope you resolved the Swizzling issue. But, am trying for UITableview Can you please post your solution here? Thanks.Unjaundiced
S
4

It might be late for giving answer, but hopefully it will help some other developer. Below link have help me a lot.

https://www.jianshu.com/p/9a93ae99fb27

   extension UITableView {

        @objc static func swizzleTableView() {

          guard self == UITableView.self else {
            return
          }

          let originalTableViewDelegateSelector = #selector(setter: self.delegate)
          let swizzledTableViewDelegateSelector = #selector(self.nsh_set(delegate:))

          let originalTableViewMethod = class_getInstanceMethod(self, originalTableViewDelegateSelector)
          let swizzledTableViewMethod = class_getInstanceMethod(self, swizzledTableViewDelegateSelector)

          method_exchangeImplementations(originalTableViewMethod!,
                                         swizzledTableViewMethod!)
        }

        @objc open func nsh_set(delegate: UITableViewDelegate?) {
          nsh_set(delegate: delegate)

          guard let delegate =  delegate else { return }

          let originalDidSelectSelector = #selector(delegate.tableView(_:didSelectRowAt:))
          let swizzleDidSelectSelector = #selector(self.tableView(_:didSelectRowAt:))

          let swizzleMethod = class_getInstanceMethod(UITableView.self, swizzleDidSelectSelector)
          let didAddMethod = class_addMethod(type(of: delegate), swizzleDidSelectSelector, method_getImplementation(swizzleMethod!), method_getTypeEncoding(swizzleMethod!))

          if didAddMethod {
            let didSelectOriginalMethod = class_getInstanceMethod(type(of: delegate), NSSelectorFromString("tableView:didSelectRowAt:"))
            let didSelectSwizzledMethod = class_getInstanceMethod(type(of: delegate), originalDidSelectSelector)
            if didSelectOriginalMethod != nil && didSelectSwizzledMethod != nil {
              method_exchangeImplementations(didSelectOriginalMethod!, didSelectSwizzledMethod!)
            }
          }
      }

      @objc open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.tableView(tableView, didSelectRowAt: indexPath)

      }
    }
Sacchariferous answered 10/12, 2019 at 7:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.