Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating WatchKit App With Beacon Ranging

Tags:

ios

swift

I am working to create a Swift-based iOS app that incorporates iBeacons to provide data to a WatchKit (OS 1) app. The app loads data from a JSON into a tableView based upon whether the beacon CLProximity is set to .Near or .Immediate. The iOS app, when launched, is able to detect beacons without issue, populate a variable, and make it available to the WatchKit app. However, when the WatchKit app attempts to request the beacon data back from the app using application:handleWatchKitExtensionRequest when the iOS has not been launched first, the object for the beacons come back as nil. My assumption is that when the app is closed on the phone, the WatchKit call to open the parent application does NOT trigger beacon ranging code that is stored in the AppDelegate. Moving the beacon initialization code into the application:handleWatchKitExtensionRequest does not fix the issue.

How would one go about constructing the app (in general terms, I know enough code that I don't necessarily need a line by line breakdown, though you're free to do so if you think it would help others) where:

  1. The app user launches the watch app to check for data nearby.
  2. The watch app requests a list of near/immediate beacons from the iOS app.
  3. The iOS app returns a list of either beacon Major strings for beacons that fit this criteria.
  4. Optimally, the iOS app lets the Watch App know when that list changes and the WatchKit app updates.

I have a feeling that part of my solution lies in DarwinNotification and with registering observers, which are not things i've worked with a lot before, but there are enough examples on StackOverflow that I can implement them if that is the case. I guess the real issue is how to gather that beacon data when the iOS application hasn't been started yet.

like image 390
xianritchie Avatar asked Jul 07 '15 21:07

xianritchie


2 Answers

Moving the code to application:handleWatchKitExtensionRequest only partially solves the problem ; The main issue lies in that your beacon is not ready immediately (CLLocationManager is very much asynchronous component) and so it takes some time to actually get the information you need.

I personally was working on something similar just as fun project of mine - it was actually video player for Watch OS1 (which does not have video player functionality) and I managed to do it. So the model of WK1 is really simple - you request the app, you get option to reply in

  func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!)

Now the fun part about it and what I actually ended up using is that you don't have to call reply() in that method, you can wait with reply after some event finishes. In my case, that was to asynchronously get all the frames of the video into some X sec buffer and then send back "Yup, I am ready to load them now" once the video processing ended.

So what you could do in my opinion is following:

  • In the watch app, request beacon data
  • In your app, check if you have beacon data
  • If you have them, just present them in WKInterfaceTable or however you do that
  • If you don't, request beacon, store reply closure
  • once you receive data from beacon, call the closure the same way you would in (3)

There might be one problem an that is that your application will get terminated before you get your desired response. In that case, calling following should take care of that

UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})

Proper way how to use background tasks is explained to detail here, so I am just providing link for you.

Also, don't forget that you can save data to NSUserDefaults, or if you use some database, there. This way, you could present results to user right away and then refresh them with new results once fetch is done, but that is more like optimization than full solution.

If you have any additional questions or there is a problem with that solution, just ask and we will figure it out! :) Good luck!

Edit:

After I posted this answer, I actually stumbled upon this answer, which provides you with almost everything you need. But I think it might be little overcomplicated. Anyway, I am just posting it here for your reference so you can have little more material to work with.

like image 66
Jiri Trecak Avatar answered Nov 14 '22 16:11

Jiri Trecak


If it helps, the Apple WatchKit Tips page suggests:

"The iPhone app should do most of the work. If your app on Apple Watch needs to perform longer running background tasks, such as networking calls, you should rely on your iPhone app to do the work. Use the openParentApplication:reply: method in WKInterfaceController to wake up your iPhone app in the background and return the data that your WatchKit extension needs. The UIApplicationDelegate method that handles the WatchKit request must return immediately. If an asynchronous call is required, to perform networking for example, use a background task to make sure your app is not suspended before it has a chance to send its reply."

Check it out and the linked docs at - https://developer.apple.com/watchkit/tips/

like image 27
Roboteich Avatar answered Nov 14 '22 18:11

Roboteich