Save Custom Dictionary to User default - ios

struct AllItemsData {
var DSTBID: String!
var CCAS: String!
var BCAS: String!
}
This is my structure from which I create an array of type
AllItemsDataArray = [AllItemsData()]
After adding some data, now I want to store it into user defaults.
I did it this way:
AllItemsDataArray.removeFirst()
let archivedArray = NSKeyedArchiver.archivedData(withRootObject: All_ItemsDataArray)
print(archivedArray)
preference.set(archivedArray, forKey: allItemsDataKey)
But the error is like:
ios[1540:537869] -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x15d1e200
The values in the array just before the crash happens is:
AllItemsData(DSTBID: GGGGGGGGGGGG, CCAS: , BCAS: )
AllItemsData(DSTBID: HHHHHHHHHHHH, CCAS: , BCAS: )

You need to implement NSCoding protocol for you object, and use class instead of struct
class AllItemsData: NSObject, NSCoding {
var DSTBID: String!
var CCAS: String!
var BCAS: String!
required convenience init?(coder decoder: NSCoder) {
let DSTBID = decoder.decodeObjectForKey("DSTBID") as? String,
let CCAS = decoder.decodeObjectForKey("CCAS") as? String,
let BCAS = decoder.decodeObjectForKey("BCAS") as? [String]
self.init(
DSTBID: DSTBID,
CCAS: CCAS,
BCAS: BCAS
)
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.DSTBID, forKey: "DSTBID")
coder.encodeObject(self.CCAS, forKey: "CCAS")
coder.encodeInt(Int32(self.BCAS), forKey: "BCAS")
}
}

Related

How to add Values to NSObject Model Variables in Swift?

I have created separate NSObject class called ProfileModel
like below:
class ProfileModel : NSObject, NSCoding{
var userId : String!
var phone : String!
var firstName : String!
var email : String!
var profileImageUrl : String!
var userAddresses : [ProfileModelUserAddress]!
// Instantiate the instance using the passed dictionary values to set the properties values
init(fromDictionary dictionary: [String:Any]){
userId = dictionary["userId"] as? String
phone = dictionary["phone"] as? String
firstName = dictionary["firstName"] as? String
email = dictionary["email"] as? String
profileImageUrl = dictionary["profileImageUrl"] as? String
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]
{
var dictionary = [String:Any]()
if userId != nil{
dictionary["userId"] = userId
}
if phone != nil{
dictionary["phone"] = phone
}
if firstName != nil{
dictionary["firstName"] = firstName
}
if email != nil{
dictionary["email"] = email
}
if profileImageUrl != nil{
dictionary["profileImageUrl"] = profileImageUrl
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder)
{
userId = aDecoder.decodeObject(forKey: "userId") as? String
userType = aDecoder.decodeObject(forKey: "userType") as? String
phone = aDecoder.decodeObject(forKey: "phone") as? String
firstName = aDecoder.decodeObject(forKey: "firstName") as? String
email = aDecoder.decodeObject(forKey: "email") as? String
profileImageUrl = aDecoder.decodeObject(forKey: "profileImageUrl") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder)
{
if userId != nil{
aCoder.encode(userId, forKey: "userId")
}
if phone != nil{
aCoder.encode(phone, forKey: "phone")
}
if firstName != nil{
aCoder.encode(firstName, forKey: "firstName")
}
if email != nil{
aCoder.encode(email, forKey: "email")
}
if profileImageUrl != nil{
aCoder.encode(profileImageUrl, forKey: "profileImageUrl")
}
}
}
In RegistrationViewController I adding firstName value which i need to show in ProfileViewController How ?
In RegistrationViewController i am adding firstName and phone values which i need in ProfileViewController:
class RegistrationViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var firstNameTextField: FloatingTextField!
var userModel : ProfileModel?
override func viewDidLoad() {
let userID: String=jsonObj?["userId"] as? String ?? ""
self.userModel?.firstName = self.firstNameTextField.text
self.userModel?.phone = phoneTextField.text
}
}
This is ProfileViewController here in name and number i am not getting firstName and phone values why?:
class ProfileViewController: UIViewController {
#IBOutlet var name: UILabel!
#IBOutlet var number: UILabel!
var userModel : ProfileModel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
name.text = userModel?.firstName
number.text = userModel?.phone
}
}
PLease help me with code.
You cannot set firstName or phone to the userModal which is nil. First you should create an instance, and then you can pass it through your controllers. We should change code step by step:
class ProfileModel {
var userId : String?
var phone : String?
var firstName : String?
var email : String?
var profileImageUrl : String?
var userAddresses : [ProfileModelUserAddress]?
init() {}
}
Second, you need to reach ProfileModel instance from both of your ViewController classes. For this, you can create a singleton class:
class ProfileManager {
static var shared = ProfileManager()
var userModel: ProfileModel?
private init() {}
}
Then you can reach it from both of your ViewControllers:
class RegistrationViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var firstNameTextField: FloatingTextField!
override func viewDidLoad() {
super.viewDidLoad()
let userModel = ProfileModel()
userModel.firstName = self.firstNameTextField.text
ProfileManager.shared.userModel = userModel
}
}
Other VC:
class ProfileViewController: UIViewController {
#IBOutlet var name: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let userModel = ProfileManager.shared.userModel,
let firstName = userModel.firstName {
name.text = firstName
}
}
}
Modify it as you wanted.

Having Issue with store non property object

I am trying to save store a custom file using UserDefaults, but my app crashesh due to :
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is my code , in my custom class I declare an empty array
class AppDefualts: NSObject {
var downloadedURLArray = [DownloadFile]() //Declare an empty array
override init() {
super.init()
downloadedURLArray = loadStoredURL()
}
//Store data
func addStored(file:DownloadFile) {
//Add URL to array and save it
downloadedURLArray.append(file)
let data = NSKeyedArchiver.archivedData(withRootObject: downloadedURLArray)
UserDefaults.standard.set(data, forKey: "storedURL")
}
//Load:
func loadStoredURL() -> Array<DownloadFile> {
let data = UserDefaults.standard.data(forKey: "storedURL")
downloadedURLArray = NSKeyedUnarchiver.unarchiveObject(with: data!) as? [DownloadFile] ?? [DownloadFile]()
return downloadedURLArray
}
Any help would be great
EDIT 1 :
I added NSCoding protocols :
func encode(with aCoder: NSCoder) {
aCoder.encode(downloadedURLArray, forKey: "storedURL")
}
required init?(coder aDecoder: NSCoder) {
downloadedURLArray = aDecoder.decodeObject(forKey: "storedURL") as! [DownloadFile]
}
now app crashes due to this :
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -encodeObject:forKey:
cannot be sent to an abstract object of class NSCoder: Create a
concrete instance!'
If you want to store the custom object in UserDefault then you have to do Encoding/Decoding. Your custom object suppose to confirm to the NSCoding Protocol.
Store into UserDefault : You have to convert you object in to NSData and store them into UserDefault by using NSKeyedArchiver
Get from UserDefault : Get the NSData from USerDefault and convert into Custom object by using NSKeyedUnArchiver
Refer the link to convert custom object into NSData and vice versa
Example: custom object which confirms NSCoding protocol
class Book: NSObject, NSCoding {
var title: String
var author: String
var pageCount: Int
var categories: [String]
var available: Bool
// Memberwise initializer
init(title: String, author: String, pageCount: Int, categories: [String], available: Bool) {
self.title = title
self.author = author
self.pageCount = pageCount
self.categories = categories
self.available = available
}
// MARK: NSCoding
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObjectForKey("title") as? String,
let author = decoder.decodeObjectForKey("author") as? String,
let categories = decoder.decodeObjectForKey("categories") as? [String]
else { return nil }
self.init(
title: title,
author: author,
pageCount: decoder.decodeIntegerForKey("pageCount"),
categories: categories,
available: decoder.decodeBoolForKey("available")
)
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.title, forKey: "title")
coder.encodeObject(self.author, forKey: "author")
coder.encodeInt(Int32(self.pageCount), forKey: "pageCount")
coder.encodeObject(self.categories, forKey: "categories")
coder.encodeBool(self.available, forKey: "available")
}
}

NSKeyedArchiver.archivedData does not work in Swift 3 iOS

When try to encode my custom object in iOS swift get this error from Xcode 8.3
unrecognized selector sent to instance 0x60800166fe80
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
And my code like this:
import UIKit
import Foundation
class Place: NSObject {
func setCustomObject(CustomObject obj:Any,Key key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
}
Here's an example how to make an object to conform to NSCoding. Basically you need to provide implementation of two methods - required convenience init?(coder decoder: NSCoder) and encode(with aCoder: NSCoder)
class Book: NSObject, NSCoding {
var title: String?
var pageCount: Int?
// Memberwise initializer
init(title: String,pageCount: Int) {
self.title = title
self.pageCount = pageCount
}
// MARK: NSCoding
// Here you will try to initialize an object from archve using keys you did set in `encode` method.
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObject(forKey: "title") as? String else { return nil }
self.init(title: title, pageCount: decoder.decodeInteger(forKey: "pageCount"))
}
// Here you need to set properties to specific keys in archive
func encode(with aCoder: NSCoder) {
aCoder.encode(self.title, forKey: "title")
aCoder.encodeCInt(Int32(self.pageCount), forKey: "pageCount")
}
}
Also I would recommend changing your setCustomObject method to this:
func setCustomObject(obj:NSCoding, key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
This way compiler prevent you passing NSKeyedArchiver an object that does not conform to NSCoding protocol.
If you don't want to provide all properties in the init method you can use default values:
init(title : String? = nil, pageCount: Int? = nil){
self.title = title
self.pageCount = pageCount
}
Now you can just init your object without any properties. Like that Book()
Here is the solutions, you have to implement the two methods
Encode Method
func encode(with aCoder: NSCoder)
Decoding method
required init?(coder aDecoder: NSCoder)
Complete Example code
class User: NSObject , NSCoding
{
var userID : Int = 0
var name : String = ""
var firstName : String = ""
var lastName : String = ""
var username : String = ""
var email : String = ""
override init(){
super.init();
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.userID, forKey: "id");
aCoder.encode(self.firstName, forKey: "first_name");
aCoder.encode(self.lastName, forKey: "last_name");
aCoder.encode(self.name, forKey: "name");
aCoder.encode(self.username,forKey: "username");
aCoder.encode(self.email, forKey: "email");
}
required init?(coder aDecoder: NSCoder) {
super.init()
self.userID = aDecoder.decodeInteger(forKey: "id");
self.firstName = aDecoder.decodeObject(forKey: "first_name") as! String;
self.lastName = aDecoder.decodeObject(forKey: "last_name") as! String;
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.username = aDecoder.decodeObject(forKey: "username") as! String
self.email = aDecoder.decodeObject(forKey: "email") as! String;
}
init(data : [String: AnyObject]) {
super.init()
self.userID = String.numberValue(data["user_id"]).intValue;
self.firstName = String.stringValue(data["first_name"]);
self.lastName = String.stringValue(data["last_name"]);
self.email = String.stringValue(data["email"]);
self.username = String.stringValue(data["user_name"]);
}
class func loadLoggedInUser() -> User {
if let archivedObject = UserDefaults.standard.object(forKey:"CurrentUserAcc"){
if let user = NSKeyedUnarchiver.unarchiveObject(with: (archivedObject as! NSData) as Data) as? User {
return user;
}
}
return User()
}
func saveUser(){
let archivedObject : NSData = NSKeyedArchiver.archivedData(withRootObject: self) as NSData
UserDefaults.standard.set(archivedObject, forKey: "CurrentUserAcc");
UserDefaults.standard.synchronize();
}
func deleteUser(){
UserDefaults.standard.set(nil, forKey: "CurrentUserAcc")
UserDefaults.standard.synchronize();
}
}

How to store an array in NSUserDefaults? swift 3

I am trying to store an array in userDefaults but i am getting this error when i run my app:
'Attempt to insert non-property list object (
"Morning_Star_2.Event(title: Optional(\"test title\"), location: Optional(\"Test Location\"))"
) for key test'
Here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtTitle: UITextField!
#IBOutlet weak var txtLocation: UITextField!
#IBOutlet weak var txtDate: UITextField!
#IBOutlet weak var txtTime: UITextField!
var eventsArray = [Event]()
#IBAction func btnSave() {
let savedEvents = UserDefaults.standard.object(forKey: "test")
let event = Event(eventTitle: txtTitle.text!, eventLocation: txtLocation.text!)
if let tempEvents = savedEvents {
eventsArray = tempEvents as! [Event]
eventsArray.append(event)
}
else {
let event = Event(eventTitle: txtTitle.text!, eventLocation: txtLocation.text!)
eventsArray.append(event)
}
UserDefaults.standard.set(eventsArray, forKey: "test")
//print(eventsArray)
}
}
To store custom objects in UserDefaults, your objects must
Inherit from NSObject
Conform to the NSCoding protocol
An example of the class implementation could look like this:
class Event: NSObject, NSCoding {
private var eventTitle: String!
private var eventLocation: String!
init(eventTitle: String, eventLocation: String) {
self.eventTitle = eventTitle
self.eventLocation = eventLocation
}
override init() {
}
required convenience init?(coder aDecoder: NSCoder) {
self.init()
eventTitle = aDecoder.decodeObject(forKey: "eventTitle") as? String
eventLocation = aDecoder.decodeObject(forKey: "eventLocation") as? String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(eventTitle, forKey: "eventTitle")
aCoder.encode(eventLocation, forKey: "eventLocation")
}
}
Now, you can only store certain objects in UserDefaults. Luckily the type Data can be stored in UserDefaults. You then need to convert your array to Data, and then store it.
let data = NSKeyedArchiver.archivedData(withRootObject: eventsArray)
// This calls the encode function of your Event class
UserDefaults.standard.set(data, forKey: "test")
/* This saves the object to a buffer, but you will need to call synchronize,
before it is actually saved to UserDefaults */
UserDefaults.standard.synchronize()
When you retrieve the data it comes back as Any?, which will have to be casted to your object:
if let data = UserDefaults.standard.object(forKey: "test") as? Data {
if let storedData = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Event] {
// In here you can access your array
}
}

Thread 1:EXC_BAD_INSTRUCTION error when saving NSMutableArray In custom object saved in NSUserDefaults in swift

I have created a custom object that consists of a string, int, UIImage. I tried to add a NSMutable array and when tried to run my project i got an error. "Thread 1:EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)".
how can I prevent this error and have this nsmutablearray in this class?
Here is my class:
class develop : NSObject, NSCoding {
var power: Int!
var name: String!
var image: UIImage!
var flag: UIImage!
var militaryName: String!
var relations: NSMutableArray!
override init() {
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(power, forKey:"power")
aCoder.encodeObject(name, forKey:"name")
aCoder.encodeObject(image, forKey:"image")
aCoder.encodeObject(flag, forKey: "flag")
aCoder.encodeObject(militaryName, forKey: "militaryName")
aCoder.encodeObject(relations, forKey: "relations")
}
required init(coder aDecoder: NSCoder) {
super.init()
power = aDecoder.decodeIntegerForKey("power")
name = aDecoder.decodeObjectForKey("name") as! String
image = aDecoder.decodeObjectForKey("image")as! UIImage
flag = aDecoder.decodeObjectForKey("flag") as! UIImage
militaryName = aDecoder.decodeObjectForKey("militaryName") as! String
relations = aDecoder.decodeObjectForKey("relations") as! NSMutableArray
}
}
here is the setting of the objects:
func setUpDefaults(){
//Afghanistan
Afghanistan.power = 0
Afghanistan.name = "Afghanistan"
Afghanistan.image = UIImage(named: "afghan_flag.jpg")!
Afghanistan.flag = UIImage(named: "afghan_flag.jpg")!
Afghanistan.militaryName = "Afghan Military"
Afghanistan.relations = [New_Zealand]
}
Here is the loading:
class Test:UIViewController {
#IBOutlet var Label: UILabel!
#IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
//Afganistan
setUpDefaults()
let developData = NSKeyedArchiver.archivedDataWithRootObject(Afghanistan)
NSUserDefaults().setObject(developData, forKey: "Afg")
if let loadedData = NSUserDefaults().dataForKey("Afg") {
if let loadedDevelop = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? develop{
Label.text = Afghanistan.militaryName
imageView.image = loadedDevelop.flag
}
}
}
}

Resources