Nate
Nate

Reputation: 31045

iOS-WatchKit File Transfers Work Unreliably

I've built an app for iOS 9 and WatchOS 2. The iOS app will periodically transfer image files from the iPhone to the Watch. Sometimes, these are pushed from the app, sometimes the Watch requests (pulls) them. If pulled, I make the requests asynchronous, and use the exact same iOS code to transfer images in both cases.

About half the time (maybe 2/3), the file transfer works. The other times, it appears that nothing happens. This is the same whether I'm pushing or pulling images.

On the iOS side, I use code similar to this (session activated already):

   if ([WCSession isSupported]) {
      WCSession *session = [WCSession defaultSession];
      if (session.reachable) {
         NSData *imgData = UIImagePNGRepresentation(img);

         NSURL *tempFile = [[session watchDirectoryURL] URLByAppendingPathComponent: @"camera.png"];
         BOOL success = [imgData writeToFile: [tempFile path] atomically: NO];
         if (success) {
            NSLog(@"transferFile:metadata:");
            [session transferFile: tempFile metadata: nil];
         } else {
            NSLog(@"will not call transferFile:metadata:");
         }
      } else {
         NSLog(@"Camera watch client not reachable.");
      }
   }

On the watch extension side, I have a singleton that activates the watch session and receives the file:

- (void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file {
   // pass the data file to the data listener (if any)
   [self.dataListener session: session didReceiveFile: file];
}

My "data listener" converts the file to a UIImage and displays it on the UI thread. However, that's probably irrelevant, as the unsuccessful operations never get that far.

During unsuccessful transfers, session:didReceiveFile: is never called. If I inspect the iOS app's log, however, I see these messages only during the operations that fail:

Dec 26 15:10:47 hostname companionappd[74893]: (Note ) WatchKit: application (com.mycompany.MyApp.watchkitapp), install status: 2, message: application install success

Dec 26 15:10:47 hostname companionappd[74893]: (Note ) WatchKit: Purging com.mycompany.MyApp.watchkitapp from installation queue, 0 apps remaining

What is happening here? It looks like the app is trying to reinstall the Watch app (?). When this is happening, I do not see the watch app crash/close and restart. It simply does nothing. No file received.

On the iOS side, I scale down the image to about 136x170 px, so the PNG files shouldn't be too big.

Any ideas what's going wrong?

Update:

I have posted a complete, minimal project that demonstrates the problem on Github here

Upvotes: 5

Views: 1471

Answers (3)

fabian789
fabian789

Reputation: 8412

For me, not a single transfer was working anymore. Polling transfer.progress showed isTransferring == true, but I never got beyond 0 completed units.

I ended up:

  1. Deleting apps on watch and iPhone
  2. Rebooting both
  3. Reinstalling

And it works.

Upvotes: 1

4oby
4oby

Reputation: 607

This is how I managed to transfer files from phone to watch:
In order for this to work, the file must be locate in appGroupFolder, and "App Groups" must be enabled from Capabilities tab, for phone and watch.

In order to get appGroup folder use following line of code:

NSURL * myFileLocationFolder = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: @"myGroupID"]; //something like group.bundle.projName

Once you got that use this to send message and handle response from watch:

 [session sendMessage:@{@"file":myFileURL.absoluteString} replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
                //got reply
            } errorHandler:^(NSError * _Nonnull error) {
                //got Error
            }];

Even though WCSession *session = [WCSession defaultSession]; I have noticed that sometimes session is deallocated, so you might consider using [WCSession defaultSession]; instead.

To catch this on the phone use:

- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler{
 //message[@"file"] - addres to my file
 //do stuff with it here

 replyHandler(@{@"myResponse":@"responseData"}); //this call triggers replyHandler block on the watch
}

Now a if you didn't forget to implement WCSessionDelegate and use

if ([WCSession isSupported]) {
    _session = [WCSession defaultSession];
    _session.delegate = self;
    [_session activateSession];
}
//here session is @property (strong, nonatomic) WCSession * session;

It all should work.

Made a broader answer, hopefully will reach out to more people.

Upvotes: 0

Nate
Nate

Reputation: 31045

I am now under the impression that this is a bug in the simulators. It seems to work more reliably on the Apple Watch hardware. Not sure if it's 100% reliable, though.

Apple bug report filed (#24023088). Will update status if there is any, and leave unsolved for any potential answers that may provide workarounds.

Upvotes: 1

Related Questions