I just installed the latest beta of Xcode to try Swift 2 and the improvements made to the Apple Watch development section.
I\'m actually having an h
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];
}
As mentioned already, shared NSUserDefaults no longer work on WatchOS2.
Here's the swift version of @RichAble's answer with a few more notes.
In your iPhone App, follow these steps:
Pick the view controller that you want to push data to the Apple Watch from and add the framework at the top.
import WatchConnectivity
Now, establish a WatchConnectivity session with the watch and send some data.
if WCSession.isSupported() { //makes sure it's not an iPad or iPod
let watchSession = WCSession.defaultSession()
watchSession.delegate = self
watchSession.activateSession()
if watchSession.paired && watchSession.watchAppInstalled {
do {
try watchSession.updateApplicationContext(["foo": "bar"])
} catch let error as NSError {
print(error.description)
}
}
}
Please note, this will NOT work if you skip setting the delegate, so even if you never use it you must set it and add this extension:
extension MyViewController: WCSessionDelegate {
}
Now, in your watch app (this exact code works for Glances and other watch kit app types as well) you add the framework:
import WatchConnectivity
Then you set up the connectivity session:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let watchSession = WCSession.defaultSession()
watchSession.delegate = self
watchSession.activateSession()
}
and you simply listen and handle the messages from the iOS app:
extension InterfaceController: WCSessionDelegate {
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
print("\(applicationContext)")
dispatch_async(dispatch_get_main_queue(), {
//update UI here
})
}
}
That's all there is to it.
Items of note:
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.
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:
#import <WatchConnectivity/WatchConnectivity.h>
and declare as WCSessionDelegate
Add code to start session after the app has launched:
if ([WCSession isSupported]) {
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
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];
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])
It took me hours and hours to get this. Watch this very useful video! It gives you the basic idea of how to use WatchConnectivity to share NSUserDefault between the iPhone app and wacth!
https://www.youtube.com/watch?v=VblFPEomUtQ