Encoding UIButton Title for State Preservation and Restoration

Hollywood workWhen an iOS app restores its state – when the user switched back into the app but the operating system have killed it off because it needed the memory space – often it needs to do so while its data store haven’t been brought online. This is more true if you’re using Core Data through a UIManagedDocument subclass. Even when you open the document inside applicationWillFinishLaunchingWithOptions  Cocoa will commence state restoration first before the event loop has its first cycle. Thus it’s likely that the document won’t finish opening during state restoration and made its NSManagedObjectContext ready for you to fetch your data objects to re-populate your view contents.

This means that your UI restoration code need to work without Core Data objects. Often it means that you need to save and restore data that your views are displaying at the moment – things such as button titles that changes dynamically. At the first iteration of the run loop you’ll app will be similar of a hollywood film set house – only the façade is present and nothing else behind it – until the Core Data stack is ready. You’ll need to do this to avoid displaying empty buttons when the app starts, because your app will start drawing itself just after state restoration completes and the run loop is about to have its first cycle.

Saving UIButton titles in particular requires more work than plain labels or text fields because button labels can be attributed strings and they have four states which may be associated with their own labels. Furthermore I learned that the numberOfLines property may need to be saved along with the title – really important if you have a multiline attributed string as a label.

So I made these two category methods on NSCoder that works quite well. They save the titles for each state in a dictionary under a single key and read it back in, taking care not to mix up NSString titles with their NSAttributedString counterparts.

@interface NSCoder (UIExtras)
#if TARGET_OS_IPHONE
-(void) encodeButtonTitle:(UIButton*) button forKey:(NSString*) key;
-(void) decodeButtonTitle:(UIButton*) button forKey:(NSString*) key;
#endif
@end

@implementation NSCoder (UIExtras)
#if TARGET_OS_IPHONE
-(void) encodeButtonTitle:(UIButton*) button forKey:(NSString*) key
{
    if(!button) {
        return;
    }
    NSMutableDictionary* titles = [NSMutableDictionary new];
    void(^encodeState)() = ^(UIControlState state) {
        NSAttributedString* attributedString = [button attributedTitleForState:state];
        if (attributedString) {
            titles[@(state)] = attributedString;
        } else {
            NSString* str = [button titleForState:state];
            if (str) {
                titles[@(state)] = str;
            }
        }
    };
    
    encodeState(UIControlStateNormal);
    encodeState(UIControlStateHighlighted);
    encodeState(UIControlStateDisabled);
    encodeState(UIControlStateSelected);
    NSDictionary* dict = @{
                           @"titles":titles,
                           @"numberOfLines" : @(button.titleLabel.numberOfLines)
                           };
    [self encodeObject:dict forKey:key];
}

-(void) decodeButtonTitle:(UIButton*) button forKey:(NSString*) key
{
    if(!button) {
        return;
    }
    NSDictionary* dict = [self decodeObjectForKey:key];
    if ([dict isKindOfClass:[NSDictionary class]]) {
        button.titleLabel.numberOfLines = [dict[@"numberOfLines"] integerValue];

        NSDictionary* titles = dict[@"titles"];
        if ([titles isKindOfClass:[NSDictionary class]]) {
            void(^decodeState)() = ^(UIControlState state) {
                id obj = titles[@(state)];
                if ([obj isKindOfClass:[NSAttributedString class]]) {
                    [button setAttributedTitle:obj forState:state];
                } else if([obj isKindOfClass:[NSString class]]) {
                    [button setTitle:obj forState:state];
                }
            };
            decodeState(UIControlStateSelected);
            decodeState(UIControlStateHighlighted);
            decodeState(UIControlStateDisabled);
            decodeState(UIControlStateNormal);
        }
    }
}
#endif
@end

I’m these method as part of the Speech Timer 2.0 remake. UI restoration is one of the challenging aspects that I’ve solved recently while writing the app. Please let me know what you think!

Until next time.



Avoid App Review rules by distributing outside the Mac App Store!


Get my FREE cheat sheets to help you distribute real macOS applications directly to power users.

* indicates required

When you subscribe you’ll also get programming tips, business advices, and career rants from the trenches about twice a month. I respect your e-mail privacy.

Avoid Delays and Rejections when Submitting Your App to The Store!


Follow my FREE cheat sheets to design, develop, or even amend your app to deserve its virtual shelf space in the App Store.

* indicates required

When you subscribe you’ll also get programming tips, business advices, and career rants from the trenches about twice a month. I respect your e-mail privacy.

0 thoughts on “Encoding UIButton Title for State Preservation and Restoration

Leave a Reply