0

I'm trying to pass data from iPhone -> Watch via Watch Connectivity using background transfer via Application Context method.

iPhone TableViewController

private func configureWCSession() {
    session?.delegate = self;
    session?.activateSession()
    print("Configured WC Session")
}

func getParsePassData () {
    let gmtTime = NSDate()

    // Query Parse
    let query = PFQuery(className: "data")
    query.whereKey("dateGame", greaterThanOrEqualTo: gmtTime)

    query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
        if error == nil
        {
            if let objectsFromParse = objects as? [PFObject]{
                for MatchupObject in objectsFromParse
                {
                    let matchupDict = ["matchupSaved" : MatchupObject]

                    do {
                        try self.session?.updateApplicationContext(matchupDict)
                        print("getParsePassData iPhone")
                    } catch {
                        print("error")
                    }
                }
            }
        }
    }

}

I'm getting error twice printed in the log (I have two matchups in Parse so maybe it knows there's two objects and thats why its throwing two errors too?):

Configured WC Session
error
error

So I haven't even gotten to the point where I can print it in the Watch app to see if the matchups passed correctly.

Watch InterfaceController:

func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
    let matchupWatch = applicationContext["matchupSaved"] as? String

    print("Matchups: %@", matchupWatch)
}

Any ideas? Will post any extra code that you need. Thanks!

EDIT 1:

Per EridB answer, I tried adding encoding into getParsePassData

func getParsePassData () {
    let gmtTime = NSDate()

    // Query Parse
    let query = PFQuery(className: "data")
    query.whereKey("dateGame", greaterThanOrEqualTo: gmtTime)

    query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
        if error == nil
        {
            if let objectsFromParse = objects as? [PFObject]{
                for MatchupObject in objectsFromParse
                {
                    let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)

                    let matchupDict = ["matchupSaved" : data]

                    do {
                        try self.session?.updateApplicationContext(matchupDict)
                        print("getParsePassData iPhone")
                    } catch {
                        print("error")
                    }
                }
            }
        }
    }

}

But get this in the log:

-[PFObject encodeWithCoder:]: unrecognized selector sent to instance 0x7fbe80d43f30

*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.

EDIT 2:

Per EridB answer, I also tried just pasting the function into my code:

func sendObjectToWatch(object: NSObject) {
    //Archiving
    let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)

    //Putting it in the dictionary
    let matchupDict = ["matchupSaved" : data]

    //Send the matchupDict via WCSession
    self.session?.updateApplicationContext(matchupDict)
}

But get this error on the first line of the function:

"Use of unresolved identifer MatchupObject"

I'm sure I must not be understanding how to use EridB's answer correctly.

EDIT 3:

NSCoder methods:

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)!
    //super.init(coder: aDecoder)

    configureWCSession()

    // Configure the PFQueryTableView
    self.parseClassName = "data"
    self.textKey = "matchup"
    self.pullToRefreshEnabled = true
    self.paginationEnabled = false
}
SRMR
  • 3,064
  • 6
  • 31
  • 59
  • @Lamar the data isn't actually passing, I've got something small that must not be right I'm assuming? – SRMR Sep 08 '15 at 13:35
  • Do any of the other WCSession APIs work (sendMessage, transferFile)? Does the applicationContext work if you send just strings? let applicationDict = ["matchUp" : "test"] – ccjensen Sep 08 '15 at 14:43
  • @ccjensen Yeah Application Context will work for me, but I just don't have something set up perfectly right for it to work. And I'm not sure which part of my code is wrong. – SRMR Sep 08 '15 at 15:04
  • What object type is the matchUp variable? I'm guessing it is not one of the [property list types](https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/PropertyList.html) – ccjensen Sep 08 '15 at 17:54
  • @ccjensen `matchUp` in the Parse database is a `NSString`. From what I know about `applicationContext` is that you need to bundle up those `matchUp`s in a `NSDictionary` to pass them. Does that clear up what your asking? I hope it was helpful. – SRMR Sep 08 '15 at 18:31
  • 1
    yea, if it is a string then that should work fine (although you should probably change this method in the TableViewController `func sendMatchup(matchUp: AnyObject)` to be `func sendMatchup(matchUp: String)`. Unfortunately that gets us no closer to figuring out why things aren't working for you. Any chance you could make a full sample project available? – ccjensen Sep 08 '15 at 18:35
  • @ccjensen changed to `(matchUp: String)`. I guess what I'm missing is the actual data that is getting passed in each function... I create the function, stating it will be a string, create the dictionary key, but then I don't see where the data is going into the dictionary that is being passed in the function – SRMR Sep 10 '15 at 00:46
  • @Lamar put a bounty on it if you can check it out again – SRMR Sep 10 '15 at 00:52
  • @ccjensen updated my code, feel like I'm getting closer, if you can help at all that'd be great! – SRMR Sep 12 '15 at 18:08
  • EridB is probably right, you need to covert your objects to a supported property list type. If you printed the error being returned from the updateApplicationContext call you'd likely see that you are getting the "invalid types" error returned – ccjensen Sep 12 '15 at 19:46
  • @ccjensen Ok. Any idea if CloudKit would work better where I wouldn't have to do the NSCoding stuff? – SRMR Sep 15 '15 at 13:33

1 Answers1

2

Error

You are getting that error, because you are putting a NSObject (MatchupObject) which does not conform to NSCoding inside the dictionary that you are going to pass.

From Apple Docs

For most types of transfers, you provide an NSDictionary object with the data you want to send. The keys and values of your dictionary must all be property list types, because the data must be serialized and sent wirelessly. (If you need to include types that are not property list types, package them in an NSData object or write them to a file before sending them.) In addition, the dictionaries you send should be compact and contain only the data you really need. Keeping your dictionaries small ensures that they are transmitted quickly and do not consume too much power on both devices.

Details

You need to archive your NSObject's to NSData and then put it in the NSDictionary. If you archive a NSObject which does not conform to NSCoding, the NSData will be nil.

This example greatly shows how to conform a NSObject to NSCoding, and if you implement these things then you just follow the code below:

//Send the dictionary to the watch
func sendObjectToWatch(object: NSObject) {
    //Archiving
    let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)

    //Putting it in the dictionary
    let matchupDict = ["matchupSaved" : data]

    //Send the matchupDict via WCSession
    self.session?.updateApplicationContext(matchupDict)
}

//When receiving object from the other side unarchive it and get the object back
func objectFromData(dictionary: NSDictionary) ->  MatchupObject {
    //Load the archived object from received dictionary
    let data = dictionary["matchupSaved"]

    //Deserialize data to MatchupObject
    let matchUpObject = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MatchupObject

    return matchUpObject
}

Since you are using Parse, modifying an object maybe cannot be done (I haven't used Parse in a while, so IDK for sure), but from their forum I found this question: https://parse.com/questions/is-there-a-way-to-serialize-a-parse-object-to-a-plain-string-or-a-json-string which can help you solve this problem easier than it looks above :)

E-Riddie
  • 14,660
  • 7
  • 52
  • 74
  • I added "EDIT 1" and "EDIT 2" above so you could see exactly what I did. I'm sure I'm not doing whatever you're suggesting perfectly right or else it would work, so I was wondering if you could give me some guidance as to what I'm not understanding. Thanks! – SRMR Sep 13 '15 at 00:40
  • Like I mentioned on my answer, you have not implemented the NSCoding protocol methods which I am referring https://www.hackingwithswift.com/read/12/3/fixing-project-10-nscoding. That's why you are getting -[PFObject encodeWithCoder:]: unrecognized selector sent to instance 0x7fbe80d43f30. As for second edit simply change MatchupObject to object l. – E-Riddie Sep 13 '15 at 04:17
  • If your referring to the example using `class Person: NSObject, NSCoding {` I tried that, but when I do `class TableViewController: PFQueryTableViewController, WCSessionDelegate, NSCoding {` it gives me an error letting me know "Redundant conformance of `TableViewController` to protocol `NSCoding`". So it looks like my class already supports the `NSCoding` protocol right? – SRMR Sep 13 '15 at 13:26
  • You need to make the `PFObject` to conform to `NSCoding` not the viewController. If you don't have access to modify the `PFObject` then refer to this link: https://parse.com/questions/is-there-a-way-to-serialize-a-parse-object-to-a-plain-string-or-a-json-string – E-Riddie Sep 13 '15 at 13:29
  • So https://parse.com/questions/is-there-a-way-to-serialize-a-parse-object-to-a-plain-string-or-a-json-string would mean creating a separate file for a `PFObject` class? I guess I just am surprised it would be that complicated to pass the dictionary is all. Or am I misunderstanding you? – SRMR Sep 13 '15 at 13:34
  • I'm not sure what exactly you mean by "As for second edit simply change MatchupObject to object I"? Do you mean `object I` or something, and I guess I don't even know what that would be. – SRMR Sep 13 '15 at 13:37
  • Also, I'm assuming I'm not using edit 1 AND edit 2, rather edit 1 OR edit 2, right? edit 1 was one version of me trying to implement your suggestion, and edit 2 was a different version of me trying to implement your suggestion. – SRMR Sep 13 '15 at 13:38
  • @SRMR you don't have to create a separate `PFObject`, you just need to conform it to `NSCoding`. Another way it to make a JSON object from `PFObject` and then parse it again from the other side. Regarding object i is a typo from me. – E-Riddie Sep 13 '15 at 13:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/89515/discussion-between-eridb-and-srmr). – E-Riddie Sep 13 '15 at 13:40
  • My `TableViewController` already is the class, so how would I do `PFObject` as a class within it? In the example they do `class Person: NSObject, NSCoding {`, but I don't think I can do that since I'm already in a `TableViewController` class – SRMR Sep 13 '15 at 13:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/89535/discussion-between-srmr-and-eridb). – SRMR Sep 14 '15 at 00:25
  • Any idea if CloudKit would work better where I wouldn't have to do the NSCoding stuff? – SRMR Sep 15 '15 at 13:33
  • @SRMR CloudKit is not supported on WatchOS 2. Read here for more details https://developer.apple.com/library/prerelease/watchos/documentation/General/Conceptual/AppleWatch2TransitionGuide/ – E-Riddie Sep 15 '15 at 16:09
  • And IMO it's better for you to pass really required data in String format, where you can modify or parse to the Watch Side. – E-Riddie Sep 15 '15 at 16:11
  • Yeah I saw that its not supported for calls straight from the Watch, but with Parse you have to add that third party library like SwiftyJSON to convert the object to something that can be passed and is in line with the NSCoding protocol. So my question is more about using CloudKit instead of Parse so I don't have to use SwiftyJSON to get the data in a format where I can pass it to the Watch ya know? – SRMR Sep 15 '15 at 17:15
  • @SRMR I don't know what are you trying to achieve but simply don't try to pass the whole object to the other side, just pass some things that make sense as Strings. Let say we have a object.name, you pass a ["name": object.name] to the other side not the whole object. Take some data to the other side (not the entire object) as JSON format (normally encoded as NSData) – E-Riddie Sep 15 '15 at 17:19
  • Yeah. I think I was just thinking CloudKit could potentially eliminate a step i.e. having to use SwiftyJSON for JSON to be compliant, you know what I mean? – SRMR Sep 15 '15 at 19:06
  • I don't quite know about CloudKit, but like I said earlier it is not supported on WatchOS side and you better not pass too much information to other side, just main things and these things can be easily transported to the other side. – E-Riddie Sep 15 '15 at 19:32