12

I am trying to launch my iPhone app from watch simulator using the below code :

WKInterfaceController subclass

[WKInterfaceController openParentApplication:[NSDictionary dictionaryWithObject:@"red" forKey:@"color"] reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(@"replyInfo %@",replyInfo);
NSLog(@"Error: %@",error);
}];

AppDelegate.m

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(@"appdelegate handleWatchKitExtensionRequest");
NSLog(@"NSDictionary: %@",userInfo);
NSLog(@"replyInfo: %@",replyInfo);
}

The error I am getting is :

Error: Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7f8603227730 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}

Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62
  • I don't know WatchKit at all, but the error seems to indicate that your code in `handleWatchKitExtensionRequest:` must call the reply() block provided as an argument to that method. Try adding `reply(@{@"data" : @"test data"});` to that method. This is using just a trivial dictionary as a test; I guess the WatchKit docs will tell you exactly what the contents of that dictionary should be. – pbasdf Jan 08 '15 at 10:37
  • Yes i Am getting same error when i print error in [InterfaceController openParentApplication:dict reply:^(NSDictionary *replyInfo, NSError *error) { NSLog(@"%@",[replyInfo objectForKey:@"Key"]); NSLog(@"error:-%@",[error description]);plese suggest how to get data in watchkit app from this method. i want string from my app to watch kit app. thanks in advance – jaydev Mar 14 '15 at 08:27

3 Answers3

18

You need to call the reply block, even if you return nil. The following will resolve your error:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(@"appdelegate handleWatchKitExtensionRequest");
NSLog(@"NSDictionary: %@",userInfo);
NSLog(@"replyInfo: %@",replyInfo);
reply(nil);
}

See the Apple documentation for further information. You can also return an NSDictionary reply(myNSDictionary); with whatever information it would be useful to return to your Watchkit extension, although the dictionary can only contain information that can be serializable to a property list file, so for instance you can pass strings but you can't just pass a dictionary containing references to instances of your custom classes without packaging them up as NSData first.

Duncan Babbage
  • 19,972
  • 4
  • 56
  • 93
  • 8
    Wow, took me forever to realize that you can't pass custom objects in the replyInfo dictionary. Even conforming to NSCoding doesn't do it. And the error message saying you never called the reply block is misleading when the real problem is the replyInfo dictionary. – bdmontz Mar 11 '15 at 17:07
  • @bdmontz Unlike XPC services, it looks like there is no provision made to register custom classes. Bummer ! Perhaps you should post that as another answer. – GoodSp33d Mar 16 '15 at 13:18
  • I dont get why in the openParentApplication reply block expects an error parameter, but in the handleWatchKitExtensionRequest block there is no error parameter to pass back? – Van Du Tran May 04 '15 at 03:33
  • 2
    You can also get this error if the iPhone app crashes when handling the request. – olegam May 13 '15 at 14:57
  • if iPhone is inactive, I am also getting this `Error: Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7f8603227730 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}` error – Min Soe May 21 '15 at 08:46
14

Aside from just not calling the reply block, this can happen for at least a couple reasons:

  1. Your iPhone app crashed while it was processing the request and therefore was never able to call the reply block. Check that you are not accidentally putting nil into an NSMutableDictionary, as that will cause a crash.
  2. You are trying to put something that can't be serialized into a plist file into the replyInfo dictionary (hat tip to @duncan-babbage). If you need to pass an NSAttributedString or your custom object, make sure it conforms to NSCoding and do this:

On the phone side build your reply dictionary:

NSMutableDictionary *reply = [NSMutableDictionary new];
MyCustomObject *myObject = <something you need to send>;
reply[@"myKey"] = [NSKeyedArchiver archivedDataWithRootObject: myObject];
NSAttributedString *myString = <some attributed string>;
reply[@"otherKey"] = [NSKeyedArchiver archivedDataWithRootObject: myString];

And unpack it back on the watch side:

NSData *objectData = replyInfo[@"myKey"];
MyCustomObject *myObject = [NSKeyedUnarchiver unarchiveObjectWithData: objectData];
NSData *stringData = replyInfo[@"otherKey"];
NSAttributedString *myString = [NSKeyedUnarchiver unarchiveObjectWithData: stringData];
bdmontz
  • 581
  • 6
  • 9
  • You forgot to mention that to use archiving you need to conform to protocol ``, that requires to implement its required methods. – E-Riddie Apr 21 '15 at 16:53
  • @EridB, that's incorrect. As stated in my answer, your object must conform to the NSCoding protocol to use archiving. – bdmontz Apr 22 '15 at 11:59
  • @bdmontz thanks for saving my bacon! I'd have spent the day trying to figure this out! I made a nice NSDictionary Category to hide all of this cruft from me, as well. – eric Apr 25 '15 at 00:27
6

I would like to add that it is important to start a background task in handleWatchKitExtensionRequest as specified in the documentation. This ensures that the main app on the iPhone is not suspended before it can send its reply. (Not initiating a background task does not cause a problem in the simulator or when the iPhone app is active. However, it causes a problem when the iPhone app is inactive.)

Code in the app delegate of the main app on iPhone:

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply
{
   __block UIBackgroundTaskIdentifier watchKitHandler;
   watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
                                                               expirationHandler:^{
                                                                 watchKitHandler = UIBackgroundTaskInvalid;
                                                               }];

   if ( [[userInfo objectForKey:@"request"] isEqualToString:@"getData"] )
   {
      // get data
      // ...
      reply( data );
   }

   dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1 ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
       [[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
    } );
}
Pang
  • 9,564
  • 146
  • 81
  • 122
John
  • 8,468
  • 5
  • 36
  • 61