views:

733

answers:

3

I am having problems finding any other information than the docs for how to save the tab order for my UITabBarController, so that the user's customization is saved for next app launch. I have searched online, but have been unable to find any blog posts or articles that goes through the proper code for doing this.

I realize I have to use the delegate methods for the UITabBarController (didEndCustomizingViewControllers:) but I am not sure how I best approach persistance in terms of saving the state of the order the user wants the tabs in.

Can someone post some code, point me in the right direction or perhaps you have a link for something saved? :)

Thanks

+5  A: 

Hi!

As far as you've asked for some sample code I will simply post here how I dealt with the same task in my app.

Quick intro: I was using a NIB file for storing initial UITabBarController state and to differ my tabs one from another I simply defined tag variables for UITabBarItem objects assigned to each UIViewController stuffed in my UITabBarController. To be able to accurately track last selected tab (including the 'More' one) I've implemented following methods for UITabBarControllerDelegate of my UITabBarController and UINavigationControllerDelegate of its moreNavigationController. Here they are:

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    [[NSUserDefaults standardUserDefaults] setInteger:mainTabBarController.selectedIndex forKey:@"mainTabBarControllerSelectedIndex"];
}

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    [[NSUserDefaults standardUserDefaults] setInteger:mainTabBarController.selectedIndex forKey:@"mainTabBarControllerSelectedIndex"];
}

#pragma mark UITabBarControllerDelegate

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    [[NSUserDefaults standardUserDefaults] setInteger:tabBarController.selectedIndex forKey:@"mainTabBarControllerSelectedIndex"];
}

And here's the code for saving the tabs order:

#pragma mark UITabBarControllerDelegate

- (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed {
    int count = mainTabBarController.viewControllers.count;
    NSMutableArray *savedTabsOrderArray = [[NSMutableArray alloc] initWithCapacity:count];
    for (int i = 0; i < count; i ++) {
        [savedTabsOrderArray addObject:[NSNumber numberWithInt:[[[mainTabBarController.viewControllers objectAtIndex:i] tabBarItem] tag]]];
    }
    [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithArray:savedTabsOrderArray] forKey:@"tabBarTabsOrder"];
    [savedTabsOrderArray release];
}

As you can see I've been storing the order of tabs' indexes in an array in NSUserDefaults.

On app's launch in applicationDidFinishLaunching: method I reordered the UIViewControllers using following code:

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    mainTabBarController.delegate = self;

    int count = mainTabBarController.viewControllers.count;
    NSArray *savedTabsOrderArray = [[userDefaults arrayForKey:@"tabBarTabsOrder"] retain];
    if (savedTabsOrderArray.count == count) {
        BOOL needsReordering = NO;

        NSMutableDictionary *tabsOrderDictionary = [[NSMutableDictionary alloc] initWithCapacity:count];
        for (int i = 0; i < count; i ++) {
            NSNumber *tag = [[NSNumber alloc] initWithInt:[[[mainTabBarController.viewControllers objectAtIndex:i] tabBarItem] tag]];
            [tabsOrderDictionary setObject:[NSNumber numberWithInt:i] forKey:[tag stringValue]];

        if (!needsReordering && ![(NSNumber *)[savedTabsOrderArray objectAtIndex:i] isEqualToNumber:tag]) {
                needsReordering = YES;
            }
        }

        if (needsReordering) {
            NSMutableArray *tabsViewControllers = [[NSMutableArray alloc] initWithCapacity:count];
            for (int i = 0; i < count; i ++) {
                [tabsViewControllers addObject:[mainTabBarController.viewControllers objectAtIndex:
                                                [(NSNumber *)[tabsOrderDictionary objectForKey:
                                                              [(NSNumber *)[savedTabsOrderArray objectAtIndex:i] stringValue]] intValue]]];
            }
            [tabsOrderDictionary release];

            mainTabBarController.viewControllers = [NSArray arrayWithArray:tabsViewControllers];
            [tabsViewControllers release];
        }
    }
    [savedTabsOrderArray release];

    if ([userDefaults integerForKey:@"mainTabBarControllerSelectedIndex"]) {
        if ([userDefaults integerForKey:@"mainTabBarControllerSelectedIndex"] == 2147483647) {
            mainTabBarController.selectedViewController = mainTabBarController.moreNavigationController;
        }
        else {
            mainTabBarController.selectedIndex = [userDefaults integerForKey:@"mainTabBarControllerSelectedIndex"];
        }
    }

    mainTabBarController.moreNavigationController.delegate = self;

    [window addSubview:mainTabBarController.view];
}

It's quite tricky and may seem strange, but don't forget that my UITabBarController was fully created in a nib file. If you construct it programmatically you may simply do the same but following the saved order.

P.S.: and don't forget to synchronize NSUserDefaults when your app terminates.

- (void)applicationWillTerminate:(UIApplication *)application {
    [[NSUserDefaults standardUserDefaults] synchronize];
}

I hope this will help. If something is not clear please do comment and ask.

Ivan Karpan
Hi Iva, thanks for posting the above. I was just wondering if you could elaborate on the actual loop you run to create the array you save in your NSUSerDefaults for the tab order? Because I realize it's possible to get the view controller's index, but I assume this just gives me the order it's in now, and not how it should be - and how this fits with creating them next time. Do you alloc the view controllers with a tag or how would I know which one is which?
Canada Dev
I actually posted all the code from the very beginning, the reason why it wasn't displayed properly is because I've totally missed that '<' character interrupts 'code' and 'pre' tags... Changed them to < and now everything looks OK.
Ivan Karpan
Ah, thanks :) But it looks like some of it is still missing from the didEndCustomizingViewControllers: method..
Canada Dev
Yes it is... ;(I don't know why I started writing code and pre tags instead of simply using this gorgeous WYSIWYG function. :-xFixed everything now (hopefully)!
Ivan Karpan
Thanks SO MUCH, Ivan. Your code is amazing. I pretty just had to copy and paste it and it worked! App now restores the tabs in the right order AND launches in the tab last selected. THANK YOU THANK YOU THANK YOU :)
Canada Dev
It worked right ahead because I've actually taken it directly from my app GlobeMaster, which is already in the App Store. ;)You're welcome!
Ivan Karpan
Hehe. Well, just saying, I didn't have to change much to make it work with mine. I haven't used IB, btw.
Canada Dev
I don't use it now too. ;)
Ivan Karpan
Wouldnt it be possible to save the whole object directly ? or maybe thats an overkill?
Yarek T
+4  A: 

First I voted up the previous answer, but then I noticed how ridiculously complex it is. It can and should be simplified.

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    NSArray *initialViewControllers = [NSArray arrayWithArray:self.tabBarController.viewControllers];
    NSArray *tabBarOrder = [[AppDelegate sharedSettingsService] tabBarOrder];
    if (tabBarOrder) {
        NSMutableArray *newViewControllers = [NSMutableArray arrayWithCapacity:initialViewControllers.count];
        for (NSNumber *tabBarNumber in tabBarOrder) {
            NSUInteger tabBarIndex = [tabBarNumber unsignedIntegerValue];
            [newViewControllers addObject:[initialViewControllers objectAtIndex:tabBarIndex]];
        }
        self.tabBarController.viewControllers = newViewControllers;
    }

    NSInteger tabBarSelectedIndex = [[AppDelegate sharedSettingsService] tabBarSelectedIndex];
    if (NSIntegerMax == tabBarSelectedIndex) {
        self.tabBarController.selectedViewController = self.tabBarController.moreNavigationController;
    } else {
        self.tabBarController.selectedIndex = tabBarSelectedIndex;
    }

    /* Add the tab bar controller's current view as a subview of the window. */
    [self.window addSubview:self.tabBarController.view];
}

- (void)applicationWillTerminate:(UIApplication *)application {

    NSInteger tabBarSelectedIndex = self.tabBarController.selectedIndex;
    [[AppDelegate sharedSettingsService] setTabBarSelectedIndex:tabBarSelectedIndex];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed {

        NSUInteger count = tabBarController.viewControllers.count;
        NSMutableArray *tabOrderArray = [[NSMutableArray alloc] initWithCapacity:count];
        for (UIViewController *viewController in viewControllers) {

            NSInteger tag = viewController.tabBarItem.tag;
            [tabOrderArray addObject:[NSNumber numberWithInteger:tag]];
        }

        [[AppDelegate sharedSettingsService] setTabBarOrder:[NSArray arrayWithArray:tabOrderArray]];
        [tabOrderArray release];
    }

All this happens in AppDelegate. You set UITabBarController's delegate to AppDelegate instance in Interface Builder. sharedSettingsService is what persists the data for me. Basically it can be a NSUserDefaults front-end or anything you like (CoreData for example). So everything is simple, Interface Builder helps here, not makes things more complex.

Aleks N.
Never thought that reordering view controllers even when it's not needed and writing code using a little bit different syntax makes 'ridiculous' off-the-shelf solution 'simpler'. I'm glad I've helped you to saved some time by not implementing everything from scratch though.
Ivan Karpan
Don't mislead my language, please. Ridiculous and ridiculously complex are different things.
Aleks N.
Count is not a property on NSArray. That should be [tabBarController.viewControllers count].
Steven Fisher
Up ticked for clarity. Thanks.
Steven Fisher
Upped one ... This solution is definitely simpler ... no offense Ivan :)
Dev
A: 

Can you explain what is "AppDelegate"? Your explanation looks good, but I'm unable to follow it?

Kwame
The Application Delegate...
Canada Dev