1

How can I skip a complete method in iOS? I know how to test for the iOS version inside a method, but not how to completely ignore a method.

Concrete example: iOS8 added self sizing table view cells, and the methods heightForRowAtIndexPath: and estimatedHeightForRowAtIndexPath: are no longer required. But I do need them for iOS7. Now when I step through the code in iOS8, both methods are called, even though they are no longer needed.

koen
  • 5,383
  • 7
  • 50
  • 89
  • You could use a conditional. (`#if` - `#else` - `#endif`). That way it would be decided at compile time if certain lines of code will be executed or not. – n00bProgrammer Jan 14 '15 at 14:06
  • Of course, but what would I test for? – koen Jan 14 '15 at 14:06
  • @Koen This might help http://stackoverflow.com/questions/24268070/ignore-ios8-code-in-xcode-5-compilation – Alladinian Jan 14 '15 at 14:08
  • Well, you could have two different implementations of the delegate class, and select which one you create. – Hot Licks Jan 14 '15 at 14:08
  • Sounds like your best option is to do the run-time check as the first line of the method and skip the entire body when you don't need it. – Phillip Mills Jan 14 '15 at 14:08
  • @PhillipMills: I thought of that, but both methods require a return value. Maybe I could just return the value of the super in iOS8? – koen Jan 14 '15 at 14:11
  • When responding to a delegate protocol method from an arbitrary class does not necessarily have `[super yourMethodCallHere]` defined. – Ian MacDonald Jan 14 '15 at 14:56
  • This question is a duplicate of: [How to only override a method depending on the runtime system iOS version?](http://stackoverflow.com/questions/26022113/how-to-override-a-method-only-if-ios-version-earlier-thn-8) – smileyborg Jan 15 '15 at 02:02
  • In that thread (in which I apparently participated), the `respondsToSelector` solution is the accepted answer. Here I read that it may not be ideal, and using a separate `delegate` is a better solution. – koen Jan 15 '15 at 13:02

2 Answers2

5

You have a UITableViewDelegate set as the delegate for a UITableView and you want to present different delegate methods to different versions of iOS.

UITableView will call [delegate respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)] before calling [delegate tableView:self heightForRowAtIndexPath:indexPath].

This calls for a custom -respondsToSelector:. In your UITableViewDelegate class add this method.

- (BOOL)respondsToSelector:(SEL)aSelector
{
    // If this device is running iOS 8 or greater.
    if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending) {
        if (aSelector == @selector(tableView:heightForRowAtIndexPath:))
            return NO;

        if (aSelector == @selector(tableView:estimatedHeightForRowAtIndexPath:))
            return NO;
    }

    return [super respondsToSelector:aSelector];
}

UPDATE: I fixed the delegate method names. DUH!

Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
  • @vikingosegundo yeah, I meant `-tableView:heightForRowAtIndexPath:` and `-tableView:estimatedHeightForRowAtIndexPath:`. Sorry about that. – Jeffery Thomas Jan 14 '15 at 19:41
  • This is a clever solution! Not 100% sure that it will work (getting a little "double double toil and trouble" there), but it's certainly worth considering. – Hot Licks Jan 14 '15 at 19:51
3

Provide a different delegate based on iOS version. This allows you to encapsulate your code in meaningfully-named blocks (your interface will indicate that it's iOS7) and you won't be doing any trickery with respondsToSelector that could break subclasses of your class that actually do want to use those methods.

@interface MyTableViewDelegate : NSObject <UITableViewDelegate>
@end

@interface MyTableViewDelegateiOS7 : MyTableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;
@end

@implementation YourClass : .. <..>
// ..
- (void)loadView {
  [super loadView];
  if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending) {
    self.tableView.delegate = [[MyTableViewDelegate alloc] init];
  } else {
    self.tableView.delegate = [[MyTableViewDelegateiOS7 alloc] init];
  }
}
@end
Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
  • `MyTableViewController` is the delegate (and datasouerce) for the `UITableView`. If I understand it correctly, your answer suggests that another class acts as the delegate? – koen Jan 14 '15 at 15:15
  • That's correct. Two other classes, in fact, though the iOS7 one extends from the other, so you won't be duplicating code, just adding the iOS7 functionality where needed. – Ian MacDonald Jan 14 '15 at 15:17
  • Alright, I get it. I think I've read about using a separate delegate/datasource class before, but never saw the need for it. This may just be the case. – koen Jan 14 '15 at 15:20
  • 1
    @Koen, indeed it is always better to have an own class for datasource/delegate, as this improves the reusability and empowers to code according to the Single Responsible Principle. Also have a look at last years WWDC video «Advances in UICollectionViews» or similar. It is shown clearly that apple uses this approach too. – vikingosegundo Jan 14 '15 at 21:54
  • [Advanced User Interfaces with Collection Views](https://developer.apple.com/videos/wwdc/2014/) – vikingosegundo Jan 14 '15 at 21:56
  • I'm trying to implement this approach, but getting crashes, see my follow up question here: http://stackoverflow.com/questions/28011737/refactoring-uitableview-delegates-for-ios7-and-ios8 – koen Jan 18 '15 at 16:01