How to save and retrieve class object from NSUserDefaults swift? - arrays

I'm trying to save my array to NSUserDefaults. But my Array exist with struct.
struct MyData {
var Text = String()
var Number = Int()
}
var Data = [MyData]()
I found this and I tried to do this;
let Data2 = NSKeyedArchiver.archivedDataWithRootObject(Data)
But this gives me this: Cannot invoke 'archivedDataWithRootObject' with an argument list of type '([(TableViewController.MyData)])'
Any advice?

Swift structs are not classes, therefore they don't conform to AnyObject protocol.
And Syntax for archivedDataWithRootObject is:
class func archivedDataWithRootObject(rootObject: AnyObject) -> NSData
Which means it only accept AnyObject type object and struct doesn't conform AnyObject protocol so you can not use struct here.
So just change struct to class and it will work fine.
UPDATE:
This way you can store it into NSUserDefaults.
Tested with playGround
import UIKit
class Person: NSObject, NSCoding {
var name: String!
var age: Int!
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String
self.age = decoder.decodeObjectForKey("age") as! Int
}
convenience init(name: String, age: Int) {
self.init()
self.name = name
self.age = age
}
func encodeWithCoder(coder: NSCoder) {
if let name = name { coder.encodeObject(name, forKey: "name") }
if let age = age { coder.encodeObject(age, forKey: "age") }
}
}
var newPerson = [Person]()
newPerson.append(Person(name: "Leo", age: 45))
newPerson.append(Person(name: "Dharmesh", age: 25))
let personData = NSKeyedArchiver.archivedDataWithRootObject(newPerson)
NSUserDefaults().setObject(personData, forKey: "personData")
if let loadedData = NSUserDefaults().dataForKey("personData") {
loadedData
if let loadedPerson = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? [Person] {
loadedPerson[0].name //"Leo"
loadedPerson[0].age //45
}
}

Related

extend an Array of Dictionary<String, Any> Swift 3

var dicts = [["key1": "value1", "key2": "value2"]]
dicts.values(of: "key1") // prints - value1
I am working on a project where I want to store the array of dictionary and then fetch the data from there on condition if array of dictionary contains the particular value.
Swift 3.0
You can try this way.
var dicts:[[String:Any]] = []
var check:Bool = false
dicts = [["search_date": "17/03/17", "search_title": ""],["search_date": "17/02/19", "search_title": "parth"],["search_date": "20/02/19", "search_title": "roy"]]
for item in dicts {
if let title = item["search_title"] as? String {
if title == "parth" {
check = true
break
}else {
check = false
}
}
else {
check = false
}
}
print(check)
We can Use Model to solve the Problem
class Person: NSObject, NSCoding {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.age = decoder.decodeInteger(forKey: "age")
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(age, forKey: "age")
}
}
Class
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// setting a value for a key
let newPerson = Person(name: "Joe", age: 10)
var people = [Person]()
people.append(newPerson)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)
UserDefaults.standard.set(encodedData, forKey: "people")
// retrieving a value for a key
if let data = UserDefaults.standard.data(forKey: "people"),
let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Person] {
myPeopleList.forEach({print( $0.name, $0.age)}) // Joe 10
} else {
print("There is an issue")
}
}
}
All Thanks to Leo Dabus
[Link] (https://stackoverflow.com/a/37983027/3706845)
Your question is very vague. But what I understood is that you want to filter the array of dictionaries so it only contains dictionaries that have a certain value, and this can be done this way:
let filteredDicts = dicts.filter({ $0.values.contains("value2") })

Swift NSCoding - How to read an encoded array

I'm using Swift playground to write a parent-child relationship using NSCoding.
The relationship can be described as:
One Author can write many Books
However, it causes an error when I try to save the collection of books;
Playground execution aborted: error: Execution was interrupted,
reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). The
process has been left at the point where it was interrupted, use
"thread return -x" to return to the state before expression
evaluation.
My Swift playground code is as follows;
// Author model
class Author: NSObject, NSCoding
{
var name: String
var books:[Book] = [Book]()
init(name:String, books:[Book]?) {
self.name = name
guard let bookList = books else {
return
}
self.books = bookList
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey:"name") as! String
let books = aDecoder.decodeObject(forKey:"books") as! [Book]
self.init(name:name, books:books)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey:"name")
aCoder.encode(books, forKey:"books")
}
}
// Book model
class Book: NSObject, NSCoding {
var title: String
var author: Author?
init(title:String, author: Author?) {
self.title = title
self.author = author
}
public convenience required init?(coder aDecoder: NSCoder) {
let title = aDecoder.decodeObject(forKey: "title") as! String
let author = aDecoder.decodeObject(forKey: "author") as! Author
self.init(title:title, author:author)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(author, forKey: "author")
}
}
// Create the data
var author:Author = Author(name: "Tom Clancy", books: nil)
let book0 = Book(title: "The Hunt for Red October", author: author)
let book1 = Book(title: "Red Storm Rising", author: author)
author.books.append(contentsOf: [book0, book1])
// -----------
// Save data
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as URL
let writePath: URL = url.appendingPathComponent("archive.plist")
print("Attempting to save to: \(writePath)")
let saveData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: saveData)
archiver.encode(books, forKey:"books")
archiver.encode(author, forKey: "author")
//NSKeyedArchiver.archiveRootObject(books, toFile: writePath.path)
archiver.finishEncoding()
_ = saveData.write(to: writePath, atomically: true)
// -----------
// Load data
print("Attempting to load from: \(writePath)")
if FileManager.default.fileExists(atPath: writePath.path) {
if let saveData = try? Data(contentsOf: writePath) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: saveData)
// Crash occurs here
var authorData = unarchiver.decodeObject(forKey: "author") as! Author
print (authorData.name)
}
} else {
print("No data archive exists")
}
So my question is: What is causing the error and how can I rectify the issue?
Many thanks
I was able to solve this problem by refactoring my code.
class Author: NSObject, NSCoding
{
var name: String
var books:[Book] = [Book]()
init(name:String, books:[Book]?) {
self.name = name
guard let bookList = books else {
return
}
self.books = bookList
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey:"name") as! String
let books = aDecoder.decodeObject(forKey:"books") as! [Book]
self.init(name:name, books:books)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey:"name")
aCoder.encode(books, forKey:"books")
}
}
class Book: NSObject, NSCoding {
var title: String
var author: Author
init(title:String, author: Author) {
self.title = title
self.author = author
}
public convenience required init?(coder aDecoder: NSCoder) {
let title = aDecoder.decodeObject(forKey: "title") as! String
let author = aDecoder.decodeObject(forKey: "author") as! Author
self.init(title:title, author:author)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(author, forKey: "author")
}
}
let author:Author = Author(name: "Tom Clancy", books: nil)
let books = [
Book(title: "The Hunt for Red October", author: author)
, Book(title: "Red Storm Rising", author: author)
]
//author.books.append(contentsOf: books)
// -----------
// Save data
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as URL
let writeFile: URL = url.appendingPathComponent("books.data")
print ("Attempting to write to: \(writeFile.path)")
NSKeyedArchiver.archiveRootObject(books, toFile: writeFile.path)
if let bookData = NSKeyedUnarchiver.unarchiveObject(withFile: writeFile.path) as? [Book] {
for book in bookData {
print ("\(book.title) - \(book.author.name)")
}
}
This seems to work.
Issue closed

How to Save complex Arrayobjects to device using NSKeyedArchiver swift

I want to save an array of any class (e.g let array = SymptomsModel) type into device using NSKeyedArchiver in swift .
I Know how to save an array if SymptomsModel class contains all variables with primitive data types , but don't know how to save it if also contains an array of any other class as its property
Below I have explained my problem with the help of example , please go through it and provide solution.
I have a class
class SymptomsModel: NSObject, NSCoding ,ResponseJSONObjectSerializable {
var slug:String?
var name:String?
var images:[Sym_images]?
var videos:[Sym_videos]?
struct Keys {
static let Name = "name"
static let Slug = "slug"
static let Images = "images"
static let Videos = "videos"
}
required init(json:SwiftyJSON.JSON) {
self.slug = json["slug"].string
self.name = json["name"].string
self.images = [Sym_images]()
if let imagesJSON = json["images"].array {
for(imagesJSON) in imagesJSON {
if let newImages = Sym_images(json: imagesJSON){
self.images?.append(newImages)
}
}
}
self.videos = [Sym_videos]()
if let videosJSONArray = json["videos"].array {
for(videosJSON) in videosJSONArray {
if let newVideos = Sym_videos(json: videosJSON){
self.videos?.append(newVideos)
}
}
}
}
init(dictionary: [String : AnyObject]) {
self.name = dictionary[Keys.Name] as? String
self.slug = dictionary[Keys.Slug] as? String
self.images = dictionary[Keys.Images] as? [Sym_acc_images_objects]
self.videos = dictionary[Keys.Videos] as? [Sym_acc_videos_objects]
}
func encodeWithCoder(archiver: NSCoder) {
archiver.encodeObject(name, forKey: Keys.Name)
archiver.encodeObject(slug, forKey: Keys.Slug)
archiver.encodeObject(images, forKey: Keys.Images)
archiver.encodeObject(videos, forKey: Keys.Videos)
}
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_images_objects]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_videos_objects]
}
and a PersistanceManager class to save the data with NskeyedArchiver as
class PersistenceManager {
class private func documentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentDirectory = paths[0] as String
return documentDirectory
}
class func saveNSArray(arrayToSave: NSArray, key: String) {
print(" saveNSArray key : \(key)")
let file = documentsDirectory().stringByAppendingPathComponent(key)
NSKeyedArchiver.archiveRootObject(arrayToSave, toFile: file)
}
class func loadNSArray(path: String) -> NSArray? {
print(" loadNSArray key : \(path)")
let file = documentsDirectory().stringByAppendingPathComponent(path)
let result = NSKeyedUnarchiver.unarchiveObjectWithFile(file)
return result as? NSArray
}
}
And Here is my implimentation of saving and then retreiving the array
class ViewController: UIViewController{
var ArraySymptom = [SymptomsModel]()
override func viewDidLoad() {
super.viewDidLoad()
ArraySymptom = loadArray()
//saving data in device
PersistenceManager.saveNSArray(ArraySymptom, key: "Symptom")
//loading data from device
if let value = PersistenceManager.loadNSArray("Symptom") as? [SymptomsModel] {
let images = value[0].images
print("images : \(images)")
let slug = value[0].slug
print("slug : \(slug)")
}
}
Here am able to get the value of slug but not able to fetch images value.
It might be happening because slug is of String type and Images is of Custom Class type .
Please suggest me the way i can get it done .
Is is possible to save these type of arrays with NSKeyedArchiver , so that i can access images value just by retreiving ArraySymptom from device.
Silly mistake it was
I was getting nil in Images because it was decoded with wrong key , it was copy paste mistake
The Error was in this function..
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_videos]
}
And the correct decoding must be
self.images = unarchiver.decodeObjectForKey(Keys.Images) as?
[Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Videos) as?
[Sym_videos]

How to check equality of object properties in an array of objects. Swift

I have a class called Movie, which as of now, only has a string property called movieTitle.
I have an array of Movie, and using the .contains method returns false even when an object with the same title is in the array. Interestingly enough, .contains works in a playground I made but not in an app setting.
Thanks for the help! I'm fairly new to the programing game so if you and ELI5 things, that would be great!
Here's a snippet of the code I have. What ends up happening, is it just keeps adding the same 10 entries onto the array.
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String: AnyObject]
if let movieSearch = json["Search"] as? [[String: AnyObject]] {
for movie in movieSearch {
if let title = movie["Title"] as? String {
let newMovie = Movie(movieTitle: title)!
if (!self.movieList.contains(newMovie)) {
self.movieList.append(newMovie)
}
self.tableView.reloadData()
}
}
}
}catch {
print("Error with Json: \(error)")
}
Movie Class
import UIKit
class Movie: NSObject, NSCoding {
// MARK: Properties
struct PropertyKey {
static let movieTitleKey = "title"
}
// MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Movies")
var movieTitle: String
// MARK: Initialization
init?(movieTitle: String) {
// Initialize stored properties.
self.movieTitle = movieTitle
super.init()
// Initialization should fail if there is no itemName
if movieTitle.isEmpty {
return nil
}
}
// MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(movieTitle, forKey: PropertyKey.movieTitleKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let title = aDecoder.decodeObject(forKey: PropertyKey.movieTitleKey) as! String
//Must call designated initializer.
self.init(movieTitle: title)
}
}
// MARK: Equatable
func ==(lhs: Movie, rhs: Movie) -> Bool { // Implement Equatable
return lhs.movieTitle == rhs.movieTitle
}
What works in playgrounds
class Movie: NSObject {
var movieTitle: String
init?(movieTitle: String) {
// Initialize stored properties.
self.movieTitle = movieTitle
super.init()
// Initialization should fail if there is no itemName
if movieTitle.isEmpty {
return nil
}
}
}
var movieList = [Movie]()
var movie1 = Movie(movieTitle: "Batman")
var movie2 = Movie(movieTitle: "Batman")
var movie3 = Movie(movieTitle: "Superman")
movieList.append(movie1!)
movieList.append(movie2!)
movieList.contains(movie1!) // Returns True
movieList.contains(movie3!) // Returns False
Because your Movie class (why is it a class?) inherits from NSObject (why?), it inherits NSObject's conformance of the Equatable protocol, with the NSObject implementation of ==. By default, this does identity comparison (comparing references), rather than value comparison.
Here's an example:
let movie1 = Movie(movieTitle: "Batman")
let movie2 = Movie(movieTitle: "Batman")
var movieList = [Movie1]
movieList.contains(movie1!) // True, because movie1 was added in
movieList.contains(movie2!) // False, movie2 was never added
Since Movie doesn't override == with an implementation that compares its value(s) (such as movieTitle), it defers to the default implementation, which is comparing the references. Even though movie2 has the same value, it's a distinct object with its own (separate) memory location. Thus, the identity comparison fails, and it's not found.
To solve this implement == to return true iff all the fields of Movie match up. What you're trying to do may be better off being implemented with structs, however.
you should try with this way.
var filtered = [Movie]()
filtered = movieList.filter({$0.movieTitle == "Superman"})
if filtered.count == 1 {
//so,"Superman" movie contained in array..
}
let me know the results... thanks.
Just try this code it works perfectly.
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String: AnyObject]
if let movieSearch = json["Search"] as? [[String: AnyObject]] {
for movie in movieSearch {
if let title = movie["Title"] as? String {
let newMovie = Movie(movieTitle: title)!
let movieTitles = (self.movieList as NSArray).value(forKeyPath: "movieTitle") as? [String]
if movieTitles == nil || movieTitles!.contains(title) == false {
self.movieList.append(newMovie)
}
self.tableView.reloadData()
}
}
}
}catch {
print("Error with Json: \(error)")
}
Try overriding isEqual method of NSObject since it is already conforming Equatable protocol. You can test the code below in a playground. Hope it helps.
class Movie: NSObject {
var movieTitle: String
init?(movieTitle: String) {
// Initialize stored properties.
self.movieTitle = movieTitle
super.init()
// Initialization should fail if there is no itemName
if movieTitle.isEmpty {
return nil
}
}
override func isEqual(_ object: Any?) -> Bool {
guard let theMovie = (object as? Movie) else { return false }
return movieTitle == theMovie.movieTitle
}
}
var movieList = [Movie]()
func appendToList(newMovie: Movie) {
if (!movieList.contains(newMovie)) {
movieList.append(newMovie)
}
}
var movie1 = Movie(movieTitle: "Batman")
var movie2 = Movie(movieTitle: "Batman")
var movie3 = Movie(movieTitle: "Superman")
appendToList(newMovie: movie1!)
movieList.count // count is 1
appendToList(newMovie: movie2!)
movieList.count // count is still 1 not incremented
movieList.contains(movie1!) // Returns true
movieList.contains(movie2!) // Returns true
movieList.contains(movie3!) // Returns false

Swift when appending custom class to array all previous values overwritten

I have a custom class called Subject, which I am trying to initialize versions of and append to a array, however when I append one to a array it overwrites all the Subjects all ready in there. For example this code
var things:[Subject] = []
things.append(Subject(initName: "1", initTeacher: "1", initClassroom: "1"))
things.append(Subject(initName: "2", initTeacher: "2", initClassroom: "2"))
print(things[0].name)
print(things[1].name)
is printing Optional("2") Optional("2") when it should be printing 'Optional("1") Optional("2")'
This is the code for my custom class
class Subject: NSManagedObject{
var name: String?
var teacher: String?
var classroom: String?
init(initName: String, initTeacher: String, initClassroom: String){
name = initName
teacher = initTeacher
classroom = initClassroom
}
func save() -> Bool{
if(name == "" || teacher == "" || classroom == ""){
return false
}else{
let appDelgate = UIApplication.sharedApplication().delegate as? AppDelegate
let managedContext = appDelgate?.managedObjectContext
let entity = NSEntityDescription.entityForName("Subject", inManagedObjectContext: managedContext!)
let subject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
subject.setValue(name, forKey: "name")
subject.setValue(teacher, forKey: "teacher")
subject.setValue(classroom, forKey: "classroom")
do{
try managedContext?.save()
}catch let error as NSError{
print("Failed because of \(error)")
}
return true
}
}
func edit(newName: String, newTeacher: String, newClassroom: String) -> Bool{
let appDelgate = UIApplication.sharedApplication().delegate as? AppDelegate
let managedContext = appDelgate?.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Subject")
fetchRequest.predicate = NSPredicate(format: "name = %#", name!)
do{
let fetchResults = try managedContext?.executeFetchRequest(fetchRequest)
let editingSubject = fetchResults![0] as! NSManagedObject
editingSubject.setValue(newName, forKey: "name")
editingSubject.setValue(newTeacher, forKey: "teacher")
editingSubject.setValue(newClassroom, forKey: "classroom")
do{
try managedContext?.save()
return true
}catch{
return false
}
}catch{
return false
}
}}
Thanks for any help

Resources