0

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!

Community
  • 1
  • 1
SeaJelly
  • 1,738
  • 1
  • 15
  • 30
  • Are you sure you're actually getting the text? Try placing an `NSLog()` statement in the `else{}` block and see if it prints out anything. If it doesn't, then the parent app is not returning correctly. I have seen this issue before if you're performing long running tasks on the iPhone, it might work on the simulator but not on an actual device. – Aleksander Jun 09 '15 at 00:24
  • Hi @Aleksander, I have put NSLogs right before setting the text (inside the else statements), and the strings are printed out correctly every 30 seconds. – SeaJelly Jun 09 '15 at 00:26
  • If the token is a correctly formatted `NSString`, this code is valid. Are you sure the `codeLabel` exists (or is the `textfield` you're targeting?). Try putting a breakpoint on that line and check for both that the textfield exists and that the string is correct. – Aleksander Jun 09 '15 at 00:46
  • 1
    The interface is not laid out yet in `awakeWithContext`. `awakeWithContext` should just be used to set up properties and so on on the `InterfaceController`. For any layout changes you should use `willActivate`. – Aleksander Jun 09 '15 at 11:33
  • This issue is not fixed yet. The behavior of WKInterfaceLabel remains unstable. The last few tries, it's not updating again. Any thoughts are appreciated – SeaJelly Jun 09 '15 at 18:18

3 Answers3

1

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?

Community
  • 1
  • 1
SeaJelly
  • 1,738
  • 1
  • 15
  • 30
0

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

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.