in iOS

Tea Time! 1.1 Update Gets Its Way

bugLast Friday, I decided to fix a bug in Tea Time!. Not so much a bug in Tea Time! actually, but a bug in Apple’s UIPickerView control that showed up when subclassing it. It turns out that it was possible to scroll the picker wheel just so, and one of the rows would come up blank (see screenshot). As far as I could tell, the UIPickerView class was unhappy that I was pre-allocating all the views I was going to show in the picker and handing them out whenever they were requested. So I had to allocate them on the fly or reuse the one in reusingView:

- (UIView *)pickerView:(UIPickerView *)pickerView
        viewForRow:(NSInteger)row forComponent:(NSInteger)component
        reusingView:(UIView *)view;

Once I did that, the bug mysteriously went away. Ah, the joys of dealing with code you have no source to.

So being the tinkerer that I am, I couldn’t just stop there, and I spent an hour fixing something else that was annoying me to no end: When you start Tea Time! there’s always a “click” sound that the picker plays because I’m selecting the last used tea configuration during initialization.

I looked high and low how to fix that, but there seemed to be no way to do it. Nothing in the published SDK information that I could see. But digging through the header file for UIPickerView, I saw this:

@protocol UIPickerViewDataSource, UIPickerViewDelegate;

UIKIT_EXTERN_CLASS @interface UIPickerView : UIView <NSCoding>
{
 //... snip....
  @package
    struct {
        unsigned int needsLayout:1;
        unsigned int delegateRespondsToNumberOfComponentsInPickerView:1;
        unsigned int delegateRespondsToNumberOfRowsInComponent:1;
        unsigned int delegateRespondsToDidSelectRow:1;
        unsigned int delegateRespondsToViewForRow:1;
        unsigned int delegateRespondsToTitleForRow:1;
        unsigned int delegateRespondsToWidthForComponent:1;
        unsigned int delegateRespondsToRowHeightForComponent:1;
        unsigned int showsSelectionBar:1;
        unsigned int allowsMultipleSelection:1;
        unsigned int allowSelectingCells:1;
        unsigned int soundsDisabled:1;
    } _pickerViewFlags;
}

See that last flag? soundsDisabled. Sounds promising, doesn’t it? Unfortunately _pickerViewFlags has @package protection, which means I can’t get to it. Or so the theory goes.

Objective C is surprisingly “good” about letting you get under the hood and bypass protection levels. So I tried accessing those flags with NSObject function setValueForKey, but the stubborn picker would refuse to let me have access and throw an exception instead. Now he was starting to really annoy me. After all, I’m supposed to be working on my other project, not hacking this little app.

I couldn’t stop though. It’s not in my nature.

Oh yeah, Objective C is refusing to give me access to those flags? Fine. I’ll resort to the hackiest or hacks. But I will turn that damn bit off! An object is just a block of memory really, and each member variable is located at a fixed offset from the start of the object. See where this is going? Are you horrified enough? Yup. In the debugger I saw that _pickerViewFlags was exactly 16 bytes from the start UIViewPicker. So I grabbed the pointer to the picker, moved forward to the _pickerViewFlags variable, and stomped the soundsDisabledBit. Mwahahahaha! That will teach you, annoying picker!

Compile, run, and…. “click”. WTF!?!?!? I went in the debugger and checked that I was changing the right bits. Everything was fine. Except that, somehow, the picker was ignoring that. Defeated, I gave up in disgust.

It was that evening when I saw a tweet from @jeff_lamarche about accessing an undocumented function to turn off the sounds in the clicker. Nice timing! He pointed me to his blog post about how to get a listing of all the undocumented SDK functions and how to access them from your programs. I couldn’t believe that there was a function to do exactly what I wanted, but Apple wasn’t exposing it. And for something so simple too!!

It was as simple as

@interface CustomPicker(hidden)
- (void)setSoundsEnabled:(BOOL)isEnabled;
@end

followed by

    [m_picker setSoundsEnabled:NO];
    [m_picker selectRow:settings.m_teaType inComponent:0 animated:NO];
    [m_picker selectRow:settings.m_teaStrength inComponent:1 animated:NO];
    [m_picker selectRow:settings.m_teaFormat inComponent:2 animated:NO];
    [m_picker setSoundsEnabled:YES];

Finally! I had defeated UIPickerView!

Now the catch is, you’re not supposed to use undocumented SDK functions. Apparently that’s grounds for getting your app rejected by Apple and having to resubmit. But I figured I had nothing to lose, and it might be an interesting learning lesson. After all, how exactly does Apple check for undocumented functionality? Do they scan the submitted executable against a set of forbidden entry points? I couldn’t imagine that’s a manual process. But on the other hand, there are reports of other apps getting onto the App Store using undocumented SDK features.

I figured, what the heck, it was a tiny little thing that wasn’t going to hurt anyone. Might as well try it.

teatimeI went ahead submitted the update last Friday, and today (Tuesday) I got the official email with the approval of my new version. Not a bad turnaround time since I imagine they don’t work weekends. It even just made it to the App Store as I was writing this. Shweet!

I’m still left wondering how Apple tries to monitor undocumented SDK usage. Did they not bother with my app since it was so small? Is it really a manual process? (I feel bad for the person in charge of that just thinking about it). Did they catch it and realized it was totally harmless and let it through? Only Apple knows I’m afraid. They really should make their process both more transparent and more consistent and fair for everybody.

But hey, I’m not complaining. No more annoying click at startup 🙂

  • I like your attempt at manually modifying the pointer. I’m amazed that it didn’t work, though…

  • Well, technically it did work. It’s just that Apple isn’t using that flag! I realized afterwards that the bit wasn’t even set to start with. And it’s not like I wasn’t looking at the right memory location, because some of the other bigs where definitely there (like the showSelector) and I could toggle them on and off directly. But not the sound. LOL!

  • Any updates on Tea Time units sold in total? Be interesting to see this for a smaller niche type application.

    Thanks in advance,
    Harry “skipped FB development for iPhone development” Wang