Apple Watch: WKInterfaceLabel doesn't update on device, but fine on simulator

1.5k views Asked by At

I have a WKInterfaceLabel on my WatchKit App storyboard. Every 30 seconds, the WatchKit App will send a request to the parent iPhone app to get a string. Upon receiving the string, I set the string as the WKInterfaceLabel's text. Code looks like this:

NSDictionary *getTokenRequest = @{@"request":@"getToken"};
[InterfaceController openParentApplication:getTokenRequest reply:^(NSDictionary *replyInfo, NSError *error) {
    if (error) {
        NSLog(@"%@", error);
    } else {
        token = replyInfo[@"token"];
        NSLog(@"token=%@", token); // I am getting the correct token
        // But the following line doesn't update label text
        [self refreshToken];
    }
}];

- (void)refreshToken {
    if ([token length] != 0) {
        [self.codeLabel setText:token];
    }
}

When app launches or resumes, the label will display the correct string returned from parent app. This is due to calling refreshToken in willActivate. What's not working is when I request data from the parent app every 30 seconds, I am also calling refreshToken in the callback of the request as shown in the code above, even though I am getting the correct string (token here) from the phone app and calling [self.codeLabel setText:token]. It's the same method call: when called within willActivate, setText updates the label fine, but when called in the callback method of a request, the label text UI doesn't update.

This issue happens only on device. When running on simulator, the label text updates every 30 seconds as expected.

When I was developing an iPhone app, I once had a similar issue, and it was resolved by calling the setText method on the main thread:

[self performSelectorOnMainThread:@selector(refreshToken) withObject:nil waitUntilDone:NO]; 

Sadly, the same fix does not work for Apple Watch.

I have also found a similar thread here: can setText for WKInterfaceLabel in init method but not in didselectrow method however it does not provide a good solution.

Any suggestions/thoughts are appreciated. Thanks in advance!

3

There are 3 answers

0
SeaJelly On

In the end, this is what worked for me:

- (void)refreshToken {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.codeLabel setText:token];
    });
}

Thanks to this post http://iosdevelopmentjournal.com/blog/2013/01/16/forcing-things-to-run-on-the-main-thread/.

In short, when UI is not updating when you tell it to, it might not be scheduled to run on the main thread.

As shown in my question, I have already suspected the threading issue and tried to use performSelectorOnMainThread but it didn't work. For those curious, here's the difference between performSelectorOnMainThread and dispatch_async: Whats the difference between performSelectorOnMainThread and dispatch_async on main queue?

0
Nonolok Lo On

The reason of fail is when you were trying to update the UI, the controller is not in active state.

The correct time to update the UI should be in the method of "willActivate".

Another solution is to add some delay before updating the UI (performSelector:@selector(UPDATE_UI) withObject:NULL afterDelay:1]).

0
Redbeard On

The reason of fail is when you were trying to update the UI, the controller is not in active state

This was the Problem in my case. I held a reference on my parent controller and tried to make changes there (in willDeactivate() of the current controller). That works in simulator but not on Device. I needed to make my changes in didDeactivate() so that the parent controller is already active:

override func didDeactivate()
{
    //Update UI of parent Controller here
    self.parentController.updateUI()
}

Maybe it helps someone.