2015-09-14 12 views
8

ich viele Fragen und viele Antworten, aber kein abschließendes Beispiel für die Anforderung gefunden:Mit WCSession mit mehr als einem Viewcontroller

Kann jemand geben ein letztes Beispiel in Objective C, was am besten Praxis zu verwenden WCSession mit einem IOS App und eine Watch App (WatchOS2) mit mehr als einem ViewController.

Was bemerkte ich, so weit sind die folgenden Fakten:

1.) Aktivieren Sie die WCSession in der übergeordneten (IOS) App im 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.) Auf der WatchOS2 Seite Einsatz <WCSessionDelegate>. Aber der Rest ist völlig unklar für mich! Einige Antworten sprechen von Tasten in Gang Wörterbuch wie die Angabe:

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

Andere sprechen über das Abrufen der Standardsitzung

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

Andere über verschiedene Warteschlangen sprechen? "Es liegt in der Verantwortung des Kunden, wenn nötig, in eine andere Warteschlange zu versenden. Senden Sie die Nachricht an die Hauptstelle zurück."

Ich bin total verwirrt. Bitte geben Sie ein Beispiel, wie Sie WCSession mit einer IOS App und einer WatchOS2 App mit mehr als einem ViewController verwenden können.

Ich brauche es für den folgenden Fall (vereinfacht): In meiner Eltern-App messen ich Herzfrequenz, Trainingszeit und Kalorien. Bei der Watch App 1. ViewController zeige ich am 2. ViewController die Herzfrequenz und die Trainingszeit an. Ich zeige auch die Herzfrequenz und die verbrannten Kalorien.

+0

Verwenden Sie es, um was zu tun? – dan

+0

Ich habe die Notwendigkeit am Ende meiner Frage hinzugefügt. Danke, Ron –

+1

Sie sollten nur den Singleton-Wert von WCSession, defaultSession für die Mehrheit der Fälle verwenden. – Tim

Antwort

9

Soweit ich die Aufgabe, die Sie gerade benötigen Synchronisation in einem Phone -> Watch Richtung so in aller Kürze eine minimale Konfiguration für Sie verstehen:

Telefon:

glaube ich, die application:didFinishLaunchingWithOptions: Handler der beste Platz für die Initialisierung daher legen Sie den folgenden Code dort:

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]; 
} 

Dann irgendwo in Ihrem Code, der eine Herzfrequenz zum Beispiel misst:

NSError *updateContextError; 
BOOL isContextUpdated = [[WCSession defaultSession] updateApplicationContext:@{@"heartRate": @"90"} error:&updateContextError] 

if (!isContextUpdated) { 
    NSLog(@"Update failed with error: %@", updateContextError); 
} 

Update:

Watch:

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<NSString *,id> *)applicationContext { 
    NSString *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 

Irgendwo in Ihrem Controller, nennen wir es 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; 
     NSString* heartRate = userInfo[@"heartRate"]; 
     NSLog (@"Successfully received heartRate notification!"); 
} 

@end 

Kodex wurde ich schrieb es nicht gerade getestet wie so kann es einige Fehler sein.

Ich denke, die Hauptidee ist jetzt ziemlich klar und eine Übertragung von verbleibenden Arten von Daten ist nicht so eine schwierige Aufgabe.

Meine aktuelle WatchConnectivity-Architektur ist viel komplizierter, aber trotzdem basiert sie auf dieser Logik.

Wenn Sie noch Fragen haben, können wir eine weitere Diskussion in den Chat verschieben.

+0

Ich werde es versuchen! Scheint sehr einfach zu sein! Ich habe hier einen guten Beitrag für das NSNotificationCenter gefunden, das ich verwenden werde: http://stackoverflow.com/questions/17080605/add-addobserver-nsnotificationcenter-in-a-1st-view-controller-handle-in-2n –

+0

@Ron Wood - Hast du dein Problem gelöst? Wenn nicht, könnte ich meinen Beitrag ein wenig mit dem 'NSNotificationCenter' Teil aktualisieren. –

+0

Nicht wirklich. Bitte aktualisiere deinen Post bezüglich des NSNotificationCenters Ich bekomme den Delegat-Aufruf nicht zurück, wenn ich das WCSessionDelegate im ExtensionDelegate der WatchApp ausführe. ExtensionDelegate.m -> #import -> Ich musste Schnittstelle hinzufügen ExtensionDelegate() Ende –

0

Ich fand, mit "Versuch und Irrtum", eine Lösung. Es funktioniert, aber ich weiß nicht genau warum! Wenn ich eine Anfrage von der Watch an die IOS-App sende, erhält der Delegierte dieses ViewControllers der Watch-App alle Daten aus der Hauptwarteschlange von der IOS-App.Ich habe den folgenden Code in der - (void)awakeWithContext:(id)context und die - (void)willActivate aller Viewcontrollers der Watch App:

von Beispiel 0 Viewcontroller:

[self packageAndSendMessage: @ {@ "request": @ "Ja", @ "Zähler": [NSString stringWithFormat: @ "% i", ]}];

von Beispiel 1 ViewController1:

[self packageAndSendMessage: @ {@ "request": @ "Ja" @ "counter": [NSString string: @ "% i", ] }];

/* 
    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<NSString *,id> * __nonnull replyMessage) { 


       dispatch_async(dispatch_get_main_queue(), ^{ 
        NSLog(@".....replyHandler called --- %@",replyMessage); 

        NSDictionary* message = replyMessage; 

        NSString* 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"); 
    } 

} 
3

Die Session-Verwaltung (WCSession ist Singleton) in einem View-Controller riecht jedoch nach MVC-Verletzung (und ich habe schon zu viele Watch-Blog-Posts auf diese Weise falsch gesehen).

Ich habe eine Regenschirm-Singleton-Klasse über die WCSession erstellt, die zuerst von Watch Extension Delegate stark referenziert wird, um sicherzustellen, dass sie bald geladen wird und nicht während der Arbeit freigegeben wird (z. B. wenn ein View-Controller während der Übertragung von UserInfo verschwindet) oder transferCurrentComplicationUserInfo passiert in einem anderen Überwachungsthread).

Nur diese Klasse dann Griffe/die WCSession hält und entkoppelt die Sitzungsdaten (Modell) aus allen View-Controller (n) in der Uhr App, Freilegung Daten meist obwohl öffentliche statische Klassenvariablen mindestens Grundstufe der Gewinde- Bereitstellung Sicherheit.

Dann wird diese Klasse sowohl von Komplikations-Controller, Glance Controller und anderen View-Controllern verwendet. Updates laufen im Hintergrund (oder in backgroundFetchHandler), keine der Apps (iOS/WatchOS) muss sich überhaupt im Vordergrund befinden (wie im Falle von updateApplicationContext) und die Sitzung muss nicht unbedingt aktuell erreichbar sein.

Ich sage nicht, dass dies die ideale Lösung ist, aber endlich hat es angefangen zu arbeiten, sobald ich es so gemacht habe. Ich würde gerne hören, dass das völlig falsch ist, aber da ich viele Probleme hatte, bevor ich mit diesem Ansatz fortfuhr, bleibe ich jetzt dabei.

Ich gebe Codebeispiel nicht absichtlich, wie es ziemlich lang ist und ich will nicht, dass jemand blind es kopiert.

+0

Ich habe die komplette Apple WWDC2015 Sitzung für WatchConnectivity gesehen. Und ich weiß, dass sie empfehlen, die Kommunikation in einem sehr frühen Stadium zu setzen! (AppDelegate). Ich kann Ihrer Empfehlung folgen.Die Frage ist jedoch, wie Sie den "Delegaten", der in AppDelegate auftritt, an alle ViewController weiterleiten. Eine sehr einfache Methode könnte darin bestehen, einen Timer in allen ViewControllern zu installieren und die öffentlichen statischen Klassenvariablen regelmäßig zu lesen. Aber ich denke, es gibt einen besseren Weg, um eine Funktion nur dann auszulösen, wenn die "- (void) session:" aufgerufen wird, um die öffentlichen statischen Klassenvariablen in den VCs zu durchlaufen. –

+0

Vielleicht diese http://stackoverflow.com/questions/28809226/notify-watchkit-app-of-an-update-without-the-watch-app-request-it (wenn CFNotificationCenter noch in watchOS 2 funktioniert). Komplikationen verwenden stattdessen "Pull" -Ansatz, mit Ausnahme der Kraftauffrischung (die fraglich ist, da sie das Tageslimit leicht überschreitet). – igraczech

+0

igraczech, können Sie Code für Ihre starke Referenz schreiben? Ich habe einen ähnlichen Prozess mit einer in der Watch ExtensionDelegate aufgerufenen WatchSessionManager-Klasse von unknellla Singleton durchgeführt, aber die Nachrichten zum Empfangen von Delegiertensitzungen werden in der unblella-Klasse nicht ausgelöst. Ich bin mir nicht sicher, ob ich den Delegierten richtig am Leben halte oder ob es zu früh handelt. –

4

Nun, dies ist eine vereinfachte Version meiner Lösung wie von Greg Robertson angefordert. Sorry, es ist nicht mehr in Objective-C; Ich kopiere nur aus einem bestehenden AppStore-genehmigten Projekt, um sicherzustellen, dass es keine Fehler gibt.

Im Prinzip kann jede WatchDataProviderDelegate an die Datenprovider-Klasse angehängt werden, da diese den Array-Halter für Delegaten bereitstellt (anstelle einer schwachen var). Eingehende WCSessionData werden mithilfe der notifyDelegates() -Methode an alle Delegierten weitergeleitet.

// 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: [String : AnyObject]) { 
     processIncomingMessage(userInfo) 
    } 

    public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String: AnyObject]) { 
     processIncomingMessage(applicationContext) 
    } 

    func processIncomingMessage(dictionary: [String: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) listening for changes 

class GlanceController: WKInterfaceController, WatchDataProviderDelegate { 

    // A var in Swift is strong by default 
    var dataProvider = WatchDataProvider.sharedInstance() 
    // Obj-C would be: @property (nonatomic, string) 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 
     }) 
    }} 
} 
+0

Vielen Dank, ich schätze das Codebeispiel. –

+1

Sollte 'addDelegate()' nur 'delegate' anhängen, wenn es * nicht * bereits im' delegates' Array existiert? – conorgriffin

+0

Sicher, danke. Fest. Es war eine dumme unvollendete Kopie-Paste. Wahrscheinlich jemand störend ... – igraczech

Verwandte Themen