NSUserDefaults not working on Xcode beta with Watch OS2 NSUserDefaults not working on Xcode beta with Watch OS2 swift swift

NSUserDefaults not working on Xcode beta with Watch OS2


With watch OS2 you can no longer use shared group containers. Apple Docs:

Watch apps that shared data with their iOS apps using a shared group container must be redesigned to handle data differently. In watchOS 2, each process must manage its own copy of any shared data in the local container directory. For data that is actually shared and updated by both apps, this requires using the Watch Connectivity framework to move that data between them.


NSUserDefaults (even with an App Groups) don't sync between the iPhone and the Watch in watchOS 2. If you want to sync settings from either your iPhone app or the Settings-Watch.bundle, you have to handle the syncing yourself.

I've found that using WatchConnectivity's user info transfers works really well in this case.Below you'll find an example of how you could implement this. The code only handles one-way syncing from the phone to the Watch, but the other way works the same.

In the iPhone app:
1) Prepare dictionary of settings that need to be synced

- (NSDictionary *)exportedSettingsForWatchApp  {      NSUserDefaults *userDefaults = [self userDefaults]; // the user defaults to sync      NSSet *keys = [self userDefaultKeysForWatchApp]; // set of keys that need to be synced      NSMutableDictionary *exportedSettings = [[NSMutableDictionary alloc] initWithCapacity:keys.count];      for (NSString *key in keys) {          id object = [userDefaults objectForKey:key];          if (object != nil) {              [exportedSettings setObject:object forKey:key];          }      }      return [exportedSettings copy];  }  

2) Determine when the settings need to be pushed to the Watch
(not shown here)

3) Push the settings to the Watch

- (void)pushSettingsToWatchApp  {      // Cancel current transfer      [self.outstandingSettingsTransfer cancel];      self.outstandingSettingsTransfer = nil;      // Cancel outstanding transfers that might have been started before the app was launched      for (WCSessionUserInfoTransfer *userInfoTransfer in self.session.outstandingUserInfoTransfers) {          BOOL isSettingsTransfer = ([userInfoTransfer.userInfo objectForKey:@"settings"] != nil);          if (isSettingsTransfer) {              [userInfoTransfer cancel];          }      }      // Mark the Watch as requiring an update      self.watchAppHasSettings = NO;      // Only start a transfer when the watch app is installed      if (self.session.isWatchAppInstalled) {          NSDictionary *exportedSettings = [self exportedSettingsForWatchApp];          if (exportedSettings == nil) {              exportedSettings = @{ };          }          NSDictionary *userInfo = @{ @"settings": exportedSettings };          self.outstandingSettingsTransfer = [self.session transferUserInfo:userInfo];       }  }  

In the Watch extension:
4) Receive the user info transfer

- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo  {      NSDictionary *settings = [userInfo objectForKey:@"settings"];      if (settings != nil) {          // Import the settings          [self importSettingsFromCompanionApp:settings];       }  } 

5) Save the received settings to the user defaults on the Watch

- (void)importSettingsFromCompanionApp:(NSDictionary *)settings  {      NSUserDefaults *userDefaults = [self userDefaults]; // the user defaults to sync      NSSet *keys = [self userDefaultKeysForWatchApp]; // set of keys that need to be synced      for (NSString *key in keys) {          id object = [settings objectForKey:key];          if (object != nil) {              [userDefaults setObject:object forKey:key];          } else {              [userDefaults removeObjectForKey:key];          }      }      [userDefaults synchronize];  }  


Theres a simple way to reproduce the old functionality, I export the old group user defaults into a dictionary, send that across WatchConnectivity framework and then reimport them into user defaults on the other side:

In both Phone and Watch apps:

  1. Add the WatchConnectivty framework
  2. #import <WatchConnectivity/WatchConnectivity.h> and declare as WCSessionDelegate
  3. Add code to start session after the app has launched:

    if ([WCSession isSupported]) {        WCSession* session = [WCSession defaultSession];        session.delegate = self;        [session activateSession];    }
  4. Use this to send the updated defaults to the other device (call after your current [defaults synchronize] ):

[[WCSession defaultSession] updateApplicationContext:[[[NSUserDefaults alloc] initWithSuiteName:@"group.com.company.myapp"] dictionaryRepresentation] error:nil];

  1. Receive and save the settings back to the default - add this to the WCDelegate:

    -(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext {    NSLog(@"New Session Context: %@", applicationContext);    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.company.myapp"];    for (NSString *key in applicationContext.allKeys) {        [defaults setObject:[applicationContext objectForKey:key] forKey:key];    }    [defaults synchronize];}

Be careful to maintain support for non WC devices - wrap your updateApplicationContext calls with if ([WCSession isSupported])