1

I do a Parse query in my iPhone app, but I am getting errors trying to do the same Parse query in my Watch app.

This is the query in my iPhone app:

- (void)viewDidLoad {
    // GMT Date from Phone
    NSDate *gmtNow = [NSDate date];
    NSLog(@"GMT Now: %@", gmtNow);

    // Query Parse
    PFQuery *query = [self queryForTable];
    [query whereKey:@"dateGame" greaterThanOrEqualTo:gmtNow];

    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
        if (!error) {
            NSMutableArray *localMatchup = [@[] mutableCopy];

            for (PFObject *object in objects) {
                // Add objects to local Arrays
                [localMatchup addObject:[object objectForKey:@"matchup"]];

                // App Group
                NSString *container = @"group.com.me.off";
                NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:container];

                // Matchup
                [defaults setObject:localMatchup forKey:@"KeyMatchup"];
                NSArray *savedMatchup = [defaults objectForKey:@"KeyMatchup"];
                NSLog(@"Default Matchup: %@", savedMatchup);
                savedMatchup = matchupArray;
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
            });

        }
    }];
}

This is the query I tried in my WatchKit app...

- (void)awakeWithContext:(id)context {
    // GMT Date from Phone
    NSDate *gmtNow = [NSDate date];
    NSLog(@"GMT Now: %@", gmtNow);

    // Query Parse
    PFQuery *query = [self queryForTable];
    [query whereKey:@"dateGame" greaterThanOrEqualTo:gmtNow];

    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
        if (!error) {
            NSMutableArray *localMatchup = [@[] mutableCopy];

            for (PFObject *object in objects) {
                // Add objects to local Arrays
                [localMatchup addObject:[object objectForKey:@"matchup"]];

                // App Group
                NSString *container = @"group.com.me.off";
                NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:container];

                // Matchup
                [defaults setObject:localMatchup forKey:@"KeyMatchup"];
                NSArray *savedMatchup = [defaults objectForKey:@"KeyMatchup"];
                NSLog(@"Default Matchup: %@", savedMatchup);
                savedMatchup = self.matchupArray;
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
            });
        }
    }];
}

But I get errors on these two lines...

`PFQuery *query = [self queryForTable];`

`[self.tableView reloadData];`

because I can't do the same exact code related to a table I'm guessing, but I'm just not sure what to change it to.

EDIT: Adding code per @cnoon answer

WatchKit InterfaceController.m:

How would I ask for my query to run here? - (void)awakeWithContext:(id)context { [super awakeWithContext:context];

    [WKInterfaceController openParentApplication:nil reply:^(NSDictionary *replyInfo, NSError *error) {
        // What to put here?
        NSLog(@"Open Parent Application");
    }];

-and-

iPhone AppDelegate.h

How would I ask for my PFQuery to run?

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
    // What to put here?
}
SRMR
  • 3,064
  • 6
  • 31
  • 59

1 Answers1

9

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:

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.

cnoon
  • 16,575
  • 7
  • 58
  • 66
  • I was told to use the PFQuery in the WatchKit extension when I asked this question http://stackoverflow.com/questions/28998918/if-watch-app-opens-before-iphone-app-runs-no-data-is-populated/28999045?noredirect=1#comment46246864_28999045 ... so that's why I was trying it. The reason I'm trying to run the Query in the Watch app is because if the Watch app gets opened before the iOS app then there is no data in my Shared App Group because no Query has been run yet. – SRMR Mar 14 '15 at 17:44
  • My main problem I'm trying to solve in all of this is that if the Watch app gets opened the first time the app ever gets used (before the iPhone app ever runs), then it will have no data because the PFQuery in the iPhone app will have never run, and as such will have never saved any data into the Shared App Group. Make sense? – SRMR Mar 14 '15 at 17:45
  • For the WKInterfaceTable part, I already have that set up and working, it was just more of I knew I couldn't use the `[self.tableView reloadData];` if I were to use the same code from my iPhone app to run a Query in my Watch app. Ya know? – SRMR Mar 14 '15 at 17:47
  • @Stephen was correct in his response that you "can" run `PFQuery`s in your Watch Extension. What I'm saying is that it is not recommended because you cannot guarantee that it will finish. Does that make sense? The iOS app can be opened in the background from the Watch Extension and left running until the PFQuery completes whether the Watch Extension is still running or not. – cnoon Mar 14 '15 at 17:53
  • As for populating the data when the app first launches, you want to design some placeholder views until the data from the PFQuery from the iOS App completes. Once you receive the updates from MMWormhole, update the interface controllers that are currently loaded. – cnoon Mar 14 '15 at 17:55
  • Do I need to use MMWormhole, I'm trying to figure out where that fits in if I'm already using the Shared App Group? – SRMR Mar 14 '15 at 18:29
  • MMWormhole makes it easy to write data into the app group and read it back out. It also sends Darwin notifications between the iOS App and Watch Kit Extension when data is written. This can be useful if you update something in the iOS app that affects the Watch interface when it is running. If this is not a case that you will run into, then the `openParentApplication` approach in my answer will be sufficient. – cnoon Mar 14 '15 at 18:37
  • That makes sense in general, but I'm struggling with how that would work specifically. I added an edit to my question to show what I'm doing, but I'm not understanding how I ask it to run the `PFQuery` in the background I guess? It seems easy, I guess I'm just stuck trying to implement that for the first time. – SRMR Mar 14 '15 at 18:55
  • I have edited my answer accordingly to additionally accommodate your edited question. – cnoon Mar 14 '15 at 19:55
  • This is a great answer and thanks for the code, do you mind putting it in Obj-C? I don't know Swift yet. Regardless, appreciate your help very much. And would this be the same if I were not using MMWormhole, like I would just skip that #2 step? – SRMR Mar 14 '15 at 20:17
  • Can you put in ObjC? And am I ok to skip step#2? Thanks! – SRMR Mar 16 '15 at 13:44
  • 1
    @cnoon I am having the same basic issue as the OP, and like him, I am doing this all in Obj-C. Can you provide some samples in that? – user717452 Mar 19 '15 at 04:57
  • Okay @user717452, I added an Objective-C implementation as well. – cnoon Mar 19 '15 at 15:53