Utiliser WCSession avec plus d'un ViewController

J'ai trouvé beaucoup de questions et beaucoup de réponses mais pas d'exemple final pour la requête:

Quelqu'un peut-il donner un dernier exemple en Objective C quelle est la meilleure pratique pour utiliser WCSession avec une application IOS et une application Watch (WatchOS2) avec plus d'un ViewController .

Ce que j'ai remarqué jusqu'à maintenant sont les faits suivants:

1.) Activez le WCSession dans l'application parent (IOS) à l'AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //Any other code you might have if ([WCSession isSupported]) { self.session = [WCSession defaultSession]; self.session.delegate = self; [self.session activateSession]; } } 

2.) Du côté de WatchOS2, utilisez <WCSessionDelegate> . Mais le rest est totalement flou pour moi! Certaines réponses parlent de spécifier des keys dans le dictionary de passage comme:

 [session updateApplicationContext:@{@"viewController1": @"item1"} error:&error]; [session updateApplicationContext:@{@"viewController2": @"item2"} error:&error]; 

D'autres parlent de récupérer la session par défaut

 WCSession* session = [WCSession defaultSession]; [session updateApplicationContext:applicationDict error:nil]; 

D'autres parlent de files d'attente différentes? "C'est la responsabilité du client d'envoyer dans une autre queue si nécessaire.

Je suis totalement confus. Alors s'il vous plaît donner un exemple comment utiliser WCSession avec une application IOS et une application WatchOS2 avec plus d'un ViewController.

J'en ai besoin pour le cas suivant (simplifié): Dans mon application parent, je mesure la fréquence cardiaque, le time d'entraînement et les calories. À l'application Watch 1. ViewController Je vais montrer la fréquence cardiaque et le time d'entraînement à 2. ViewController Je vais montrer la fréquence cardiaque, aussi et les calories brûlées.

Pour autant que je comprenne la tâche que vous avez juste besoin de synchronisation dans une direction Phone -> Watch , donc en un mot une configuration minimale pour vous:

Téléphone:

Je crois que l' application:didFinishLaunchingWithOptions: handler est le meilleur endroit pour l'initialisation WCSession donc place le code suivant:

 if ([WCSession isSupported]) { // You even don't need to set a delegate because you don't need to receive messages from Watch. // Everything that you need is just activate a session. [[WCSession defaultSession] activateSession]; } 

Puis quelque part dans votre code qui mesure un rythme cardiaque par exemple:

 NSError *updateContextError; BOOL isContextUpdated = [[WCSession defaultSession] updateApplicationContext:@{@"heartRate": @"90"} error:&updateContextError] if (!isContextUpdated) { NSLog(@"Update failed with error: %@", updateContextError); } 

mettre à jour:

Regarder:

ExtensionDelegate.h:

 @import WatchConnectivity; #import <WatchKit/WatchKit.h> @interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate> @end 

ExtensionDelegate.m:

 #import "ExtensionDelegate.h" @implementation ExtensionDelegate - (void)applicationDidFinishLaunching { // Session objects are always available on Apple Watch thus there is no use in calling +WCSession.isSupported method. [WCSession defaultSession].delegate = self; [[WCSession defaultSession] activateSession]; } - (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSSsortingng *,id> *)applicationContext { NSSsortingng *heartRate = [applicationContext objectForKey:@"heartRate"]; // Compose a userInfo to pass it using postNotificationName method. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:heartRate forKey:@"heartRate"]; // Broadcast data outside. [[NSNotificationCenter defaultCenter] postNotificationName: @"heartRateDidUpdate" object:nil userInfo:userInfo]; } @end 

Quelque part dans votre controller, nommons-le XYZController1.

XYZController1:

 #import "XYZController1.h" @implementation XYZController1 - (void)awakeWithContext:(id)context { [super awakeWithContext:context]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUpdatedHeartRate:) name:@"heartRateDidUpdate" object:nil]; } -(void)handleUpdatedHeartRate:(NSNotification *)notification { NSDictionary* userInfo = notification.userInfo; NSSsortingng* heartRate = userInfo[@"heartRate"]; NSLog (@"Successfully received heartRate notification!"); } @end 

Le code n'a pas été testé Je viens de l'écrire tel quel afin qu'il puisse y avoir quelques fautes de frappe.

Je pense que l'idée principale est maintenant claire et qu'un transfert des types de données restants n'est pas une tâche aussi difficile.

Mon architecture actuelle de WatchConnectivity est beaucoup plus compliquée mais elle repose néanless sur cette logique.

Si vous avez encore des questions, nous pouvons en discuter plus en détail.

Eh bien, c'est une version simplifiée de ma solution demandée par Greg Robertson . Désolé, ce n'est plus en Objective-C; Je ne fais que copyr-coller à partir d'un projet approuvé par AppStore pour m'assurer qu'il n'y aura pas d'erreurs.

Essentiellement, n'importe quel WatchDataProviderDelegate peut s'accrocher à la class du fournisseur de données car cela fournit un support de tableau pour les delegates (au lieu d'un var faible). Les WCSessionData entrantes sont transmises à tous les delegates à l'aide de la méthode notifyDelegates ().

 // MARK: - Data Provider Class class WatchDataProvider: WCSessionDelegate { // This class is singleton static let sharedInstance = WatchDataProvider() // Sub-Delegates we'll forward to var delegates = [AnyObject]() init() { if WCSession.isSupported() { WCSession.defaultSession().delegate = self WCSession.defaultSession().activateSession() WatchDataProvider.activated = true; } } // MARK: - WCSessionDelegate public func session(session: WCSession, didReceiveUserInfo userInfo: [Ssortingng : AnyObject]) { processIncomingMessage(userInfo) } public func session(session: WCSession, didReceiveApplicationContext applicationContext: [Ssortingng: AnyObject]) { processIncomingMessage(applicationContext) } func processIncomingMessage(dictionary: [Ssortingng:AnyObject] ) { // do something with incoming data< notifyDelegates() } // MARK: - QLWatchDataProviderDelegate public func addDelegate(delegate: AnyObject) { if !(delegates as NSArray).containsObject(delegate) { delegates.append(delegate) } } public func removeDelegate(delegate: AnyObject) { if (delegates as NSArray).containsObject(delegate) { delegates.removeAtIndex((delegates as NSArray).indexOfObject(delegate)) } } func notifyDelegates() { for delegate in delegates { if delegate.respondsToSelector("watchDataDidUpdate") { let validDelegate = delegate as! WatchDataProviderDelegate validDelegate.watchDataDidUpdate() } } } } // MARK: - Watch Glance (or any view controller) listning for changes class GlanceController: WKInterfaceController, WatchDataProviderDelegate { // A var in Swift is strong by default var dataProvider = WatchDataProvider.sharedInstance() // Obj-C would be: @property (nonatomic, ssortingng) WatchDataProvider *dataProvider override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) dataProvider.addDelegate(self) } // WatchDataProviderDelegate func watchDataDidUpdate() { dispatch_async(dispatch_get_main_queue(), { // update UI on main thread }) }} } class AnyOtherClass: UIViewController, WatchDataProviderDelegate { func viewDidLoad() { WatchDataProvider.sharedInstance().addDelegate(self) } // WatchDataProviderDelegate func watchDataDidUpdate() { dispatch_async(dispatch_get_main_queue(), { // update UI on main thread }) }} } 

Faire de la gestion de session (même si WCSession est singleton) dans un View-Controller ressemble à une violation de MVC (et j'ai déjà vu trop de messages de blog Watch erronés de cette façon).

J'ai fait une class singleton singleton sur WCSession, qui est d'abord fortement référencée par Watch Delegate Delegate pour s'assurer qu'elle se chargera bientôt et ne sera pas libérée au milieu du travail (par exemple quand un View-Controller disparaît pendant que transferUserInfo ou transferCurrentComplicationUserInfo arrive dans un autre fil de montre).

Seule cette class gère / maintient la session WCS et dissocie datatables de session (Model) de tous les View-Controller (s) dans l'application watch, exposant datatables principalement par des variables de class statiques publiques fournissant au less un niveau basique de security.

Ensuite, cette class est utilisée à la fois par le controller de complication, le controller de regard et d'autres controllers de vue. Les mises à jour s'exécutent en arrière-plan (ou dans backgroundFetchHandler), aucune des applications (iOS / WatchOS) ne doit être au premier plan (comme dans le cas de updateApplicationContext) et la session ne doit pas nécessairement être joignable.

Je ne dis pas que c'est la solution idéale, mais finalement, il a commencé à fonctionner une fois que je l'ai fait de cette façon. J'adorerais entendre que c'est complètement faux, mais comme j'ai eu beaucoup de problèmes avant d'aller avec cette approche, je vais m'en tenir à cela maintenant.

Je ne donne pas d'exemple de code intentionnellement, car il est assez long et je ne veux pas que quelqu'un le copy à l'aveuglette.

J'ai trouvé, avec "try and error" , une solution. Ça marche, mais je ne sais pas exactement pourquoi! Si j'envoie une requête de l'application Watch à l'application IOS, le délégué de ce ViewController de l'application Watch récupère toutes datatables de la queue principale de l'application IOS. J'ai ajouté le code suivant dans le - (void)awakeWithContext:(id)context et le - (void)willActivate de tous les ViewControllers de l'application Watch:

Par exemple 0 ViewController:

[self packageAndSendMessage: @ {@ "request": @ "Oui", @ "countur": [NSSsortingng ssortingngWithFormat: @ "% i", 0 ]}];

Par exemple 1 ViewController1:

[self packageAndSendMessage: @ {@ "request": @ "Oui", @ "countur": [NSSsortingng ssortingngWithFormat: @ "% i", 1 ]}];

 /* Helper function - accept Dictionary of values to send them to its phone - using sendMessage - including replay from phone */ -(void)packageAndSendMessage:(NSDictionary*)request { if(WCSession.isSupported){ WCSession* session = WCSession.defaultSession; session.delegate = self; [session activateSession]; if(session.reachable) { [session sendMessage:request replyHandler: ^(NSDictionary<NSSsortingng *,id> * __nonnull replyMessage) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@".....replyHandler called --- %@",replyMessage); NSDictionary* message = replyMessage; NSSsortingng* response = message[@"response"]; [[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess]; if(response) NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", response); else NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", response); }); } errorHandler:^(NSError * __nonnull error) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", error.localizedDescription); }); } ]; } else { NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", @"Session Not reachable"); } } else { NSLog(@"WK InterfaceController - (void)packageAndSendMessage = %@", @"Session Not Supported"); } }