Passing Multiple Objects To WatchKit In A NSUserDefaults - swift

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.

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]
}

Related

use func to delete a specific item from core data binary data

I am trying to delete binary data from core data. I am using a var int 'place' to determine what specific item I am trying to delete I am getting a runtime error under helpImage.shareInstance.deleteInfo(info: place) which is below.
Cannot convert value of type 'Int' to expected argument type 'Info'
What can I do to delete the 1st item saved in a core data binary attribute?
import UIKit;import CoreData
class ViewController: UIViewController {
var place = 0
override func viewDidLoad() {
super.viewDidLoad()
let gwen = UIImage(named: "unnamed.jpg")
if let imageData = gwen.self?.pngData() {
helpImage.shareInstance.saveImage(data: imageData)
}
let alz = UIImage(named: "alba.jpeg")
if let imageData = alz.self?.pngData() {
helpImage.shareInstance.saveImage(data: imageData)
}
}
#objc func deleteM(){
helpImage.shareInstance.deleteInfo(info: place)
}
}
class helpImage: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpImage()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveImage(data: Data) {
let imageInstance = Info(context: context)
imageInstance.img = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func deleteInfo(info: Info) {
do {
try context.delete(info)
} catch {
print(error.localizedDescription)
}
}
}
I'm also a newbie in swift and learning so if anyone has feedback, I'm more than happy to implement.
here is stroryBoard: on "Save" button click, we will save images in CoreData and on "Show" button click, we will display a tableView with our images fetched from CoreData
here is coreData: don't forget to check Manual/None in Codegen in coreData Class
then manually add coreData NSManagedObject SubClass files (there will be two files).
To generate the class and properties files initially:
source: https://developer.apple.com/documentation/coredata/modeling_data/generating_code
From the Xcode menu bar, choose Editor > Create NSManagedObject Subclass.
Select your data model, then the appropriate entity, and choose where to save the files. Xcode places both a class and a properties file into your project.
// this is how your "Picture+CoreDataClass.swift" looks like
import Foundation
import CoreData
#objc(Picture)
public class Picture: NSManagedObject {
}
// this how your "Picture+CoreDataProperties.swift" looks like
import Foundation
import CoreData
extension Picture {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Picture> {
return NSFetchRequest<Picture>(entityName: "Picture")
}
#NSManaged public var pic: String?
#NSManaged public var id: Int64
}
extension Picture : Identifiable {
}
I have used below extension to get currentTimeStamp as we will need unique ID for each of our coreData object, we will pass currentTimeStamp as ID to be unique.
// MARK: - Get the Current Local Time and Date Timestamp
source: https://stackoverflow.com/questions/46376823/ios-swift-get-the-current-local-time-and-date-timestamp
extension Date {
static var currentTimeStamp: Int64{
return Int64(Date().timeIntervalSince1970 * 1000)
}
}
I have made CRUD functions in DataBaseHelper.swift file
// this "DataBaseHelper" Class
import Foundation
import UIKit
import CoreData
class DataBaseHelper {
// MARK: - Get Context
class func getContext() -> NSManagedObjectContext{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = DataBaseHelper()
let context = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Picture")
// MARK: - Save Images
func saveImage(picString: String, id: Int64 = Date.currentTimeStamp) {
// here I have passed id = Date.currentTimeStamp, as we need unique ID for each of our object in coreData and Date.currentTimeStamp will always give unique integer. we will use this ID to update and delete particular object.
let entity = NSEntityDescription.entity(forEntityName: "Picture", in: context)!
let image = NSManagedObject(entity: entity, insertInto: context)
image.setValue(picString, forKey: "pic") // key should be same as the attributes taken in coreData Table.
image.setValue(id, forKey: "id") // key should be same as the attributes taken in coreData Table.
do {
try context.save()
print("Images saved in coreData")
print("imageString: \(picString), id: \(id)") // this will print your entered (saved) object in coreData
} catch let error {
print("Could not save images: \(error.localizedDescription)")
}
}
// MARK: - fetch Images
func fetchImages() -> [Picture] {
var arrImages = [Picture]()
fetchRequest.returnsObjectsAsFaults = false
do {
arrImages = try context.fetch(fetchRequest) as? [Picture] ?? [Picture]()
print("Images while fetching from coreData: \(arrImages)") // this will print all objects saved in coreData in an array form.
} catch let error {
print("Could not fetch images: \(error.localizedDescription)")
}
return arrImages
}
// MARK: - fetch Images by ID
func fetchImagesByID(id: Int64) -> Picture {
fetchRequest.returnsObjectsAsFaults = false
let predicate = NSPredicate(format: "id == \(id)")
fetchRequest.predicate = predicate
let result = try? context.fetch(fetchRequest)
return result?.first as? Picture ?? Picture()
}
// MARK: - Update Image
func updateImage(object: Picture) {
let image = fetchImagesByID(id: object.id) // we will first fetch object by its ID then update it.
image.pic = object.pic
do {
try context.save()
print("Image updated in CoreData")
print("after updating Picture --> \(object)")
} catch let error {
print("Could not update Picture: \(error.localizedDescription)")
}
}
// MARK: - Delete Image
func deleteImage(id: Int64) {
let image = fetchImagesByID(id: id) // we will first fetch object by its ID then delete it.
context.delete(image)
do {
try context.save()
print("Image deleted from CoreData")
} catch let error {
print("Could not delete Image --> \(error.localizedDescription)")
}
}
}
here is our ViewController:
I have added 4 images in Assets folder.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var btnSaveImages: UIButton!
#IBOutlet weak var tableViewPicture: UITableView!
#IBOutlet weak var btnShowImages: UIButton!
var resultImages = [Picture]() // this an an instance of our model class (coreData)
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "CoreData Demo"
setUpTableView()
}
// MARK: - Save Images
func saveImagesInCoreData() {
// I have added 4 images in Assets folder, here we will save 3 images in CoreData
let image1 = "flower1"
let image2 = "flower2"
let image3 = "flower3"
DataBaseHelper.shareInstance.saveImage(picString: image1)
DataBaseHelper.shareInstance.saveImage(picString: image2)
DataBaseHelper.shareInstance.saveImage(picString: image3)
}
// MARK: - Fetch Images
func fetchImagesFromCoreData() {
resultImages = DataBaseHelper.shareInstance.fetchImages() // this will give fetched images
}
// MARK: - Set Up TableView
func setUpTableView () {
tableViewPicture.delegate = self
tableViewPicture.dataSource = self
}
// MARK: - Button Save Images Event
#IBAction func btnSaveImages_Event(_ sender: UIButton) {
saveImagesInCoreData() // save images in CoreData
}
// MARK: - Button Show Images Event
#IBAction func btnShowImages_Event(_ sender: UIButton) {
fetchImagesFromCoreData() // fetch Images from CoreData
self.tableViewPicture.reloadData() // reload tableView
}
}
// MARK: - Extesnion TableViewDelegate and TableViewDataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return resultImages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell") ?? UITableViewCell()
cell.imageView?.image = UIImage(named: resultImages[indexPath.row].pic ?? "")
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .default, title: "Edit") { (action, indexPath) in
print("Action Edit")
// here we will edit image of selected row in our tableView, we will update selected row with new image that is "flower4".
let image4 = "flower4"
self.resultImages[indexPath.row].pic = image4
DataBaseHelper.shareInstance.updateImage(object: self.resultImages[indexPath.row]) // update image of selected row in tableView
self.tableViewPicture.reloadData()
}
editAction.backgroundColor = .lightGray
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
print("Action Delete")
// here we will delete an object of selected row in our tableView.
DataBaseHelper.shareInstance.deleteImage(id: self.resultImages[indexPath.row].id) // delete object of selected row in tableView
self.resultImages.remove(at: indexPath.row) // remove from resultImages array
self.tableViewPicture.reloadData()
}
return [deleteAction, editAction]
}
}
if you have doubt feel free to ask !

how to implement json in realm swift

I have a dictionary in json format. I want to show it with a table in my app(about 1000 cells) and also save it to realm database. I am new to database can anyone please tell me how to implement this? Should I try to convert the format outside the app or load the json when it is used?
My dictionary looks like this..
[
{
"id":0,
"book":1,
"lesson":1,
"kanji":"\u4e2d\u56fd\u4eba"
} {
"id":1,
"book":1,
"lesson":1,
"kanji":"\u65e5\u672c\u4eba"
},
...
]
First of all, you will need to install 2 pods:
pod 'SwiftyJSON' #great for handling JSON
pod 'RealmSwift' #Realm database
You will need to create the object that will be able to be saved in Realm. I suppose your object is some type of course or something similar. You can rename it per your needs:
import Foundation
import RealmSwift
import SwiftyJSON
class Course: Object {
#objc dynamic var id = 0
#objc dynamic var book = 0
#objc dynamic var lesson = 0
#objc dynamic var kanji = ""
override static func primaryKey() -> String? { //you need this in case you will want to update this object in Realm
return "id"
}
convenience init(courseJson: JSON) {
self.id = courseJson["id"].intValue
self.book = courseJson["book"].intValue
self.lesson = courseJson["lesson"].intValue
self.kanji = courseJson["kanji"].stringValue
}
}
Now, at the place where you get your json from the server, you need to do this:
var courses = [Course]()
let jsonArray = JSON(dataFromServer).arrayValue
for courseJson in jsonArray {
let course = Course(courseJson: courseJson)
courses.append(course)
}
//Now, you need to save these objects in Realm:
let realm = try! Realm()
try! realm.write {
realm.add(courses, update: true)
}
In the viewDidLoad method of the view controller where you want to show the data, you need to fetch all your Course objects from Realm, add them to an array and show data in the tableView/collectionView:
import UIKit
import RealmSwift
class PresentingCoursesVC: UIViewController {
//MARK: IBOutlets
#IBOutlet weak var tableView: UITableView!
//MARK: Properties
var courses = Results<Course>?
//MARK: Lifecycles
override func viewDidLoad() {
super.viewDidLoad()
getCourses()
}
//MARK: Private functions
private func getCourses() {
let realm = try! Realm()
courses = realm.objects(Course.self).sorted(byKeyPath: "id", ascending: true)
tableView.reloadData()
}
}
//MARK: TableView Datasource
extension PresentingCoursesVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courses?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCustomCell") as! MyCustomCell
let course = courses[indexPath.row]
cell.setupCell(course: course)
return cell
}
}
I hope my answer was helpful!
class Book: Object {
#objc dynamic var id = 0
#objc dynamic var book = 0
#objc dynamic var lesson = 0
#objc dynamic var kanji = ""
}
class MyDic: Object {
var yourKeyOfDic = List<Book>()
}
Now map your dic with MyDic class object and then add that object in realm by following code,
let realm = try! Realm()
try! realm.write {
realm.add(yourDicObject)
}

How to make singleton class from class, which inherits from nsobject and nscoding?

I am trying to make the game settings which loads, saves itself, and makes it singleton class. All my efforts lead to failure, XCode asks me "Cannot invoke initializer for type "Settings" with no arguments". How can I fix this?
This is the code:
class Settings: NSObject, NSCoding {
static let sharedInstance = Settings()
var currentLevel: Int
var positionOfPlayer: [Int]?
var sounds: Bool
var shape: String
var completedLevels: [Int: Bool]
init?(currentLevel: Int, positionOfPlayer: [Int]?, sounds: Bool, shape: String, completedLevels: [Int: Bool]) {
self.currentLevel = currentLevel
self.sounds = sounds
self.shape = shape
self.completedLevels = completedLevels
if let position = positionOfPlayer as [Int]? {
self.positionOfPlayer = position
}
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let currentLevel = aDecoder.decodeObjectForKey(SettingNames.nameOfCurrentLevel) as? Int
let completedLevels = aDecoder.decodeObjectForKey(SettingNames.nameOfCompletedLevels) as? [Int: Bool]
let positionOfPlayer = aDecoder.decodeObjectForKey(SettingNames.positionOfPlayerOnCurrentLevel) as? [Int]
let sounds = aDecoder.decodeObjectForKey(SettingNames.nameOfSounds) as? Bool
let shape = aDecoder.decodeObjectForKey(SettingNames.nameOfShapes) as? String
self.init(currentLevel: currentLevel!, positionOfPlayer: positionOfPlayer, sounds: sounds!, shape: shape!, completedLevels: completedLevels!)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(currentLevel, forKey: SettingNames.nameOfCurrentLevel)
aCoder.encodeObject(completedLevels, forKey: SettingNames.nameOfCompletedLevels)
if let position = positionOfPlayer as [Int]? {
// If game canceled or ended during playing, it saves the current player position.
// Next time, when player open the game, it will continue
aCoder.encodeObject(position, forKey: SettingNames.positionOfPlayerOnCurrentLevel)
}
aCoder.encodeBool(sounds, forKey: SettingNames.nameOfSounds)
aCoder.encodeObject(shape, forKey: SettingNames.nameOfShapes)
}
}
You're trying to access a constructor which accepts no parameters which was not implemented for the current class. Try overriding the init method, that should remove the error.
override init(){
// some code
}
Here's a full example I've tried:
import Foundation
class Settings : NSObject, NSCoding {
static let sharedInstance = Settings()
var a: String?
var b: String?
convenience init(a: String, b: String){
self.init()
self.a = a
self.b = b
}
override init(){
}
required init?(coder aDecoder: NSCoder) {
}
func encodeWithCoder(aCoder: NSCoder) {
}
}
Just a point of view: a Singleton that requires you to pass parameters in order to configure it does no longer behave like a singleton.

Saving Array with NSCoding

I have a small app that has a few saving functionalities. I have a data model class called: Closet:
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var Task: [Assignment]? // <----- assignment class is in example 2
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(deptName, forKey: "deptName")
aCoder.encodeObject(managerName, forKey: "mngName")
// aCoder.encodeObject(Task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
course = aDecoder.decodeObjectForKey("deptName") as! String
instructor = aDecoder.decodeObjectForKey("mngName") as! String
// Task = aDecoder.decodeObjectForKey("tasKArray") as? [Assignment]
}
override init() {
super.init()
}
}
So this is the main controller data model which in the first View Controller, a user is able to tap the "+" button to add a department name and manager name. The problem is not with saving this as i save it successfully using NSKeyedArchive and loads it back when the app starts.
The Problem:
I want to add an array of assignments on this data model Department called Assignment which would have a title and a notes variable. This is the Data model for Assignment:
Assignment.swift
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encodeWithCoder(aCoder: NSCoder) {
// Methods
aCoder.encodeObject(title, forKey: "Title")
aCoder.encodeObject(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObjectForKey("Title") as! String
notes = aDecoder.decodeObjectForKey("notepad") as! String
super.init()
}
override init() {
super.init()
}
}
So what i am essentially trying to achieve is an app where a user enters different departments with different manager names which work now in my app, but within a department, the user can click the "+" button to add an assignment title and notes section that can be editable when clicked which i can handle afterwards. These assignments are different from department to department.
My big problem is achieving this functionality. I can't seem to get this working.
I want this array assigment property to be part of the Department Class so each cell can have their own sort of To-Do list. any help would definitely help me out a lot. Thanks :)
You are using NSCoder correctly, but there are two errors in capitalization. The first error affects the functionality of the application, and the second error is a stylistic mistake. You encoded Task with the key "taskArray", but you tried to decode it with the key "tasKArray". If you fix the capital K in the latter, then your code will work.
The second capitalization error is a stylistic mistake: Task, like all properties in Swift, should be written in lowerCamelCase (llamaCase).
Be sure to pay close attention to indentation. In programming, there are special indentation rules we follow that help make code clear. Here is the corrected code with proper capitalization and indentation:
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var task: [Assignment]?
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(deptName, forKey: "deptName")
aCoder.encodeObject(managerName, forKey: "mngName")
aCoder.encodeObject(task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
course = aDecoder.decodeObjectForKey("deptName") as! String
instructor = aDecoder.decodeObjectForKey("mngName") as! String
task = aDecoder.decodeObjectForKey("taskArray") as? [Assignment]
}
override init() {
super.init()
}
}
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encodeWithCoder(aCoder: NSCoder) {
// Methods
aCoder.encodeObject(title, forKey: "Title")
aCoder.encodeObject(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObjectForKey("Title") as! String
notes = aDecoder.decodeObjectForKey("notepad") as! String
super.init()
}
override init() {
super.init()
}
}
Updated for Swift 5 / Xcode Version 12.4 (12D4e)
Thanks for the example above Tone416 -- I've reworked it for Swift 5 as the protocols and methods have changed. I've also included a simple test to prove it out so you should be able to just cut and paste this into a playground a run it.
import Foundation
class Department: NSObject, NSCoding {
var deptName = ""
var managerName = ""
var task: [Assignment]?
func encode(with coder: NSCoder) {
coder.encode(deptName, forKey: "deptName")
coder.encode(managerName, forKey: "mngName")
coder.encode(task, forKey: "taskArray")
}
required init(coder aDecoder: NSCoder) {
super.init()
deptName = aDecoder.decodeObject(forKey: "deptName") as! String
managerName = aDecoder.decodeObject(forKey: "mngName") as! String
task = aDecoder.decodeObject(forKey: "taskArray") as? [Assignment]
}
override init() {
super.init()
}
convenience init(deptName: String, managerName: String, task: [Assignment]?) {
self.init()
self.deptName = deptName
self.managerName = managerName
self.task = task
}
}
class Assignment: NSObject, NSCoding {
var title = ""
var notes = ""
func encode(with coder: NSCoder) {
// Methods
coder.encode(title, forKey: "Title")
coder.encode(notes, forKey: "notepad")
}
required init(coder aDecoder: NSCoder) {
// Methods
title = aDecoder.decodeObject(forKey: "Title") as! String
notes = aDecoder.decodeObject(forKey: "notepad") as! String
super.init()
}
override init() {
super.init()
}
convenience init(title: String, notes: String) {
self.init()
self.title = title
self.notes = notes
}
}
// Create some data for testing
let assignment1 = Assignment(title: "title 1", notes: "notes 1")
let assignment2 = Assignment(title: "title 2", notes: "notes 2")
let myDepartment = Department(deptName: "My Dept", managerName: "My Manager", task: [assignment1, assignment2])
// Try archive and unarchive
do {
// Archive
let data = try NSKeyedArchiver.archivedData(withRootObject: myDepartment, requiringSecureCoding: false)
print ("Bytes in archive: \(data.count)")
// Unarchive
let obj = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! Department
// Print the contents of the unarchived object
print("Department: \(obj.deptName) Manager: \(obj.managerName)")
if let task = obj.task {
for i in 0...task.count-1 {
print("Task: \(task[i].title) \(task[i].notes)")
}
}
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
Enjoy

Get "does not implement methodSignatureForSelector" when try to store Array in NSUserDefaults,Swift?

I try to store Array of objects in NSUserDefaults.
I have following snippets of code:
var accounts = MyAccounts()
var array:Array<MyAccounts.MyCalendar> = accounts.populateFromCalendars()
NSUserDefaults.standardUserDefaults().
setObject(array, forKey: "test_storeAccounts_array") // <- get error here
NSUserDefaults.standardUserDefaults().synchronize()
But I get Exception:
does not implement methodSignatureForSelector: -- trouble ahead
my class structure:
class MyAccounts {
/* ... */
class MyCalendar {
var title:String?
var identifier:String?
var email:String?
var calType:String?
var isActive:Bool?
var isMainAcount:Bool?
init(){}
}
}
Any ideas?
Make sure your class inherits from NSObject
class MyAccounts:NSObject {
/* ... */
class MyCalendar {
var title:String?
var identifier:String?
var email:String?
var calType:String?
var isActive:Bool?
var isMainAcount:Bool?
init(){}
}
}
I was getting this exception in Swift 3.0. In my case, my model class was not inherit from NSObject base class. just inherit my class from NSObject base class and implements NSCoding protocol (if your container array has custom objects)
class Stock: NSObject, NSCoding {
var stockName: String?
override init() {
}
//MARK: NSCoding protocol methods
func encode(with aCoder: NSCoder){
aCoder.encode(self.stockName, forKey: "name")
}
required init(coder decoder: NSCoder) {
if let name = decoder.decodeObject(forKey: "name") as? String{
self.stockName = name
}
}
func getStockDataFromDict(stockDict stockDict:[String:AnyObject]) -> Stock {
if let stockName = stockDict["name"] {
self.stockName = stockName as? String
}
return self
}
}
In Swift 2, I experienced similar error while using the Notification Pattern within a custom class. Note that when the same notification(Observe) is implemented in a ViewController class , it doesn't complain. Its only with the custom class, created from a Swift file without subclassing this error was thrown
class myClass : NSObject {
override init(){
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("functionCall:"), name: "NotificationName", object: nil)
}
//Implement function
func functionCall(notification: NSNotification) {
//Extract the object and implement the function
}
}
You need to convert the class into NSData first. Something like this:
var data = NSKeyedArchiver.archivedDataWithRootObject(accounts.populateFromCalendars())
var userDefaults = NSUserDefaults.standardUserDefaults();
userDefaults.setObject(data, forKey: "test_storeAccounts_array");