1

I have a three tab application that shares a plist for connection information (Client ID, Server Address, Port Number). In each of the view controllers, an NSUserDefaults object is initialized within the viewDidLoad method:

- (void)viewDidLoad {
    [super viewDidLoad];

    // load default settings into class instance variables
    defaults = [NSUserDefaults standardUserDefaults];
    self.clientID = [defaults objectForKey:@"clientID"];
    self.serverAddress = [defaults objectForKey:@"serverAddress"];
    self.serverPort = [defaults objectForKey:@"serverPort"];
}

One of my views represents a "Settings" page that allows a user to make changes to the plist. However, when the plist is updated, the changes aren't reflected across all of the tab views because a synchronize is needed for each of the objects:

[defaults synchronize];

I've learned that the viewDidLoad method is only called once during the lifetime of an application (at least for a Tab Bar application), so, I can't put the synchronize calls here. I then turned to the AppDelegate class and discovered the tabBarController method. How do I use this method to synchronize the NSUserDefaults objects across all view controllers without the need for a sync button? Is this even the correct way of sharing/adjusting preferences while an application is open?

Here's where I'm at now:

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {

    // I need to synchronize somewhere in here???
    switch (tabBarController.selectedIndex)
    {
    case 0:
        NSLog(@"Tab 0 selected");
        break;
    case 1:
        NSLog(@"Tab 1 selected");
        break;
    case 2:
        NSLog(@"Tab 2 Selected");
        break;
    }
}

Thanks ahead of time.

CountZachula
  • 170
  • 1
  • 7

3 Answers3

1

Rather than force a lot of extra loads by re-syncing on every view display, you can have each of your view controllers register as a notification observer when they get loaded (viewDidLoad), and whereever you change your settings, you post a "settings_changed" notification. (This is an incredibly useful pattern for decoupling objects that need to communicate).

So each of your viewDidLoad methods will have this:

  [[NSNotificationCenter defaultCenter] 
        addObserver:self 
           selector:@selector(refreshDefaults) 
               name:@"Defaults_changed" 
             object:nil];

The viewDidUnload will unregister the vc:

  [[NSNotificationCenter defaultCenter]
        removeObserver: self 
                  name: @"Defaults_changed"
                object: nil];

Handle the event:

 - (void) refreshDefaults {
   // re-load your defaults here
 }

Any place that makes a change will do this:

  [[NSNotificationCenter defaultCenter] 
     postNotificationName:@"Defaults_changed"
                   object: nil];
Rayfleck
  • 12,116
  • 8
  • 48
  • 74
  • Thank you. I have tested this method and it does indeed work. Either way, my application is small enough that the load is negligible. – CountZachula Jul 11 '11 at 04:30
0

Try syncing the defaults in viewWillAppear instead of viewDidLoad.

It may happen that viewDidLoad is called multiple times (in which case, viewDidUnload will have been called between), for example, if there is a memory issue.

By synchronizing the defaults in viewWillAppear, you are ensuring that the relevant UIViewController is updated just before it becomes the selected view controller.

PengOne
  • 48,188
  • 17
  • 130
  • 149
  • It's even worse. viewWillAppear is called every time a view is about to display whereas viewDidLoad is called once - when the view is loaded. – Alex Kremer Jul 08 '11 at 23:16
  • you _must_ put it into viewWillAppear otherwise it wont get synchronized upon each display (tabbar switching views) – Alex Kremer Jul 08 '11 at 23:20
  • Yes, that was my point, I believe. Is your comment that doing it this way is worse than his way? If not, then I've misinterpreted your first comment. – PengOne Jul 08 '11 at 23:22
  • This is not the answer for the question as phrased, but this is the answer MorbidZach is actually looking for. Good catch. – Steven Fisher Jul 08 '11 at 23:23
  • @PengOne I was trying to say that you _must_ do it in the viewWillAppear otherwise you can get all kinds of sync issues. You were absolutely correct to point it out. You just forgot to say that it is a _must_ :) – Alex Kremer Jul 08 '11 at 23:27
  • Thank you! viewWillAppear worked flawlessly! I had to implement the method by hand, as it wasn't in the controller by default. – CountZachula Jul 08 '11 at 23:32
  • Although this works the correct way to implement this would be to follow @Rayfleck's answer. Even if you do not go ahead and implement @Rayfleck's answer it is worth reading properly to understand the concepts that help promote loose coupling whilst allowing objects to communicate. – Paul.s Jul 09 '11 at 01:17
  • @Paul.s: And even though this answer is valid and correct, it's worth down voting because there is a different one you prefer? Interesting. – PengOne Jul 09 '11 at 01:29
  • People often come here to learn therefore promoting the best answer is surely the main aim of the site? For example the next person who comes along with a similar problem may implement this solution only to find that their reload is a lot more expensive than it is in this case, but hey they found an answer here so it must be correct right? Therefore promoting the better implementation makes more sense to me. – Paul.s Jul 09 '11 at 01:44
  • @Paul.s: I agree with you posting a comment to that effect. I agree with you voting up the solution(s) you think is best. I just take issue with you voting down a reasonable and correct answer because you think there is a better one elsewhere. That's my point. But votes are yours to spend as you like. – PengOne Jul 09 '11 at 01:48
0

You only need to call synchronize when you've made changes. Every time you call setObject:obj forKey:@"key" to set the object, call synchronize directly afterwards.

Also, instead of creating a pointer to NSUserDefaults try using the shared instance directly, like [[NSUserDefaults standardUserDefaults] objectForKey:@""]; and [[NSUserDefaults standardUserDefaults] setObject:obj forKey:@""]; to set the objects. Then call synchronize after setting an object.

Greg
  • 9,068
  • 6
  • 49
  • 91
  • The problem is that he loads the defaults only once per ViewController in `viewDidLoad`, so these changes will not be reflected in the ViewControllers after they are changed. – PengOne Jul 08 '11 at 23:19
  • How do you define 'loading them'? If you synchronize the defaults as soon as you make changes, and use the shared instance directly instead of creating a pointer, all changes will be there when you get them. – Greg Jul 08 '11 at 23:26