UIButton troubles, a better solution

I described in my previous post how you can change the class of a live object instance. The use-case was a workaround for bug in Cocoa Touch’s UIKit. What if I could fix the bug, instead of applying a work around in my sub-class?

Update: Buttons created with UIButtonTypeRoundedRect is a special case returning a private subclass of UIButton, this bugfix can therefor not safely be used with rounded buttons. Good news is that they already have a large nice touch area.

With Objective-C you can

In short the problem is that the implementation of the factory method buttonWithType: in UIButton do not respect subclasses, so it will always return instances of UIButton. This is bad since it is the only method available to use to get custom buttons. Without access to the source code for UIButton changes can not be made to the implementation, so in order to fix the bug in UIKit we must wrap the original implementation of buttonWithType: with our own method that applies the bugfix.

Adding a category with a new implementation will not work, since that would shadow the original implementation. What is need is some sort of AOP, and Objective-C gives us enough of this but under the name of method-swizzling.

We can intercept method calls to buttonWithType: by swizzling the implementation with the implementation of our own method. In Objective-C all method implementations are called by name, that name is what is know as a selector. By swizzling the implementations we can make messages with the old selector call our new implementation, and vice versa.

We do this by adding a category to UIButton with this class method:

  Method oldMethod = class_getClassMethod(self, @selector(buttonWithType:));
  Method newMethod = class_getClassMethod(self, @selector(__buttonWithType:));
  method_exchangeImplementations(oldMethod, newMethod);

The class method load is called once for each class and category as they are loaded into the run-time, and is the optimal place to add initialization code. Think of it as static initializer blocks in Java.

And that is it, apart from implementing the actual bugfix in our __buttonWithType: method.

A better bugfix

The fix I did in my previous post only worked if the new subclass of UIBUtton did not introduce any new instance variables. This is a limitation, and not quite as obvious to new users of our bugfix, so this time around the bugfix will work with any subclass.

The trick here is allocate a new instance of the correct subclass if needed, and copy over the instance variables. Implementation:

  UIButton* button = [self __buttonWithType:type];
  Class buttonClass = [button class];
  if (button && buttonClass != self && buttonClass == [UIButton class]) {
    size_t oldSize = class_getInstanceSize([button class]);
    size_t newSize = class_getInstanceSize(self);
    button->isa = self;
    if (oldSize < newSize) {
      UIButton* newButton = [self alloc];
      memcpy(newButton, button, oldSize);
      button = [newButton autorelease];
  return button;

At first glance the first statement might look like a recursive call, but remember that we have swizzled the implementations so it will call the original implementation of buttonWithType:.

Also note that we must autorelease the new instance that we allocated, and that we must not release the original instance as that one is already owned by an auto release pool.

And that is how you go about fixing bugs in Objective-C for code that you do not have access to the source code for.

This Post Has 4 Comments

  1. Mikael

    When I try to implement this bug fix my buttons looses it’s designs. I can only see the title not the lines och graphics. I want to use UIButtonTypeRoundedRect.
    Can you help me locate the problem?

    1. Fredrik Olsson

      I have not used this with [UIButton buttonWithType:UIButtonTypeRoundedRect] before, turns out that this is a special case returning a UIRoundedRectButton. UIRoundedRectButton is a private subclass of UIButton, and therefor it gets kind of ruined when we change the class.

      What you must do in this special case is to create your custom class (Where you override pointInside:withEvent: I assume?) as a subclass of UIRoundedRectButton. I can not recommend this as UIRoundedRectButton is private, and could very well be dropped and/or break in any point release upgrade of the iPhone firmware.

      But thanks for bringing this to my attention. I have now updated the post to inform readers about this issue, and updated the code to avoid this special case.

Leave a Reply