Human Interface Guidelines compliant UIButton subclass
iOS Human Interface Guidelines tell us:
Make it easy for people to interact with content and controls by giving each interactive element ample spacing. Give tappable controls a hit target of about 44 x 44 points.
Sometimes icons for the buttons, provided by designers, are smaller than the above mentioned 44 x 44 points size (44 x 44 non-retina, 88 x 88 retina).
There are several ways to solve this guideline inconsistency:
- Don't do anything - the App will look bad and feel bad - the user won't be able to tap on tiny buttons.
- Make the designer change it - sometimes the designer is unavailable; it's a bother to ask the designer to do this if he is not in your office, sitting next to you; sometimes the button really looks good smaller.
- Stretch the icon - the icon will look pixelated or blurred; we should aim for being pixel perfect in our Apps.
- Override the
pointInside:withEvent:
method.
According to the docs, the pointInside:withEvent:
method
returns a Boolean value indicating whether the receiver contains the specified point.
We can use this method, to enlarge the area where the button captures touches (we still have to remember that because of how the view hierarchy works in iOS, the touch has to be in the bounds of the superview of our view).
- (BOOL)pointInside:(CGPoint)point
withEvent:(UIEvent *)event {
const static CGFloat minimumSide = 44;
CGFloat differenceY = minimumSide - self.bounds.size.height;
CGFloat differenceX = minimumSide - self.bounds.size.width;
CGFloat insetY = MAX(0, differenceY);
CGFloat insetX = MAX(0, differenceX);
return CGRectContainsPoint(CGRectInset(self.bounds, -insetX, -insetY), point) || [super pointInside:point withEvent:event];
}
- We use
self.bounds
instead ofself.frame
because the documentation states:
point - A point that is in the receiver’s local coordinate system (bounds).
-
We use
CGRectContainsPoint(rect, point)
to do a simple geometric check if thepoint
is inside of therect
. -
We use
CGRectInset(rect, dx, dy)
to create a bigger rectangle to check if the point is contained in it.dx
anddy
are the differences between 44 and width or height accordingly.CGRectInset
returns a rectangle with the same center point, so we don't have to do math ourselves. -
We call
super
for safety reasons. We don't know if Apple isn't using some hack now or in future iOS version, so better safe than sorry. Theoretically there should be no reason to call it, as we already check if the point is in the bounds of the (even bigger) button.
I use it by creating a thin UIButton
subclass, for example ABOutsideTouchButton
and just use this as a superclass of most of the interface elements.