Last Updated: February 25, 2016
·
4.036K
· sage042

Fix the UIButton highlight/selected image flickering

If you have a UIButton that uses the same image for the UIControlStateSelected and UIControlStateHighlighted, you probably have noticed the images flickers when the button is quickly touched. UIKit handles the button's state as an enumerated bit flag, so during a quick touch event the button exists in a state where it is both selected and highlighted.

To fix the flicker, the UIButton needs to have the image set for the state UIControlStateSelected | UIControlStateHighlighted.

UIImage *sl = [self imageForState:UIControlStateSelected];
[aButton setImage:sl forState:(UIControlStateSelected | UIControlStateHighlighted)];

To apply this fix to all UIButtons loaded from NIB files, create a new UIButton category containing the following methods.

void UIButtonAwakeFromNibSwizzle() {
    Class c = [UIButton class];
    SEL orig = @selector(awakeFromNib);
    SEL new = @selector(fixedAwakeFromNib);

    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}

- (void)fixedAwakeFromNib {
    // swizzled functions get exchanged, so calling fixedAwakeFromNib calls the original function
    [self fixedAwakeFromNib];

    UIImage *hl = [self imageForState:UIControlStateHighlighted];
    UIImage *sl = [self imageForState:UIControlStateSelected];
    if (hl == sl) {
        [self setImage:hl forState:(UIControlStateSelected | UIControlStateHighlighted)];
    }
}

Now if you call UIButtonAwakeFromNibSwizzle() in your App Delegate's didFinishLaunchingWithOptions, fixedAwakeFromNib will be called whenever a UIButton calls awakeFromNib.