transfer array of custom objects with WatchConnectivity / encoding in Swift - swift2

Am trying to transfer an array of custom objects from iOS to watchkitextension.
Understood that in order to do so, data needs to be encoded. Am though getting error when decoding.
Here we go:
The custom object:
final class Person: NSObject {
var PersonName:String = ""
var PersonAge:Int = 0
var joined:NSDate = NSDate()
init(PersonName: String, PersonAge:Int, joined:NSDate){
self.PersonName = PersonName
self.PersonAge = PersonAge
self.joined = joined
super.init()
}
}
extension Person: NSCoding {
private struct CodingKeys {
static let PersonName = "PersonName"
static let PersonAge = "PersonAge"
static let joined = "joined"
}
convenience init(coder aDecoder: NSCoder) {
let PersonName = aDecoder.decodeObjectForKey(CodingKeys.PersonName) as! String
let PersonAge = aDecoder.decodeIntForKey(CodingKeys.PersonAge) as! Int
let joined = aDecoder.decodeDoubleForKey(CodingKeys.joined) as! NSDate
self.init(PersonName: PersonName, PersonAge: PersonAge, joined: joined)
}
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(PersonName, forKey: CodingKeys.PersonName)
encoder.encodeObject(PersonAge, forKey: CodingKeys.PersonAge)
encoder.encodeObject(joined, forKey: CodingKeys.joined)
}
}
The class with the array:
#objc(Group)
final class Group: NSObject {
static let sharedInstance = Group()
var Persons:[Person] = []
required override init() {
super.init()
}
init (Persons:[Person]){
self.Persons = Persons
super.init()
}
}
extension Group: NSCoding {
private struct CodingKeys {
static let Persons = "Persons"
}
convenience init(coder aDecoder: NSCoder) {
let Persons = aDecoder.decodeObjectForKey(CodingKeys.Persons) as! [Person]
self.init(Persons: Persons)
self.Persons = aDecoder.decodeObjectForKey(CodingKeys.Persons) as! [Person]
}
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(Persons, forKey: CodingKeys.Persons)
}
}
Creating example object, append to array, then encode:
let aPerson:Person? = Person(PersonName: "Martin", PersonAge: 50, joined: NSDate())
Group.sharedInstance.Persons.append(aPerson!)
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(Group.sharedInstance)
And here I get the error "execution was interrupted - reason signal SIGABRT"
let decodedData = NSKeyedUnarchiver.unarchiveObjectWithData(encodedData) as? Group

Try changing these sections to:
convenience init(coder aDecoder: NSCoder) {
let PersonName = aDecoder.decodeObjectForKey(CodingKeys.PersonName) as! String
let PersonAge = aDecoder.decodeIntForKey(CodingKeys.PersonAge) as! Int
let joined = aDecoder.decodeObjectForKey(CodingKeys.joined) as! NSDate
self.init(PersonName: PersonName, PersonAge: PersonAge, joined: joined)
}
func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(PersonName, forKey: CodingKeys.PersonName)
encoder.encodeInt(PersonAge, forKey: CodingKeys.PersonAge)
encoder.encodeObject(joined, forKey: CodingKeys.joined)
}
So that the serialization matches the deserialization, and the types desired by the Person class. That said, the WWDC talk on WatchConnectivity specifically recommended not to use NSKeyedArchiver as it is not a very space efficient serialization method.

Related

core data nstableview searchbar

i have a nstableview which i fill with data of core data.
NSManagedObject
import Foundation
import CoreData
#objc(Person)
public class Person: NSManagedObject {
#NSManaged public var firstName: String
#NSManaged public var secondName: String
}
RequestData
func requestData() {
let appdelegate = NSApplication.shared().delegate as! AppDelegate
let context = appdelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
do {
person = try context.fetch(request) as! [Person]
tableViewn.reloadData()
} catch { }
}
i also have a custom cell view for my tableView.
I fill the data like this:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let view = tableView.make(withIdentifier: "Cell", owner: self) as? CustomCell
view?.txtfirstName.stringValue = person.firstName
view?.txtsecondName.stringValue = person.secondName
return view
}
Now i would like to realize a searchbar (which i have already in my view controller) for searching with first or second name.
but i have no idea how i can realize this.
Set the delegate of the NSSearchField to the target class
implement controlTextDidChange
override func controlTextDidChange(_ notification: Notification) {
if let field = notification.object as? NSSearchField {
let query = field.stringValue
let predicate : NSPredicate?
if query.isEmpty {
predicate = nil
} else {
predicate = NSPredicate(format: "firstName contains[cd] %# OR lastName contains[cd] %#", query, query)
}
requestData(with: predicate)
} else {
super.controlTextDidChange(notification)
}
}
Change requestData to
func requestData(with predicate : NSPredicate? = nil) {
let appdelegate = NSApplication.shared().delegate as! AppDelegate
let context = appdelegate.persistentContainer.viewContext
let request = NSFetchRequest<Person>(entityName: "Person")
request.predicate = predicate
do {
person = try context.fetch(request)
tableViewn.reloadData()
} catch { }
}
Side note:
If you are using NSManagedObject subclasses create the fetch request more specific NSFetchRequest<Person>(entityName... rather than NSFetchRequest<NSFetchRequestResult>(entityName..., that avoids the type cast in the fetch line.

NSCoder crash on decodeBool forKey (Xcode 8, Swift 3)

I have this simple class
import UIKit
class SimpleModel: NSObject, NSCoding {
var name : String!
var done : Bool!
init(name:String) {
self.name = name
self.done = false
}
internal required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.done = aDecoder.decodeBool(forKey: "done") // BUG HERE
}
func encode(with encoder: NSCoder) {
encoder.encode(self.name, forKey: "name")
encoder.encode(self.done, forKey: "done")
}
}
the save code:
let data = NSKeyedArchiver.archivedData(withRootObject: storageArray)
UserDefaults.standard.set(data, forKey: "storage")
UserDefaults.standard.synchronize()
the read code:
if let data = UserDefaults.standard.data(forKey: "storage") {
storageArray = NSKeyedUnarchiver.unarchiveObject(with: data) as! [SimpleModel]
}
the problem occurs when the NSKeyedUnarchiver does it's job. I can not understand where the problem comes from.
Thanks!
the trick is remove ! form the primitive types.
If you put ! you are saying "make an implicit-unwrapped optional" so the encoder will archive as NSNumber instead of Bool (or Int, Double).
If you remove ! the encoder will archive as Bool and things works as expected (I spent an "incident" and this solution is provided by Apple)
Bool and Int have new methods:
self.x = Int(decoder.decodeCInt(forKey: "Speed"))
self.y = decoder.decodeObject(forKey: "Serial") as! String
self.z = Bool(decoder.decodeBool(forKey: "Direction") )
I had the same Problem.
Try this:
self.done = aDecoder.decodeObject(forKey: "done") as? Bool ?? aDecoder.decodeBool(forKey: "done")

Use of unresolved identifier 'objects'

How to define objects in the code and of which type?
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate=UIApplication.sharedApplication().delegate as! AppDelegate
let context=appDelegate.managedObjectContext
let request=NSFetchRequest(entityName:lineEntityName)
do{
let objects = try context.executeFetchRequest(request)
}
catch let error as NSError {
print(error)
}
if let objectList=objects
{
for oneObject in objectList
{
let lineNum=oneObject.valueForKey(lineNumberKey) as integerValue
let lineText=oneObject.valueForKey(lineTextKey) as String
let lineField=lineFields(lineNum)
textField.text=lineText
}
}
else
{
print("There was an Error")
}
let app=UIApplication.sharedApplication()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"applicationWillResignActiveNotification", name: UIApplicationWillResignActiveNotification, object: app)
// Do any additional setup after loading the view, typically from a nib.
}
The recommended way is to put all good code in the do clause which solves the problem.
And executeFetchRequest returns a non-optional array so the optional binding can be omitted.
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = appDelegate.managedObjectContext
let request = NSFetchRequest(entityName:lineEntityName)
do {
let objects = try context.executeFetchRequest(request)
for oneObject in objects
{
let lineNum = oneObject.valueForKey(lineNumberKey) as integerValue
let lineText = oneObject.valueForKey(lineTextKey) as String
let lineField = lineFields(lineNum)
textField.text = lineText
}
let app = UIApplication.sharedApplication()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"applicationWillResignActiveNotification", name: UIApplicationWillResignActiveNotification, object: app)
// Do any additional setup after loading the view, typically from a nib.
}
catch let error as NSError {
print(error)
}
}

How to unarchive custom array with NSUserDefaults?

I'm saving an array of type ClassA to NSUserDefaults. ClassA look like this:
class ClassA :NSObject, NSCoding{
init (descriptionParam: String) {
self.description = descriptionParam
}
var description: String?
required init(coder aDecoder: NSCoder) {
if let description = aDecoder.decodeObjectForKey("description") as? String {
self.description = description
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let description = self.description {
aCoder.encodeObject(description, forKey: "description")
}
}
}
This is how I'm saving the array to NSUserDefaults:
let myData = NSKeyedArchiver.archivedDataWithRootObject(ClassAManager.classa_array)
userDefaults.setObject(myData, forKey: "classAarray");
I'm doing the following in my initial viewDidLoad():
var classA: AnyObject? = NSUserDefaultsManager.userDefaults.objectForKey("classAarray") as AnyObject?
let classAunpacked = NSKeyedUnarchiver.unarchiveObjectWithData(classA) as [ClassA]
I get the following compile-time error on the second line above (the one with let):
Cannot invoke 'unarchiveObjectWithData' with an argument list of type '(AnyObject?)'
However, if I try to retrieve the array with anything other than AnyObject?, I get other compile time errors. It also seems I can't cast from AnyObject? to [ClassA]. Any ideas how this should be done?
unarchiveObjectWithData takes an NSData as it's sole argument, not an optional AnyObject. Since the result of unarchive... is also an optional, I'd suggest using:
if let classA = NSUserDefaultsManager.userDefaults.dataForKey("classAarray") {
if let classAunpacked = NSKeyedUnarchiver.unarchiveObjectWithData(classA) as? [ClassA] {
// Use classAunpacked here
}
}

PFQueryTableView Controller not loading custom Cells

Im working on an app and I would like it to populate the cells based on users who are within a set distance from the currentuser. For some reason the customcells are not being populated with the correct objects. The labels and images that are supposed to be retrieved are blank. All i get is a blank cell. I made sure i gave the cell identifier the correct name, and i also made sure to link the tableviewcontroller and the tablecellview to their respective classes,but still no luck.
first i created initializers:
class TableViewController: PFQueryTableViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var currLocation: CLLocationCoordinate2D?
override init!(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.parseClassName = "User"
self.textKey = "FBName"
// self.imageKey = "pictureURL"
self.pullToRefreshEnabled = true
self.objectsPerPage = 10
self.paginationEnabled = true
}
Then in viewDidLoad i enabled location services:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 200
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
loadData()
println("location services enabled bruh")
}
}
Next i overrode the queryfortable function:
override func queryForTable() -> PFQuery! {
let query = PFQuery(className: "User")
if let queryLoc = currLocation {
query.whereKey("location", nearGeoPoint: PFGeoPoint(latitude: queryLoc.latitude, longitude: queryLoc.longitude), withinMiles: 50)
query.limit = 40
query.orderByAscending("createdAt")
println("\(queryLoc.latitude)")
return query
} else {
query.whereKey("location", nearGeoPoint: PFGeoPoint(latitude: 37.411822, longitude: -121.941125), withinMiles: 50)
query.limit = 40
query.orderByAscending("createdAt")
println("else statement")
return query
}
}
then the objectAtIndexPath function
override func objectAtIndexPath(indexPath: NSIndexPath!) -> PFObject! {
var obj : PFObject? = nil
if indexPath.row < self.objects.count {
obj = self.objects[indexPath.row] as? PFObject
}
return obj
}
and lastly I returned the cell, but for some reason it does not work:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject?) -> PFTableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as TableViewCell
cell.userName?.text = object?.valueForKey("FBName") as? String
let userProfilePhotoURLString = object?.valueForKey("pictureURL") as? String
var pictureURL: NSURL = NSURL(string: userProfilePhotoURLString!)!
var urlRequest: NSURLRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (NSURLResponse response, NSData data,NSError error) -> Void in
if error == nil && data != nil {
cell.userImage?.image = UIImage(data: data)
}
}
cell.ratingsView?.show(rating: 4.0, text: nil)
return cell
}
ps, i have the number of sections set to 1, just didnt think that method would be useful to show here.
okay I found the issue! The issue was that I was trying to use PFQuery in order to retrieve a list of PFUsers. I found out that cannot be done using PFQuery infact PFUser has it's own query method for retrieving information from its users.
all i had to do was replace this line:
let query = PFQuery(className: "User")
with this:
let query = PFUser.query()

Resources