Call ExtensionDelegate to create/refresh data for Complication
Asked Answered
M

2

5

All my data creation is done in the ExtensionDelegate.swift.

The problem is ExtensionDelegate.swift doesn't get called before the function getCurrentTimelineEntryForComplication in my ComplicationController.swift.

Any ideas? Here is my code and details:

So my array extEvnts is empty in my ComplicationController.swift:

    func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {

        let extEvnts = ExtensionDelegate.evnts
}

Because my ExtensionDelegate.swift hasn't gotten called yet, which is what creates the data for the array:

class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {

    private let session = WCSession.defaultSession()
    var receivedData = Array<Dictionary<String, String>>()
    static var evnts = [Evnt]()

    func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {

        if let tColorValue = userInfo["TeamColor"] as? String, let matchValue = userInfo["Matchup"] as? String {

            receivedData.append(["TeamColor" : tColorValue , "Matchup" : matchValue])
            ExtensionDelegate.evnts.append(Evnt(dataDictionary: ["TeamColor" : tColorValue , "Matchup" : matchValue]))

        } else {
            print("tColorValue and matchValue are not same as dictionary value")
        }

    }

    func applicationDidFinishLaunching() {
        // Perform any final initialization of your application.
        if WCSession.isSupported() {
            session.delegate = self
            session.activateSession()
        }
    }
}

EDIT:

Per Apple, it looks like this has something to do with it, but for some reason I have no idea how to actually implement it because I'm not able to call mydelegate.evnts:

// Get the complication data from the extension delegate.
let myDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
var data : Dictionary = myDelegate.myComplicationData[ComplicationCurrentEntry]!

So I've tried something like this, and still can't get it working because I'm still getting no data:

func someMethod() {
    let myDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
    let dict = ExtensionDelegate.evnts
    print("ExtensionDel.evnts: \(dict.count)")

}

Mcatee answered 21/2, 2016 at 22:5 Comment(1)
You realize you've asked a variant of this same question six times now.Contrastive
A
3

Useful question that helped me here

In the function requestedUpdateDidBegin() you can update the information that you will display in your complication. So in this method you may make a call to your parent app using a WatchConnectivity method like sendMessage:replyHandler:errorHandler: to receive new information.

You can use NSUserDefaults to store your imperative data that will be used in your ComplicationController, then load this information from NSUserDefaults for your complication. I store this data in user defaults so that I always have old data to display in case the new data fails to load.

Absher answered 22/2, 2016 at 21:11 Comment(4)
In this scenario, the OP needs the update to happen after the data is received. In that case, they merely have to explicitly extend (or reload) the timeline to get ClockKit to request the new data from the data source. It is true that a requested update can be used to extend (or reload) the timeline, but the OP is trying to update both the app and the complication. Trying to fetch data within the data source would unnecessarily complicate the design and duplicate connectivity code. I'm not saying it wouldn't work, but it would be a lot of extra work and harder to test or maintain.Contrastive
I use this method requestedUpdateDidBegin to retrieve new data from the parent app in order to have the most recent data when the complication needs an update. Like you said it may not be the most efficient, but it's how I handle having the most up to date data. I haven't seen any other straight forward way that say a weather app may update its complication since it will want the most recent data.Absher
Are you using a scheduled update to initiate this, and then explicitly transferring data from the phone? That's a bit different from data being received, then doing a manual update. But both approaches (in addition to push notifications) do work! (Out of curiosity, do you also support NSURLSession fetches from the watch, when it is out of range of the phone?)Contrastive
@PetahChristian Any time the watch app is launched even for the Glance I retrieve the same data and store it. Otherwise, I fetch the data per every complication update. And no I only get the data from the phone and have the phone fetch the data from the cloud/server.Absher
C
3

TL/DR: Have the extension tell ClockKit to update the complication after the data is received.

First issue:

So my array extEvnts is empty in my ComplicationController.swift ... Because my ExtensionDelegate.swift hasn't gotten called yet, which is what creates the data for the array

Your array is empty because the data hasn't been received at that point.

You can't (get the complication controller to) force the watch (extension) to receive data which may not have even been transmitted yet.

If you look at the WCSession Class Reference, transferUserInfo queues data to be transferred in the background, when the system decides it's a good time to send the info.

Remember that background transfers are not be delivered immediately. The system sends data as quickly as possible but transfers are not instantaneous, and the system may delay transfers slightly to improve power usage. Also, sending a large data file requires a commensurate amount of time to transmit the data to the other device and process it on the receiving side.

Second issue:

You're trying to combine updating your app and your complication based on data sent from your phone. But your app and your complication don't necessarily run together. It's not surprising or unexpected that the watch updates the complication before any data has even been sent/received. The App Programming Guide for watchOS mentions that

Complications exist entirely in the WatchKit extension. Their user interface is not defined in the Watch app. Instead, it is defined by an object implementing the CLKComplicationDataSource protocol. When watchOS needs to update your complication, it launches your WatchKit extension. However, the Watch app’s executable is not launched.

There's no mechanism for the complication controller to say, "Wait, I'm not ready to provide an update. The complication controller can't wait on (or as mentioned, force) the watch extension to receive data.

It's only responsibility is to immediately return data based on what's currently available to it. If there's no data, it must return an empty timeline.

Approaching this problem:

You shouldn't necessarily think of app updates and complication updates as the same thing. The first is not budgeted, but the second is budgeted. If you update your complication too often, you may exceed your daily budget, and no further updates will occur for the remainder of the day.

Complications aren't meant to be frequently updated. Complications should provide as much data as possible during each update cycle. You shouldn't ask the system to update your complication within minutes. You should provide data to last for many hours or for an entire day.

Having covered that, you could wait until your extension has received data, then can ask ClockKit to extend your timeline, so new entries can be added to it. extendTimelineForComplication: is documented in the CLKComplicationServer Class reference.

As an aside, if your data is urgent, you should use transferCurrentComplicationUserInfo. It's a high-priority message, which is placed at the head of the queue, and the extension is woken up to receive it. See this answer for a comparison between it and transferUserInfo.

You also could setup a singleton to hold your data which the watch app and complication controller both use. This was mentioned in an answer to an old question of yours, and also recommended by an Apple employee on the developer forums.

Contrastive answered 22/2, 2016 at 3:53 Comment(6)
First Issue: “You can't (get the complication controller to) force the watch (extension) to receive data which may not have even been transmitted yet.” So every time I ran the Watch App part, the data came over immediately; I assumed that for the purposes of the Simulator at least, that it would receive the data initially immediately for the Complication. Sounds like that is not the case?Mcatee
Second Issue: Every time I ran the Watch App, the data came in immediately thru the ExtensionDelegate. I understand that there might be a background delay, but why would the ExtensionDelegate function WCSession: only delay if the data was getting called from the Complication but not the Watch App? I would think that the WCSession: method would not care if it was the Complication asking it for data rather than the Watch App, but it seems like it will run immediately if it is for the Watch App but not run immediately if it is for the Complication.Mcatee
Approaching this problem: How would someone go about debugging a Complication using transferUserInfo in the Simulator if the data never comes over initially and you have to wait for it? I’m literally just hitting run and asking for it to pull the initial data so I can see if it is getting the right data, not asking it for multiple updates and refreshes yet. I understand updates are budgeted, but shouldn’t they give you an initial data set (which then maybe doesn’t get refreshed for an hour, ect)?Mcatee
“As an aside, if your data is urgent, you should use transferCurrentComplicationUserInfo” The WCSession: I’d think would run at its own pace without regard to whether it was the Complication or the Watch App that would eventually ask for the data. Sounds like that is not how it works though. Should this be what I call in getCurrentTimelineEntryForComplication since it sounds like it is the only way to run a Complication using transferUserInfo in the Simulator and actually get the data when you run it to start up?Mcatee
“You also could setup a singleton to hold your data which the watch app and complication controller both use” One of the other answers I got and saw in docs was to use the ExtensionDelegate. Is using ExtensionDelegate different than using a Singleton then for getting data to the Complication when it starts up for the first time?Mcatee
The watch app is launched by you. The complication is launched (in the background) by the system. You're assuming the app will get its data before the complication is launched. As you see, that's not the case. You shouldn't make that presumption. I answered your original question, and gave you a solution for your problem. You should try it out. I won't be providing answers for every other question you asked in comments.Contrastive
A
3

Useful question that helped me here

In the function requestedUpdateDidBegin() you can update the information that you will display in your complication. So in this method you may make a call to your parent app using a WatchConnectivity method like sendMessage:replyHandler:errorHandler: to receive new information.

You can use NSUserDefaults to store your imperative data that will be used in your ComplicationController, then load this information from NSUserDefaults for your complication. I store this data in user defaults so that I always have old data to display in case the new data fails to load.

Absher answered 22/2, 2016 at 21:11 Comment(4)
In this scenario, the OP needs the update to happen after the data is received. In that case, they merely have to explicitly extend (or reload) the timeline to get ClockKit to request the new data from the data source. It is true that a requested update can be used to extend (or reload) the timeline, but the OP is trying to update both the app and the complication. Trying to fetch data within the data source would unnecessarily complicate the design and duplicate connectivity code. I'm not saying it wouldn't work, but it would be a lot of extra work and harder to test or maintain.Contrastive
I use this method requestedUpdateDidBegin to retrieve new data from the parent app in order to have the most recent data when the complication needs an update. Like you said it may not be the most efficient, but it's how I handle having the most up to date data. I haven't seen any other straight forward way that say a weather app may update its complication since it will want the most recent data.Absher
Are you using a scheduled update to initiate this, and then explicitly transferring data from the phone? That's a bit different from data being received, then doing a manual update. But both approaches (in addition to push notifications) do work! (Out of curiosity, do you also support NSURLSession fetches from the watch, when it is out of range of the phone?)Contrastive
@PetahChristian Any time the watch app is launched even for the Glance I retrieve the same data and store it. Otherwise, I fetch the data per every complication update. And no I only get the data from the phone and have the phone fetch the data from the cloud/server.Absher

© 2022 - 2024 — McMap. All rights reserved.