3

I want to add a new custom UIButtonType to the UIButton class via a category like so:

enum {
    UIButtonTypeMatteWhiteBordered = 0x100
};

@interface UIButton (Custom)

+ (id)buttonWithType:(UIButtonType)buttonType;

@end

Is it possible to get the super implementation of that overridden method somehow?

+ (id)buttonWithType:(UIButtonType)buttonType {
    return [super buttonWithType:buttonType];
}

The code above is not valid since super refers to UIControl in this context.

jscs
  • 63,694
  • 13
  • 151
  • 195

3 Answers3

7

You can replace the method at runtime with your own custom method like so:

#import <objc/runtime.h>

@implementation UIButton(Custom)

// At runtime this method will be called as buttonWithType:
+ (id)customButtonWithType:(UIButtonType)buttonType 
{
    // ---Add in custom code here---

    // This line at runtime does not go into an infinite loop
    // because it will call the real method instead of ours. 
    return [self customButtonWithType:buttonType];
}

// Swaps our custom implementation with the default one
// +load is called when a class is loaded into the system
+ (void) load
{
    SEL origSel = @selector(buttonWithType:);

    SEL newSel = @selector(customButtonWithType:);

    Class buttonClass = [UIButton class];

    Method origMethod = class_getInstanceMethod(buttonClass, origSel);
    Method newMethod = class_getInstanceMethod(buttonClass, newSel);
    method_exchangeImplementations(origMethod, newMethod);
}

Be careful how you use this, remember that it replaces the default implementation for every single UIButton your app uses. Also, it does override +load, so it may not work for classes that already have a +load method and rely on it.

In your case, you may well be better off just subclassing UIButton.

Edit: As Tyler notes below, because you have to use a class level method to make a button this may be the only way to override creation.

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • @Kendall Don't you think that's a bit too much of a hack? IMHO subclassing is *so* much simpler and easier to debug. – Jacob Relkin Dec 03 '10 at 19:26
  • Usually, yes. But there are some things where it's nice to change the behavior system wide... note that I did recommend subclassing in this case. I just thought that people should know it was actually possible when needed, since the question subject itself was pretty general. – Kendall Helmstetter Gelner Dec 03 '10 at 20:50
  • That is an awesome technique. How does the runtime know to call the original method from within the new method? Can you stack arbitrarily-many implementations this way? – Tyler Dec 03 '10 at 21:08
  • Oh, I get it. It knows to call the old imp, because the pointers from selector->implementation are swapped - so the "customX" selector actually calls the original implementation. And it looks like, yes, you can stack these arbitrarily, although each addition will have to have it's own selector (different method name) to avoid clashes. I'm getting this from here: http://www.cocoadev.com/index.pl?MethodSwizzling – Tyler Dec 03 '10 at 21:18
  • +1 swizzling is the only realistic way of doing this. – Dave DeLong Dec 03 '10 at 21:47
  • Thanks for posting the answer Tyler, I think you have it exactly right... and it would appear from your message that a subclass would not work after all, so this might be a good case for it. – Kendall Helmstetter Gelner Dec 04 '10 at 05:22
  • Yes I know about method swizzling but i was looking if there was a "sanctioned" or "official" way of doing it. And generally i refrain from doing such hacks in production code. –  Dec 06 '10 at 15:57
  • It's not a hack. It's how the runtime works, using published calls to simply slightly alter the calling order between classes. I wouldn't hesitate to create a schema dynamically either if the situation called for it. – Kendall Helmstetter Gelner Dec 06 '10 at 18:18
3

Jacob has a good point that category methods act differently than subclass methods. Apple strongly suggests that you only provide category methods that are entirely new, because there are multiple things that can go wrong otherwise - one of those being that defining a category method basically erases all other existing implementations of the same-named method.

Unfortunately for what you're trying to do, UIButton seems to be specifically designed to avoid subclassing. The only sanctioned way to get an instance of a UIButton is through the constructor [UIButton buttonWithType:]. The problem with a subclass like Jacob suggests (like this):

@implementation MyCustomButton 

+ (id)buttonWithType:(UIButtonType)buttonType {
  return [super buttonWithType:buttonType]; //super here refers to UIButton
}

@end

is that the type returned by [MyCustomButton buttonWithType:] will still be a UIButton, not MyCustomButton. Because Apple hasn't provided any UIButton init methods, there's not really a way for a subclass to instantiate itself and be properly initialized as a UIButton.

If you want some customized behavior, you can create a custom UIView subclass that always contains a button as a subview, so that you can take advantage of some of UIButton's functionality.

Something like this:

@interface MyButton : UIView {}

- (void)buttonTapped;

@end

@implementation MyButton

-(id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = self.bounds;
    [button addTarget:self action:@selector(buttonTapped)
     forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:button];
  }
  return self;
}

- (void)buttonTapped {
  // Respond to a button tap.
}

@end

If you want the button to do different things depending on more complex user interactions, you can make more calls to [UIButton addTarget:action:forControlEvents:] for different control events.

Reference: Apple's UIButton class reference

Tyler
  • 28,498
  • 11
  • 90
  • 106
2

No, this is not possible when you use a category to augment a class' functionality, you are not extending the class, you are actually wholly overriding the existing method, you lose the original method completely. Gone like the wind.

If you create a subclass of UIButton, then this is totally possible:

enum {
    UIButtonTypeMatteWhiteBordered = 0x100
};

@interface MyCustomButton : UIButton {}

@end

@implementation MyCustomButton 

+ (id)buttonWithType:(UIButtonType)buttonType {
    return [super buttonWithType:buttonType]; //super here refers to UIButton
}

@end
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
  • Subclassing doesn't work, because `UIButton` is a class cluster. You could override this method on a subclass, but then you'd explicitly have to call `[MyCustomButton buttonWithType:...]`, and if you're going to do that, you may as well just `alloc/init` it yourself. The better answer is to swizzle the IMP – Dave DeLong Dec 03 '10 at 21:46
  • 1
    Ahh Objective C, the only language where one can satisfy a penchant for imp swizzling. – Chris Hatton Apr 11 '13 at 12:06