4
votes

I am trying to update a watchOS2 clockkit complication with data transferred via WatchConnectivity from IOS / iPhone.

Despite quite a bit of research, so far unsuccessful. I found though that other posts are describing similar challenge (with no solution yet)

Am facing 3 problems:

1) sendMessage from ComplicationController does not seem to wake the IOS Parent App (whilst the same sendMessage sent from InterfaceController does wake IOS Parent App up)

2) even when value is transferred to ComplicationController (via sendUserInfoToComplication and when IOS app is in foreground), the value displayed in the complication only sometimes gets updated (haven't found pattern why sometimes it does / sometimes not)

3) I set "getNextRequestUpdate.." to 2 minutes (for test purposes). This does not seem to make any difference though. (gets triggered even in simulator at arbitrary times, but "budget" is not utilised / I set stoppers to verify)

Please note that I am relatively new to IOS / Swift programming. But I see, based on other questions / posts, that I do not seem to be the only one struggling with this.

Here the sample code:

ComplicationController:

//
//  ComplicationController.swift
//  IOSDataToComplication WatchKit Extension
//
//  Created by Thomas Peter on 11.10.2015.
//  Copyright © 2015 Thomas Peter. All rights reserved.
//

import ClockKit
import WatchConnectivity


class ComplicationController: NSObject, CLKComplicationDataSource, WCSessionDelegate {

    var session:WCSession!
    var text:String = "watchdefault"
    var textOld:String = ""
    var header:String = "TestHeader"

    override init(){
        super.init()
        startSession()

    }

    // MARK: - Timeline Configuration

    func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
        handler([.None])
    }

    func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(nil)
    }

    func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(nil)
    }

    func getPrivacyBehaviorForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationPrivacyBehavior) -> Void) {
        handler(.ShowOnLockScreen)
    }

    // MARK: - Timeline Population

    func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
        // Call the handler with the current timeline entry

        if complication.family == .ModularLarge {

            //createData()

            let entry = self.createTimeLineEntry(text, date: NSDate())

            handler(entry)


        } else {


            handler(nil)
        }
    }


    func getTimelineEntriesForComplication(complication: CLKComplication, beforeDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
        // Call the handler with the timeline entries prior to the given date
        handler(nil)
    }

    func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
        // Call the handler with the timeline entries after to the given date
        handler(nil)
    }

    // MARK: - Update Scheduling

    func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
        // Call the handler with the date when you would next like to be given the opportunity to update your complication content
        handler(NSDate(timeIntervalSinceNow: (60 * 2)))
    }

    // MARK: - Placeholder Templates

    func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
        // This method will be called once per supported complication, and the results will be cached
        let template = CLKComplicationTemplateModularLargeStandardBody()
        template.headerTextProvider = CLKSimpleTextProvider(text: "header")
        template.body1TextProvider = CLKSimpleTextProvider(text:"defaul text")


        handler(nil)
    }

    func requestedUpdateDidBegin() {
        print("Complication update is starting")


        createData()

        let server=CLKComplicationServer.sharedInstance()

        for comp in (server.activeComplications) {
            server.reloadTimelineForComplication(comp)
            print("Timeline has been reloaded!")

        }

    }



    func requestedUpdateBudgetExhausted() {
        print("Budget exhausted")
    }


    func createTimeLineEntry(bodyText: String, date:NSDate) -> CLKComplicationTimelineEntry {

        let template = CLKComplicationTemplateModularLargeStandardBody()
        template.headerTextProvider = CLKSimpleTextProvider(text: header)
        template.body1TextProvider = CLKSimpleTextProvider(text: text)

        let entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
        return(entry)
    }

    func createData(){


        let applicationData = ["wake":"fromComplication"]


        session.sendMessage(applicationData, replyHandler: {(replyMessage: [String : AnyObject]) -> Void in
            // handle reply from iPhone app here

            //let answer:[String:AnyObject] = replyMessage
            self.text = replyMessage["text"] as! String
            print("complication received messagereply \(self.text)")

            }, errorHandler: {(error ) -> Void in
                // catch any errors here
                print("no reply message")


        })

        print("complication sent \(applicationData) to iOS")

    }

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

        textOld = text
        text = userInfo["text"] as! String

        print("complication received userinfo \(text)")

        dispatch_async(dispatch_get_main_queue()) {

        //if text != textOld {
            self.requestedUpdateDidBegin()
        //}
        }
    }




    private func startSession(){

        if (WCSession.isSupported()) {
            session = WCSession.defaultSession()
            session.delegate = self;
            session.activateSession()
        }
    }

}

ViewController:

//
//  ViewController.swift
//  IOSDataToComplication
//
//  Created by Thomas Peter on 11.10.2015.
//  Copyright © 2015 Thomas Peter. All rights reserved.
//

import UIKit
import WatchConnectivity


class ViewController: UIViewController, WCSessionDelegate {

    var session:WCSession!

    var time:String = "default"

    var text:String = "default"




    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        startSession()
        getData()
        sendUserInfoToComplication()
  }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    func getData(){
        let timeValue = NSDate()
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "hh:mm"
        time = dateFormatter.stringFromDate(timeValue)

        text = "iPhone \(time)"
        print("constructed \(text)")

    }

    private func startSession(){

        if (WCSession.isSupported()) {
            session = WCSession.defaultSession()
            session.delegate = self;
            session.activateSession()
        }

        print("iPhone session started")

    }

    func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {

        let receivedMessage = message["wake"] as! String

        print("iphone received message \(receivedMessage)")


        let applicationDict = ["text":text]

        replyHandler(applicationDict as [String : AnyObject])

        getData()

    }

    func sendUserInfoToComplication(){

        let applicationDict = ["text":text]

        session.transferCurrentComplicationUserInfo(applicationDict)
        print("iphone sent userinfo \(applicationDict) to complication")

    }


}

Also, when running Simulator, I get a whole number of similar messages to the following:

objc[7501]: Class SPAssetCacheAssets is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/System/Library/Frameworks/WatchKit.framework/WatchKit and /Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/System/Library/PrivateFrameworks/SockPuppetGizmo.framework/SockPuppetGizmo. One of the two will be used. Which one is undefined.
objc[7501]: Class SPAssetCacheSyncData is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/System/Library/Frameworks/WatchKit.framework/WatchKit and /Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/System/Library/PrivateFrameworks/SockPuppetGizmo.framework/SockPuppetGizmo. One of the two will be used. Which one is undefined.
2
Did you ever get this sorted out? - bkSwifty
unfortunately not. Still battling with all 3 points listed above - TPeter

2 Answers

0
votes

Use CLKComplicationServer like this:

CLKComplicationServer* server = [CLKComplicationServer sharedInstance];
[server.activeComplications enumerateObjectsUsingBlock:^(CLKComplication * _Nonnull each, NSUInteger idx, BOOL * _Nonnull stop) {
    [server reloadTimelineForComplication: each];
}];

It will invoke your ComplicationController.

0
votes

When debugging complications in the simulators you need to quit the watch simulator between each time you start a debug session from Xcode. If you don't you'll get the issues your experiencing with the 7007 error and getNextRequestedUpdateDateWithHandler will not be called.

This will update your complication:

let server = CLKComplicationServer.sharedInstance()
server.activeComplications.forEach { server.reloadTimelineForComplication($0) }