How to store an array in NSUserDefaults? swift 3 - ios

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

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")
}
}

Save Custom Dictionary to User default

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")
}
}

MBProgressHUD causing iOS application to crash (Swift 2.0)

I have a ViewController which saves user inputs to CoreData and after the save is attempted displaying MBProgressHUD to state if the save was successful or not.
I have an AddNewViewController class
class AddNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate {
#IBOutlet weak var inputErrorMessage: UILabel!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var dayPicker: UIPickerView!
#IBOutlet weak var durationPicker: UIPickerView!
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var amountTextField: UITextField!
#IBOutlet weak var notesTextField: UITextField!
//variable to contrain the origin view controller
var originVC: String?
// variables to hold user input
var name: String?
var amount: Double?
var notes: String?
var durationDay: Double?
var durationType: String?
// The days and duration options to display in the pickers
var durationPickerDataSource = ["Day(s)","Week(s)","Month(s)","Year(s)"];
var dayPickerDataSource = ["1","2","3","4","5","6","7","8","9","10","11","12"];
#IBAction func saveButton(sender: AnyObject) {
CoreDataStatic.data.saveIncomeBudgetAndExpenses(originVC!, name: name!, amount: amount, durationDay: durationDay!, durationType: durationType!, notes: notes!)
}
/**
The number of columns in the picker view.
*/
func numberOfComponentsInPickerView(dayPickerView: UIPickerView) -> Int {
return 1
}
/**
The number of items in the picker view. Equal to the number of days(12) and duration options(4) .
*/
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == durationPicker {
return durationPickerDataSource.count;
}
else {
return dayPickerDataSource.count;
}
}
/**
Gets the titles to use for each element of the picker view.
*/
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == durationPicker{
durationType = durationPickerDataSource[row]
return durationType
}
else {
durationDay = Double(dayPickerDataSource[row])
return dayPickerDataSource[row]
}
}
/**
Display acknowledgement if the Income, Budget or Fixed Expense saved.
*/
func displayMessage(origin: String) {
var message : String
//Changes the message depending on what the user was trying to save.
if CoreDataStatic.data.saved == true {
message = "\(origin) saved!"
}
else if CoreDataStatic.data.saved == false {
message = "Error: \(origin) failed to save!"
}
else {
message = "Error!"
}
print(message)
//displays acknowledgement for 2 seconds.
/*let acknowledgement = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
acknowledgement.mode = MBProgressHUDMode.Text
acknowledgement.label.text = message
acknowledgement.hideAnimated(true, afterDelay: 2)*/
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.durationPicker.dataSource = self;
self.durationPicker.delegate = self;
self.dayPicker.dataSource = self;
self.dayPicker.delegate = self;
}
A CoreData class:
struct CoreDataStatic {
static let data = CoreData()
}
public class CoreData {
var appDel : AppDelegate
//Manage a collection of managed objects.
let context : NSManagedObjectContext
//Describes an entity in Core Data.
let incomeEntity : NSEntityDescription
let budgetEntity : NSEntityDescription
let fixedExpenseEntity : NSEntityDescription
//Retrieve data from Core Data with the entity 'Scores'.
let income = NSFetchRequest(entityName: "Income")
let budget = NSFetchRequest(entityName: "Budget")
let fixedExpense = NSFetchRequest(entityName: "FixedExpenses")
//Set the key that needs updating which is always 'score'
let nameKeyToUpdate = "name"
let amountDayKeyToUpdate = "amountDay"
let amountWeekKeyToUpdate = "amountWeek"
let amountMonthKeyToUpdate = "amountMonth"
let amountYearKeyToUpdate = "amountYear"
let durationDayKeyToUpdate = "durationDay"
let durationTypeKeyToUpdate = "durationType"
let notesKeyToUpdate = "notes"
var saved : Bool?
func saveIncomeBudgetAndExpenses(origin: String, name: String, amountDay: Double, amountWeek: Double, amountMonth: Double, amountYear: Double, durationDay: Double, durationType: String, notes: String) {
//saving in enity depending on origin view controller
let entity : NSEntityDescription
if origin == "Income" {
entity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
}
else if origin == "Budget" {
entity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
}
else {
entity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
}
let saveNew = NSManagedObject(entity: entity,
insertIntoManagedObjectContext:context)
// add user input to the relevant entity
saveNew.setValue(name, forKey: nameKeyToUpdate)
saveNew.setValue(amountDay, forKey: amountDayKeyToUpdate)
saveNew.setValue(amountWeek, forKey: amountWeekKeyToUpdate)
saveNew.setValue(amountMonth, forKey: amountMonthKeyToUpdate)
saveNew.setValue(amountYear, forKey: amountYearKeyToUpdate)
saveNew.setValue(durationDay, forKey: durationDayKeyToUpdate)
saveNew.setValue(durationType, forKey: durationTypeKeyToUpdate)
saveNew.setValue(notes, forKey: notesKeyToUpdate)
do {
try context.save()
print("saved")
saved = true
}
catch _ {
print("didnt save")
saved = false
}
AddNewViewController().displayMessage(origin)
}
init(){
appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
context = appDel.managedObjectContext
incomeEntity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
budgetEntity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
fixedExpenseEntity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
}
}
This code runs and as expected however when the commented out section in the displayMessage() function is uncommented I get the following error:
"fatal error: unexpectedly found nil while unwrapping an Optional value"
due to the line self.durationPicker.dataSource = self; in the override viewDidLoad()
Any help would be appreciated.
Note* if i call the displayMessage() within the saveButton function the code works so unsure why it isn't working when calling the message from the CoreData class.
I am unsure if this is the correct way about it but i found a fix.
a variable (bool) was created called attemptSave which is defaulted to false.
within the saveIncomeBudgetAndExpenses try and catch, the attemptSave is changed to true.
The displayMessage() function is now called within both button clicks using an if statement to check if an attemptSave is yes, if so, call function.

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