Search code examples
swiftnsuserdefaultswatchkit

Passing Multiple Objects To WatchKit In A NSUserDefaults


With the help of some great tutorials and users here, I've had success implementing SwiftyJSON in my app and getting a basic WatchKit app built alongside. My last hurdle to pass is getting my whole set of parsed JSON data to be passed to WatchKit, as to allow me to choose from a cell in a TableView and pull up more specific detail on a piece of criteria.

I'm parsing JSON data in my Minion.swift file, like so;

import UIKit

class Minion {

    var name: String?
    var age: String?
    var height: String?
    var weight: String?


    class func fetchMinionData() -> [Minion] {
        let dataURL = NSURL(string: "http://myurl/json/")

        var dataError: NSError?

        let data = NSData(contentsOfURL: dataURL!, options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &dataError)


        let minionJSON = JSONValue(data)
        var minions = [Minion]()

       for minionDictionary in minionJSON {
           minions.append(Minion(minionDetails: minionDictionary))
        }

        return minions 
    }

    init(minionDetails: JSONValue) {
        name = minionDetails["san"].string
        age = minionDetails["age"].string
        height = minionDetails["height"].string
        weight = minionDetails["free"].string
    }
}

For my iOS app, this is working well to populate my UITableView and subsequent Detail View. I have my ViewController.Swift like so;

import UIKit

class ViewController: UITableViewController {

    let minions: [Minion]

    required init(coder aDecoder: NSCoder!) {
        minions = Minion.fetchMinionData()
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let defaults = NSUserDefaults(suiteName: "group.com.mygroup.data")
        let key = "dashboardData"
        defaults?.setObject(minions, forKey: key)
        defaults?.synchronize()
    }

// MARK: Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

}

I've truncated much of the code as I don't believe it's relevant to WatchKit. In the WatchKit extension, I have my InterfaceController.swift like so;

import WatchKit

class InterfaceController: WKInterfaceController {

    @IBOutlet weak var minionTable: WKInterfaceTable!

    let defaults = NSUserDefaults(suiteName: "group.com.mygroup.data")

    var dashboardData: String? {
        defaults?.synchronize()
        return defaults?.stringForKey("dashboardData")
    }

    let minions = ???

When I run the iOS app, it throws me the error "Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')" because I am passing the whole set of JSON data as "minions." If I set my NSUserDefaults key to "minions[0].name" it will pass the single string, but passing the whole set of data so the WatchKit table can allow me to choose a row seems to be evading me.

In advance, as always, I am most grateful.


Solution

  • Your Minion class need to implement the NSCoding. Then in your view controller you need to transfer your Minion object to NSData object.

     class Minion: NSObject, NSCoding  {
        .....
        init(coder aDecoder: NSCoder!) {
          aCoder.encodeObject(name, forKey: "name")
          aCoder.encodeObject(age, forKey: "age")
          aCoder.encodeObject(height, forKey: "height")
          aCoder.encodeObject(weight, forKey: "weight")
        }
    
        func encodeWithCoder(aCoder: NSCoder) {
           name = aDecoder.decodeObjectForKey("name") as String
           age = aDecoder.decodeObjectForKey("age") as String
           height = aDecoder.decodeObjectForKey("height") as String
           weight = aDecoder.decodeObjectForKey("weight") as String
        }
    }
    

    In your ViewController class:

      NSKeyedArchiver.setClassName("Minion", forClass: Minion.self)
      defaults?.setObject(NSKeyedArchiver.archivedDataWithRootObject(minions), forKey: "minions")
    

    If you want to retrieve the data from NSUserDefaults:

    if let data = defaults?.objectForKey("minions") as? NSData {
        NSKeyedUnarchiver.setClass(Minion.self, forClassName: "Minion")
        let minions = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [Minion]
    }