layoutSubviews Considered Reentrant

I had the weirdest bug the other day: rotating the iPhone worked fine in iOS 7, but not in iOS 8. Actually it was weirder: Some UIView instances adjusted themselves perfectly after the rotation, but some had the wrong size (even though they all had the exact same UIView subclass).

After some digging I discovered that layoutSubview was called several times during rotation, and each time the view had a different frame. Moreover, these calls were not happening sequentially! Instead, layoutSubviews was called with frame1, and before returning it was being called with frame2, and before returning form that it was called with frame3 — all for the same UIView.

There wasn’t a lot of code in my layoutSubviews, nothing that takes long. But it was apparently long enough to cause a bug in some instances.

My solution was to schedule a layout update for a split second later:

- (void)layoutSubviews
{
    [super layoutSubviews];
    [self performSelector:@selector(adjustLayout) withObject:nil afterDelay:0.01]; // avoid re-entering with different frame
}

- (void)adjustLayout
{
    if (CGRectEqualToRect(self.bounds, self.oldBounds)) return;
    // layout code here
    self.oldBounds = self.bounds;
}

The Simplest iOS Badge

SimplestBadgeLabel
In the past I used a UILabel subclass to show a badge. But since the dawn of flat UI, there’s no need to subclass.

Here is all the code you need:

let label = UILabel()
label.clipsToBounds = true
label.layer.cornerRadius = label.font.pointSize * 1.2 / 2
label.backgroundColor = UIColor.grayColor()
label.textColor = UIColor.whiteColor()
label.text = " Some Text "; // note spaces before and after text

All it does is set the cornerRadius of the label’s underlying CALayer, and add spaces before and after the label text. That’s it!

For nicely organized badge extension for UILabel and UIButton and UIBarButtonItem, see this GitHub gist:
Badge.swift