Fighting iOS7’s Control Center with UIButtons at the bottom of the screen.

Do me a favor and load up the calendar app on iOS7. Now, at the bottom tap “Today”. Did the word “Today” highlight? No? Try it again. And again. Wait, it just worked? Press it again. I bet it didn’t work. It’s completely random, and depends on how quickly you press the button.

The same goes for any button in the bottom 50 or so pixels of the screen. As of iOS 7.0.4, both the back gesture and the one for bringing up the Control Center eat highlighted button states. Not only do we have borderless buttons by default, but only about 10% of the time do they have a highlighted state. This is a fundamental flaw in UX.

In Hum we took cues from the UISlider Control to create nice, tappable buttons. We have an obvious pressed or highlighted state as well. These states are important for UI feedback, if only to confirm that I did indeed press a button. These controls don’t need to be as obvious as we’ve designed for Hum, but it’s important that something happens.

Hum's Buttons

Hum’s Buttons

So if the highlight events are being eaten by the back and control center gestures at an OS level, how am I supposed to give the user feedback that the button was even pressed?

A workaround.

We’ve found a pretty simple workaround for this. Instead of using standard properties like button.highlighted = YES or button.selected = YES that don’t work anyway in that bottom band, we simply fake a highlight by first delaying the final method that’s called on button press. Simply set the background image of the unhighlighted button to the highlighted state with an unperceived delay BEFORE we call the final method. It looks like this:

[self.stopRecordingButton setImage:[UIImage imageNamed:@"stopRecordingButton"] forState:UIControlStateNormal];
[self.stopRecordingButton setImage:[UIImage imageNamed:@"stopRecordingButton-highlighted"] forState:UIControlStateHighlighted];
[self.stopRecordingButton addTarget:self action:@selector(stopRecordingDelay) forControlEvents:UIControlEventTouchUpInside];

-(void)stopRecordingDelay
{
    [self.stopRecordingButton setImage:[UIImage imageNamed:@"stopRecordingButton-highlighted"] forState:UIControlStateNormal];

    [self performSelector:@selector(stopRecording) withObject:nil afterDelay:0.025f];
}

- (void)stopRecording
{
    [self.stopRecordingButton setImage:[UIImage imageNamed:@"stopRecordingButton"] forState:UIControlStateNormal];

    //Do real stuff
}

When creating our buttons we set two images for the button, one for normal, the other for highlighted. Pay attention to the image names.

We then tell the button to call an intermediary method that sets the unhighlighted button’s background to the highlighted state, faking the standard highlighting interaction that doesn’t work in that 50px space at the bottom of the screen. After a tiny, unperceived delay of .025 seconds we then call our real method and reset our button, if needed, to the original, unhighlighted state.

Now, regardless of how quickly the user taps the buttons within that bottom band they’ll receive the feedback that their button was pressed.

Not the worst hack on the planet, I hope.