Parse Query in Watchkit

Parse Query in WatchKit

There's no such thing as a UITableView in a WatchKit application. Instead, you have to work with WKInterfaceTable. Before you continue, I'd also suggest you read through the documentation in the WatchKit Programming Guide. It will give you a MUCH better understanding of all the toolsets available to you as an aspiring Apple Watch developer.

WKInterfaceTable

Once you know the ins and outs of a WKInterfaceTable, you'll quickly see why your approach is flawed for two reasons. First off, you don't have a reloadData method. The alternative in WatchKit is to setNumberOfRows(_:withRowTypes:). Then you need to iterate through each row and configure it.

PFQuery in WatchKit Extension

The second reason you are going to have issues is due to your use of PFQuery.

This is a bit of side advice, so take it or leave it. I'm speaking from experience here having already built a very large page-based Watch App that heavily communicates with the iOS App.

I would advise you to stop making PFQuerys in your WatchKit Extension. The reason is that users using your Watch App are only going to have the app open for a second or two. Everything will happen extremely fast. Because of this, it is extremely difficult to guarantee the success of network calls before the Watch App is terminated by the user. This makes things MUCH more difficult, but is simply the way it is.

Instead, you want to run your PFQuery calls on the iOS App and return that information back to the Watch Extension through the following calls:

  • WKInterfaceController - openParentApplication(_:reply:)
  • UIApplicationDelegate - handleWatchKitExtensionRequest(_:reply:)

You can also cache the PFQuery into the shared app group using MMWormhole or a similar approach alternative. Below is an example of how you can have your Watch Extension request the iOS Application to run a PFQuery, cache the data in MMWormhole and notify the Watch Extension once it is finished. By always reading the data out of the cache, you have a consistent mechanism whether the Watch Extension was still running as well as closed and re-opened.


Objective-C

InterfaceController.m

- (void)willActivate {
[super willActivate];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[WKInterfaceController
openParentApplication:@{@"pfquery_request": @"dumm_val"}
reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(@"User Info: %@", replyInfo);
NSLog(@"Error: %@", error);

if ([replyInfo[@"success"] boolValue]) {
NSLog(@"Read data from Wormhole and update interface!");
}
}];
});
}

AppDelegate.m

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
if (userInfo[@"pfquery_request"]) {
NSLog(@"Starting PFQuery"); // won't print out to console since you're running the watch extension

// 1. Run the PFQuery
// 2. Write the data into MMWormhole (done in PFQuery completion block)
// 3. Send the reply back to the extension as success (done in PFQuery completion block)

reply(@{@"success": @(YES)});
}

reply(@{@"success": @(NO)});
}

Swift

InterfaceController.swift

override func willActivate() {
super.willActivate()

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Float(NSEC_PER_SEC))), dispatch_get_main_queue()) {
WKInterfaceController.openParentApplication(["pfquery_request": "dummy_val"]) { userInfo, error in
println("User Info: \(userInfo)")
println("Error: \(error)")

if let success = (userInfo as? [String: AnyObject])?["success"] as? NSNumber {
if success.boolValue == true {
println("Read data from Wormhole and update interface!")
}
}
}

return
}
}

AppDelegate.swift

func application(
application: UIApplication!,
handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!)
{
if let pfqueryRequest: AnyObject = (userInfo as? [String: AnyObject])?["pfquery_request"] {
println("Starting PFQuery") // won't print out to console since you're running the watch extension

// 1. Run the PFQuery
// 2. Write the data into MMWormhole (done in PFQuery completion block)
// 3. Send the reply back to the extension as success (done in PFQuery completion block)

reply(["success": true])
}

reply(["success": false])
}

Hopefully that helps break down the complexity of having a consistent way to read data from the cache as well as offload network requests (or PFQueries) to the iOS App.

PFQuery findObjectsInBackgroundWithBlock: in WatchKit app works in simulator, not on watch unless iOS app is in foreground

Turned out that I had several problems related to openParentApplication:reply:, and they all seem to have been resolved by using the technique from this post:

http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply

I had started a background task for my own app, but I didn't use the "bogus workaround" background task described in this post, because it wasn't necessary for me in the simulator. On the watch, though, it apparently was.

WatchKit: direct communication with the containing iOS app

I just answered a very similar question here which will allow you to open the iOS app from the Watch Extension and getting a reply back.

In order to debug the iOS app while running the Watch Extension, you should follow the steps explained here.

Pass Data from iOS query to Watch

Make the Parse query in your AppDelegate method and package it in an NSDictionary and call reply(queryDict); The replyInfo dictionary in your InterfaceController will be populated with queryDict.

How do I use pfquery in parse.com filtering on a boolean field in ios?

[query whereKey:@"MyBoolCol" equalTo:[NSNumber numberWithBool:YES]];

Update Table Rows in WatchKit

Create a new method that is called at the end of your PFQuery's callback and in the willActivate method, such as:

- (void)populateTable {
// Hide "No Games" Label
if ([self.matchupArray count] > 0) {

self.noGamesLabel.hidden = YES;
self.rowTable.hidden = NO;

[self.rowTable setNumberOfRows:[self.matchupArray count] withRowType:@"rows"];

for (NSInteger index = 0; index < [self.matchupArray count]; index++) {
// Matchup
NSString *matchupString = [self.matchupArray objectAtIndex:index];
RowController *controller = [self.rowTable rowControllerAtIndex:index];
[controller.matchupLabel setText:matchupString];
}
}
// Show "No Games" Label
else {
self.rowTable.hidden = YES;
self.noGamesLabel.hidden = NO;
}
}

In here you may wish to also show a label to tell the user when the data is being refreshed.

The end of the callback also looks like it should be:

// Matchup
[defaults setObject:localMatchup forKey:@"KeyMatchup"];
NSLog(@"Default Matchup: %@", localMatchup);
self.matchupArray = localMatchup;
[self populateTable];

WatchKit Extension Bridging Header errors (in Swift Project)

watchOS does not have the same frameworks as iOS. You will not be able to use the Parse SDK in WatchKit.

WatchKit App only starts iPhone Simulator and WatchKit App, but not the IOS App

In order to debug the iOS app while running the Watch Extension, you should follow the steps explained here.

You also need to understand that the iOS App does not have to be running while the Watch app is running. You have several combinations of possible runtime scenarios...all of which your iOS app and Watch App must handle seamlessly.

  • iOS App is running but not the Watch App
  • Watch App is running but not the iOS App
  • Both apps are running in the foreground
  • Watch App is running in the foreground and iOS App is running in the background

I have also posted some additional information about launching the parent app from the Watch Extension as well as sharing data here.

Hopefully that helps.



Related Topics



Leave a reply



Submit