I have a model class User that I want to save in UserDefaults
import UIKit
class User: NSObject {
var name:String!
var email:String!
var userId:String!
var phone:String!
var admin_status:String!
var social_code:String!
var token:String!
var otp:String!
var forget_otp:String!
var p_img:String!
var created:String!
var status:String!
static var currentUser:User = User()
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.email = aDecoder.decodeObject(forKey: "email") as! String
self.userId = aDecoder.decodeObject(forKey: "userId") as! String
self.phone = aDecoder.decodeObject(forKey: "phone") as! String
self.admin_status = aDecoder.decodeObject(forKey: "admin_status") as! String
self.social_code = aDecoder.decodeObject(forKey: "social_code") as! String
self.token = aDecoder.decodeObject(forKey: "token") as! String
self.otp = aDecoder.decodeObject(forKey: "otp") as! String
self.forget_otp = aDecoder.decodeObject(forKey: "forget_otp") as! String
self.p_img = aDecoder.decodeObject(forKey: "p_img") as! String
self.created = aDecoder.decodeObject(forKey: "created") as! String
self.status = aDecoder.decodeObject(forKey: "status") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(email, forKey: "email")
aCoder.encode(userId, forKey: "userId")
aCoder.encode(phone, forKey: "phone")
aCoder.encode(admin_status, forKey: "admin_status")
aCoder.encode(social_code, forKey: "social_code")
aCoder.encode(token, forKey: "token")
aCoder.encode(otp, forKey: "otp")
aCoder.encode(forget_otp, forKey: "forget_otp")
aCoder.encode(p_img, forKey: "p_img")
aCoder.encode(created, forKey: "created")
aCoder.encode(status, forKey: "status")
}
}
Code to save and get from UserDefaults
class func setUserDefault(ObjectToSave : AnyObject? , KeyToSave : String)
{
let defaults = UserDefaults.standard
if (ObjectToSave != nil)
{
defaults.set(ObjectToSave, forKey: KeyToSave)
}
UserDefaults.standard.synchronize()
}
class func getUserDefault(KeyToReturnValye : String) -> AnyObject?
{
let defaults = UserDefaults.standard
if let name = defaults.value(forKey: KeyToReturnValye)
{
return name as AnyObject
}
return nil
}
Saving
let user:User = User()
user.name = json["data"]["first_name"].string
user.email = json["data"]["email"].string
user.phone = json["data"]["phone"].string
user.social_code = json["data"]["social_code"].string
user.admin_status = json["data"]["admin_status"].string
user.otp = json["data"]["otp"].string
user.forget_otp = json["data"]["forget_otp"].string
user.p_img = json["data"]["p_img"].string
user.status = json["data"]["status"].string
user.userId = json["data"]["id"].string
user.created = json["data"]["created"].string
Utilities.setUserDefault(ObjectToSave: user, KeyToSave: "user")
I also tried this
let encodedData = NSKeyedArchiver.archivedData(withRootObject: user)
UserDefaults.standard.set(encodedData, forKey: "User")
but it crashes because of static var currentUser:User = User()
how to fix this ?
As for NSCoding: you have wrong method signature. Change
func encodeWithCoder(aCoder: NSCoder)
to
func encode(with aCoder: NSCoder)
Also, your object should explicitly conform to the NSCoding protocol:
class User: NSObject, NSCoding {...}
As for UserDefaults, you can not store custom objects in the UserDefaults, only NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary (or similar types in Swift), see documentation
Have you tried by replacing your this line,
static var currentUser:User = User()
with this line,
static var currentUser:User!
Related
How save tableview data in swift MVC?
How can i save data in table on another ViewController?
I have a custom class called ToDoItem.
func save() {
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: ToDoItem.self)
UserDefaults.standard.set(encodedData, forKey: "todosave")
UserDefaults.standard.synchronize()
}
//
func load() {
if let decoded = UserDefaults.standard.object(forKey: "todosave") {
let _ = NSKeyedUnarchiver.unarchiveObject(with: decoded as! Data) as! [ToDoItem]
}
}
I've tried saving it with NSUserDefaults but I get an error.
ToDoItem.swift
import Foundation
class ToDoItem: NSObject, NSCoding {
var id: String
var image: String
var title: String
var date: Date
init(id: String, image: String, title: String, date: Date) {
self.id = id
self.image = image
self.title = title
self.date = date
}
required convenience init(coder aDecoder: NSCoder) {
let id = aDecoder.decodeObject(forKey: "id") as! String
let image = aDecoder.decodeObject(forKey: "image") as! String
let title = aDecoder.decodeObject(forKey: "title") as! String
let date = aDecoder.decodeObject(forKey: "date") as! Date
self.init(id: id, image: image, title: title, date: date)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(image, forKey: "image")
aCoder.encode(title, forKey: "title")
aCoder.encode(date, forKey: "date")
}
}
I would suggest using Object Mapper and then you can convert the object to JSON, after convert you save it to User Defaults as normally with type Any. Then for loading data, you map the data from the User Defaults key that you saved before to your TODOItem.
The link for ObjectMapper: https://github.com/Hearst-DD/ObjectMapper
Also you can use this library for mapping different JSONs to your desired objects
I was going to use Core Data but thought this was easier but can't find anything online about how to store an array of Cell class as a variable in the Cell class itself.
class Cell: NSObject, NSCoding {
var name:String!
var tag:String?
var more:String?
var amount:String!
var balance:String!
var subCells:[Cell]?//THIS IS THE ONE I'M STUGGLING WITH
override init() {
super.init()
}
init(name: String, tag: String, more: String, amount: String, balance: String, subCells: [Cell] = [Cell]()) {
super.init()
self.name = name
self.tag = tag
self.more = more
self.amount = amount
self.balance = balance
self.subCells = subCells//THIS
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as! String
self.tag = decoder.decodeObject(forKey: "tag") as? String
self.more = decoder.decodeObject(forKey: "more") as? String
self.amount = decoder.decodeObject(forKey: "amount") as! String
self.balance = decoder.decodeObject(forKey: "balance") as! String
self.subCells = decoder.decodeObject(forKey: "subCells") as? [Cell]//THIS
}
func initWithCoder(decoder: NSCoder) -> Cell{
self.name = decoder.decodeObject(forKey: "name") as! String
self.tag = decoder.decodeObject(forKey: "tag") as? String
self.more = decoder.decodeObject(forKey: "more") as? String
self.amount = decoder.decodeObject(forKey: "amount") as! String
self.balance = decoder.decodeObject(forKey: "balance") as! String
self.subCells = decoder.decodeObject(forKey: "subCells") as? [Cell]//THIS
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.tag, forKey: "tag")
aCoder.encode(self.more, forKey: "more")
aCoder.encode(self.amount, forKey: "amount")
aCoder.encode(self.balance, forKey: "balance")
aCoder.encode(self.subCells, forKey: "subCell")//THIS
}
}
Am I forced to use core data to solve this or can I encode the array before encoding the whole class in the viewdidload()
ok i figured it out
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as! String
self.tag = decoder.decodeObject(forKey: "tag") as? String
self.more = decoder.decodeObject(forKey: "more") as? String
self.amount = decoder.decodeObject(forKey: "amount") as! String
self.balance = decoder.decodeObject(forKey: "balance") as! String
self.subCells = NSKeyedUnarchiver.unarchiveObject(with: (decoder.decodeObject(forKey: "subCells") as! NSData) as Data) as? [Cell]
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.tag, forKey: "tag")
aCoder.encode(self.more, forKey: "more")
aCoder.encode(self.amount, forKey: "amount")
aCoder.encode(self.balance, forKey: "balance")
aCoder.encode(NSKeyedArchiver.archivedData(withRootObject: self.subCells), forKey: "subCells")
}
I am writing a demo app where I store items in NSUserDefaults. The iOS UI Test looks like this:
func test_should_create_account_successfully() {
app.textFields["usernameTextField"].tapAndType(text: "johndoe")
app.textFields["passwordTextField"].tapAndType(text: "password")
app.buttons["registerButton"].tap()
let users = dataAccess.getUsers()
XCTAssertTrue(users.count > 0)
}
The registerButton tap fired the following code:
#IBAction func saveButtonClicked() {
let user = User(username: self.usernameTextField.text!, password: self.passwordTextField.text!)
self.dataAccess.saveUser(user)
}
DataAccess class is defined below:
func saveUser(_ user:User) {
user.userId = UUID().uuidString
var users = getUsers()
users.append(user)
let usersData = NSKeyedArchiver.archivedData(withRootObject: users)
// save the user
let userDefaults = UserDefaults.standard
userDefaults.setValue(usersData, forKey: "users")
userDefaults.synchronize()
}
func getUsers() -> [User] {
let userDefaults = UserDefaults.standard
let usersData = userDefaults.value(forKey: "users") as? Data
if usersData == nil {
return [User]()
}
let users = NSKeyedUnarchiver.unarchiveObject(with: usersData!) as! [User]
return users
}
The problem is that the following line always return 0 users:
let users = dataAccess.getUsers()
This only happens in iOS UI Test and not in normal Unit Test target.
UPDATE: User class is NSCoding Protocol compatible
public class User : NSObject, NSCoding {
var username :String!
var password :String!
var userId :String!
init(username :String, password :String) {
self.username = username
self.password = password
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(self.userId,forKey: "userId")
aCoder.encode(self.username, forKey: "username")
aCoder.encode(self.password, forKey: "password")
}
public required init?(coder aDecoder: NSCoder) {
self.userId = aDecoder.decodeObject(forKey: "userId") as! String
self.username = aDecoder.decodeObject(forKey: "username") as! String
self.password = aDecoder.decodeObject(forKey: "password") as! String
}
}
It is because your class User is not properly encoded do it like this sample class (in swift 3):
class User: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String, String, String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
self.url = aDecoder.decodeObject(forKey: "url") as? String ?? ""
self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.url, forKey: "url")
aCoder.encode(self.desc, forKey: "desc")
}
}
then save and get it from UserDefaults like this:
func save() {
let data = NSKeyedArchiver.archivedData(withRootObject: object)
UserDefaults.standard.set(data, forKey:"userData" )
}
func get() -> MyObject? {
guard let data = UserDefaults.standard.object(forKey: "userData") as? Data else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject
}
Error is crashing app when using NSCoder and NSKeyArchiver.
I had made a recent post around NSCoder but since then I've changed my code around and got a new error and decided a new post is best.
The app is a blog reader, reading from a MYSQL database using PHP to fill a table view with custom objects in Swift using JSON. I've been trying to save mainArray so that when the user moves cells across sections (each section has an array) it can save where the user left it.
Blog.swift: Handles the Blogs custom objects
import UIKit
class Blog: NSObject, NSCoding {
var blogName: String!
var blogStatus1: String!
var blogStatus2: String!
var blogURL: String!
var blogID: String!
var blogType: String!
var blogDate: String!
var blogPop: String!
static func createBlog(from jsonObject: AnyObject) -> Blog? {
guard let bID: String = jsonObject.object(forKey: "id") as? String,
let bName: String = jsonObject.object(forKey: "blogName") as? String,
let bStatus1: String = jsonObject.object(forKey: "blogStatus1") as? String,
let bStatus2: String = jsonObject.object(forKey: "blogStatus2") as? String,
let bURL: String = jsonObject.object(forKey: "blogURL") as? String,
let bType: String = jsonObject.object(forKey: "blogType") as? String,
let bDate: String = jsonObject.object(forKey: "blogDate") as? String,
let bPop: String = jsonObject.object(forKey: "blogPop") as? String
else {
print("Error: (Creating Blog Object)")
return nil
}
let blog = Blog()
blog.blogName = bName
blog.blogStatus1 = bStatus1
blog.blogStatus2 = bStatus2
blog.blogURL = bURL
blog.blogID = bID
blog.blogType = bType
blog.blogDate = bDate
blog.blogPop = bPop
return blog
}
// NSCoding
convenience required init?(coder aDecoder: NSCoder) {
self.init (coder : aDecoder) // *** Crashes Here ***
self.blogName = aDecoder.decodeObject(forKey: "blogName") as! String
self.blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as! String
self.blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as! String
self.blogURL = aDecoder.decodeObject(forKey: "blogURL") as! String
self.blogID = aDecoder.decodeObject(forKey: "blogID") as! String
self.blogType = aDecoder.decodeObject(forKey: "blogType") as! String
self.blogDate = aDecoder.decodeObject(forKey: "blogDate") as! String
self.blogPop = aDecoder.decodeObject(forKey: "blogPop") as! String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(blogName, forKey: "blogName")
aCoder.encode(blogStatus1, forKey: "blogStatus1")
aCoder.encode(blogStatus2, forKey: "blogStatus2")
aCoder.encode(blogURL, forKey: "blogURL")
aCoder.encode(blogID, forKey: "blogID")
aCoder.encode(blogType, forKey: "blogType")
aCoder.encode(blogDate, forKey: "blogDate")
aCoder.encode(blogPop, forKey: "blogPop")
}
}
MainController.swift - Where table view is located
var mainArray = [Blog]()
var followedArray = [Blog]()
override func viewDidLoad() {
super.viewDidLoad()
// Receiving Data from Server
retrieveData()
if let data = UserDefaults.standard.data(forKey: "mainArrayKey"),
let myBlogList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Blog] {
mainArray = myBlogList
print("mainArray: \(mainArray)")
} else {
print("Error: (Saving to UserDefaults)")
}
}
// Retrieving Data from Server
func retrieveData() {
let getDataURL = "http://example.com/receiving.php"
let url: NSURL = NSURL(string: getDataURL)!
do {
let data: Data = try Data(contentsOf: url as URL)
let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog.createBlog(from: jsonObject as AnyObject) {
mainArray.append(blog)
// Save to UserDefaults
let encodedData = NSKeyedArchiver.archivedData(withRootObject: mainArray)
UserDefaults.standard.set(encodedData, forKey: "mainArrayKey")
}
}
}
catch {
print("Error: (Retrieving Data)")
}
myTableView.reloadData()
// Logs
print("This is mainArray", mainArray)
// Check UserDefaults
if UserDefaults.standard.object(forKey: "mainArrayKey") != nil{
print("mainArray key exists")
}
else {
print("mainArray key does not exist")
}
}
Looks like an infinite loop to me. You call init(coder:), and the first line calls init(coder:), and the first line calls init(coder:), and so on ad infinitum.
You need to call a different initializer inside it. Try self.init() instead.
As others have stated it's indeed an infinite loop. What you need to do is change it to self.init() and also add in the following to your code. Or implement your own init that does whatever needs to be done.
override init() {
super.init()
}
I feel as if I am doing things correctly, but I am getting an error at the end of my data conversion and retrieval. Please see the code below:
class Task:NSObject, NSCoding {
var name:String
var notes:String
var date:NSDate
var taskCompleted:Bool
init(name:String, notes:String,date:NSDate, taskCompleted:Bool){
self.name = name
self.notes = notes
self.date = date
self.taskCompleted = taskCompleted
}
required init(coder decoder: NSCoder){
self.name = (decoder.decodeObjectForKey("name") as! String?)!
self.notes = (decoder.decodeObjectForKey("notes") as! String?)!
self.date = (decoder.decodeObjectForKey("date") as! NSDate?)!
self.taskCompleted = (decoder.decodeObjectForKey("taskCompleted") as! Bool?)!
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeObject(self.notes, forKey: "notes")
coder.encodeObject(self.date, forKey: "date")
coder.encodeObject(self.taskCompleted, forKey: "taskCompleted")
}
}
I then save and get the data as follows:
let nowData = NSKeyedArchiver.archivedDataWithRootObject([nowTasks])
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(nowData, forKey: "nowData")
let loadedData = defaults.dataForKey("nowData")
let loadedArray = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData!) as! [Task]
When I call print(loadedArray.first) I get the error: NSArray element failed to match the Swift Array Element type
Looks like your code should work fine even with some really weird forced casting going on in your decoder method. Try like this:
class Task: NSObject, NSCoding {
var name = String()
var notes = String()
var date: NSDate
var taskCompleted: Bool
init(name: String, notes: String, date: NSDate, taskCompleted: Bool){
self.name = name
self.notes = notes
self.date = date
self.taskCompleted = taskCompleted
}
required init(coder decoder: NSCoder){
self.name = decoder.decodeObjectForKey("name") as! String
self.notes = decoder.decodeObjectForKey("notes") as! String
self.date = decoder.decodeObjectForKey("date") as! NSDate
self.taskCompleted = decoder.decodeBoolForKey("taskCompleted")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(name, forKey: "name")
coder.encodeObject(notes, forKey: "notes")
coder.encodeObject(date, forKey: "date")
coder.encodeBool(taskCompleted, forKey: "taskCompleted")
}
}
Testing with plist files:
let task1 = Task(name: "task1", notes: "note a", date: NSDate(), taskCompleted: false)
let task2 = Task(name: "task2", notes: "note b", date: NSDate(), taskCompleted: true)
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let fileURL = documentsDirectory.URLByAppendingPathComponent("data.plist")
if let filePath = fileURL.path {
NSKeyedArchiver.archiveRootObject([task1,task2], toFile: filePath)
if let loadedArray = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as? [Task] {
print(loadedArray.count)
print(loadedArray.first?.name ?? "")
print(loadedArray.first?.notes ?? "")
print(loadedArray.first!.date )
print(loadedArray.first!.taskCompleted)
print(loadedArray.last?.name ?? "")
print(loadedArray.last?.notes ?? "")
print(loadedArray.last!.date )
print(loadedArray.last!.taskCompleted)
}
}