//
you're reading...

Programming

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 = ;
        if (attributedString) {
            titles[@(state)] = attributedString;
        } else {
            NSString* str = ;
            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]]) {
                    ;
                } else if([obj isKindOfClass:[NSString class]]) {
                    ;
                }
            };
            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.



Do you enjoy this post? Enter your e-mail address below to receive articles like this one in your mailbox.
* indicates required

Discussion

No comments yet.

Leave a Reply

Free Updates!

Learn how to grow your indie business while keeping your day job.

Categories

Archives

Keep updated!

Don't miss out on new articles!