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
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.
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.
Perhaps late to the game, but been learning Swift for less than two months at school and sat perhaps more than fifteen hours with this because I couldn't find a decent explanation on the interwebz.
Give all your tabItem's a tag, starting at 1. You do this in each separate view. If you got six views, their tabItems will in that case have a unique number each ranging between 1 and 6.
Add UITabBarControllerDelegate to all the ViewControllers' classes so you can use the function explained later in point 5.
class FirstViewController: UIViewController, UITabBarControllerDelegate {
Add the following variable globally (right after the code above, as an example) so you can save variables locally on the phone from any function within the class.
let defaults = NSUserDefaults.standardUserDefaults()
Delegate the tabBarController to the view so the view can update any changes to the tabBarController. Put the following into your viewDidLoad().
tabBarController!.delegate = self
Implement the following code. This one will activate when the user is editing the tab view. What the code does is taking the [ViewControllers]'s tags in the order they are in (after the user changed it) and saves it locally on the phone. The first viewController's tag is saved as an integer in the variable "0", the second tag in a variable called "1", and so on.
func tabBarController(tabBarController: UITabBarController, didEndCustomizingViewControllers viewControllers: [UIViewController], changed: Bool) {
if (changed) {
print("New tab order:")
for (var i=0; i<viewControllers.count; i++) {
defaults.setInteger(viewControllers[i].tabBarItem.tag, forKey: String(i))
print("\(i): \(viewControllers[i].tabBarItem.title!) (\(viewControllers[i].tabBarItem.tag))")
}
}
}
The prints tell you the new order of the tabs. Nothing you will need, but I think it's nice to see what's happening in the background. All this was only to save the order of the tabs. You will now have to retrieve them when the program is starting.
Switch from your UIViewControl file to AppDelegate.swift.
Finally, write the following in func application(application: UIApplication, didFinishLaunchingWithOptions... that you can find at the top of AppDelegate.swift.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Let you read and write to local variables on your phone
let defaults = NSUserDefaults.standardUserDefaults()
// Getting access to your tabBarController
let tabBar: UITabBarController = self.window?.rootViewController as! UITabBarController
var junkViewControllers = [UIViewController]()
// returns 0 if not set, hence having the tabItem's tags starting at 1.
var tagNumber : Int = defaults.integerForKey("0")
if (tagNumber != 0) {
for (var i=0; i<tabBar.viewControllers?.count; i++) {
// the tags are between 1-6 but the order of the
// viewControllers in the array are between 0-5
// hence the "-1" below.
tagNumber = defaults.integerForKey( String(i) ) - 1
junkViewControllers.append(tabBar.viewControllers![tagNumber])
}
tabBar.viewControllers = junkViewControllers
}
}
What is good to know is that all views that a tabBarController contains is stored as an array in tabBarController.viewControllers.
This code basically creates an array called junkViewControllers. The for-loop then adds the existing UIViewControllers from the program in the order from the previous stored variables, based on the UIViewControllers' tags. When all this is done, the tabBarController shortened to tabBar is overwritten with the array junkViewController.
This is what did the trick for me, building on the answers from Rickard & Jesper. All of the code goes into the main TabBarController.
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
let tabOrderKey = "customTabBarOrder"
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
loadCustomTabOrder()
}
func loadCustomTabOrder() {
let defaults = NSUserDefaults.standardUserDefaults()
let standardOrderChanged = defaults.boolForKey(tabOrderKey)
if standardOrderChanged {
print("Standard Order has changed")
var VCArray = [UIViewController]()
var tagNumber = 0
let tabBar = self as UITabBarController
if let countVC = tabBar.viewControllers?.count {
print("\(countVC) VCs in total")
for var x = 0; x < countVC; x++ {
tagNumber = defaults.integerForKey("tabPosition\(x)")
for VC in tabBar.viewControllers! {
if tagNumber == VC.tabBarItem.tag {
VCArray.append(VC)
print("Position \(x): \(VCArray[x].tabBarItem.title!) VC (tag \(tagNumber))")
}
}
}
}
tabBar.viewControllers = VCArray
}
}
func tabBarController(tabBarController: UITabBarController, didEndCustomizingViewControllers viewControllers: [UIViewController], changed: Bool) {
print("Change func called")
if changed {
print("Order has changed")
let defaults = NSUserDefaults.standardUserDefaults()
for var x = 0; x < viewControllers.count; x++ {
defaults.setInteger(viewControllers[x].tabBarItem.tag, forKey: "tabPosition\(x)")
print("\(viewControllers[x].tabBarItem.title!) VC (with tag: \(viewControllers[x].tabBarItem.tag)) is now in position \(x)")
}
defaults.setBool(true, forKey: tabOrderKey)
} else {
print("Nothing has changed")
}
}
}
Updated Answer for Swift 2.0
`
let tabBarOrderKey = "tabBarOrderKey"
extension TabBarButtonsController: UITabBarControllerDelegate {
// Saves new tab bar custom order
func tabBarController(tabBarController: UITabBarController, didEndCustomizingViewControllers viewControllers: [UIViewController], changed: Bool) {
var orderedTagItems = [Int]()
if changed {
for viewController in viewControllers {
let tag = viewController.tabBarItem.tag
orderedTagItems.append(tag)
}
NSUserDefaults.standardUserDefaults().setObject(orderedTagItems, forKey: tabBarOrderKey)
}
}
// set up tag to compare with when pulling from defaults and for saving initial tab bar change
func setUpTabBarItemTags() {
var tag = 0
if let viewControllers = viewControllers {
for view in viewControllers {
view.tabBarItem.tag = tag
tag += 1
}
}
}
// Get Saved Tab Bar Order from defaults
func getSavedTabBarItemsOrder() {
var newViewControllerOrder = [UIViewController]()
if let initialViewControllers = viewControllers {
if let tabBarOrder = NSUserDefaults.standardUserDefaults().objectForKey(tabBarOrderKey) as? [Int] {
for tag in tabBarOrder {
newViewControllerOrder.append(initialViewControllers[tag])
}
setViewControllers(newViewControllerOrder, animated: false)
}
}
}
}
`
Remember to set the delegate and call these methods in the view did load
I'd like to share the code I have been working on for nearly 3 days now trying all sorts of combinations with many errors and failure - the general life of coding! ha ha. Anyway the code below works with Swift 5. It's the extraction of How to: Save order of tabs when customizing tabs in UITabBarController , Sam's Code, but with the for loop update for Swift 5. I have got this working great, all in the TabBarViewController.swift file.
So if you just paste this into your Swift file, make sure your Tab Bar Items have a tag number from the Attributes Inspector and your good to go!
Thanks again to Sam for the Code in the first place.
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
let tabOrderKey = "customTabBarOrder"
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
loadCustomTabOrder()
}
func loadCustomTabOrder() {
let defaults = UserDefaults.standard
let standardOrderChanged = defaults.bool(forKey: tabOrderKey)
if standardOrderChanged {
print("Standard Order has changed")
var VCArray = [UIViewController]()
var tagNumber = 0
let tabBar = self as UITabBarController
if let countVC = tabBar.viewControllers?.count {
print("\(countVC) VCs in total")
for x in 0..<countVC {
tagNumber = defaults.integer(forKey: "tabPosition\(x)")
for VC in tabBar.viewControllers! {
if tagNumber == VC.tabBarItem.tag {
VCArray.append(VC)
print("Position \(x): \(VCArray[x].tabBarItem.title!) VC (tag \(tagNumber))")
}
}
}
}
tabBar.viewControllers = VCArray
}
}
func tabBarController(_ tabBarController: UITabBarController, didEndCustomizing viewControllers: [UIViewController], changed: Bool) {
print("Change func called")
if changed {
print("Order has changed")
let defaults = UserDefaults.standard
for x in 0..<(viewControllers.count) {
defaults.set(viewControllers[x].tabBarItem.tag, forKey: "tabPosition\(x)")
print("\(viewControllers[x].tabBarItem.title!) VC (with tag: \(viewControllers[x].tabBarItem.tag)) is now in position \(x)")
}
defaults.set(true, forKey: tabOrderKey)
} else {
print("Nothing has changed")
}
}
}