I am trying to save custom object in UserDefaults, when I print the object, the retrieved object Id is different then saved object. Below is the code.
Saving
let customAlertView:CustomAlertView = CustomAlertView()
customAlertView.buyingRoleInnerStackView = contextInnerItem
customAlertView.buyingRoleInnerArrStackView = contextArrInnerItem
print("customAlertView :: \(customAlertView)")
//--> Prints customAlertView ::CustomAlertView: 0x1702bc9e0>
let encodedData = NSKeyedArchiver.archivedData(withRootObject: customAlertView)
let defaults = UserDefaults.standard
defaults.set(encodedData, forKey: AppConstants().KEY_CUSTOM_ALERT_VIEW)
Retriving
let defaults = UserDefaults.standard
if let viewData = defaults.object(forKey: AppConstants().KEY_CUSTOM_ALERT_VIEW) as? Data {
let alertView = NSKeyedUnarchiver.unarchiveObject(with: viewData) as! CustomAlertView
print("alertView :: \(alertView)")
}
//--> Prints alertView ::CustomAlertView: 0x1702be120>
}
CustomAlertView Class
class CustomAlertView :NSObject, NSCoding {
var buyingRoleInnerStackView:UIStackView!
var buyingRoleInnerArrStackView:UIStackView!
override init() {}
required init(coder aDecoder: NSCoder) {
self.buyingRoleInnerStackView = aDecoder.decodeObject(forKey: "buyingRoleInnerStackView") as! UIStackView
self.buyingRoleInnerArrStackView = aDecoder.decodeObject(forKey: "buyingRoleInnerArrStackView") as! UIStackView
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.buyingRoleInnerStackView, forKey: "buyingRoleInnerStackView")
aCoder.encode(self.buyingRoleInnerArrStackView, forKey: "buyingRoleInnerArrStackView")
}
....
}
Related
I just started learning Swift so the level of this question may be obvious enough for you, however, I simply can't understand what to do.
I "created" a simple app which allows you to add logs of your day. Each cell stores the time you added, a custom icon that changes depending on the time, and, the log text itself (simple text).
Everything works fine. But, as I didn't know about the "Userdefaults" stuff, the clock resets every time I kill the app.
I read many articles about Userdefaults but I have no idea what to do to keep saving my data even when I kill the app.
Here's what I tried to do:
class ToDoItem: NSObject, NSCoding {
var title: String
var date: String
var type: String!
public init(title: String, date: String, type: String) {
self.title = title
self.date = date
self.type = type
}
required init?(coder aDecoder: NSCoder)
{
// Try to unserialize the "title" variable
if let title = aDecoder.decodeObject(forKey: "title") as? String
{
self.title = title
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
self.date = dateFormatter.string(from: Date())
let hour = NSCalendar.current.component(.hour, from: Date())
var tempType = ""
switch hour {
case 5..<9: tempType = "morning_1"
case 6..<12: tempType = "morning_2"
case 12: tempType = "noon_1"
case 13..<16: tempType = "afternoon_1"
case 16..<20: tempType = "dusk_1"
case 20..<23: tempType = "evening_1"
case 23..<00: tempType = "midnight_1"
default: tempType = "morning_1"
}
self.type = tempType
}
else
{
// There were no objects encoded with the key "title",
// so that's an error.
return nil
}
let userDefaults = UserDefaults.standard
userDefaults.set(true, forKey: "title")
}
func encode(with aCoder: NSCoder)
{
// Store the objects into the coder object
aCoder.encode(self.title, forKey: "title")
let defaults = UserDefaults.standard
defaults.set(false, forKey: "title")
}
}
extension Collection where Iterator.Element == ToDoItem
{
// Builds the persistence URL. This is a location inside
// the "Application Support" directory for the App.
private static func persistencePath() -> URL?
{
let url = try? FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
return url?.appendingPathComponent("todoitems.bin")
}
// Write the array to persistence
func writeToPersistence() throws
{
if let url = Self.persistencePath(), let array = self as? NSArray
{
let data = NSKeyedArchiver.archivedData(withRootObject: array)
try data.write(to: url)
}
else
{
throw NSError(domain: "com.example.MyToDo", code: 10, userInfo: nil)
}
}
// Read the array from persistence
static func readFromPersistence() throws -> [ToDoItem]
{
if let url = persistencePath(), let data = (try Data(contentsOf: url) as Data?)
{
if let array = NSKeyedUnarchiver.unarchiveObject(with: data) as? [ToDoItem]
{
return array
}
else
{
throw NSError(domain: "com.example.MyToDo", code: 11, userInfo: nil)
}
}
else
{
throw NSError(domain: "com.example.MyToDo", code: 12, userInfo: nil)
}
}
}
can anyone help me or at least point what I have to do? thank you!
You're using NSKeyedArchiver and NSKeyedUnarchiver which is a different mechanism than UserDefaults. Both are perfectly valid, but you have to pick one, they don't work together.
Here you are archiving and unarchiving an array of ToDoItem. For this to work, ToDoItem needs to be archivable, meaning it must implement the NSCoding protocol, which is:
public protocol NSCoding {
public func encode(with aCoder: NSCoder)
public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
All the properties that you want to save must be added to/extracted from the NSCoder object. Here is an example:
class ToDoItem: NSObject, NSCoding
{
var title: String
var date: Date
required init?(coder aDecoder: NSCoder)
{
guard let title = aDecoder.decodeObject(forKey: "title") as? String else {
return nil
}
guard let date = aDecoder.decodeObject(forKey: "date") as? Date else {
return nil
}
self.title = title
self.date = date
}
func encode(with aCoder: NSCoder)
{
aCoder.encode(self.title, forKey: "title")
aCoder.encode(self.date, forKey: "date")
}
}
I am trying to add a key and a value to a dictionary then add this dictionary the user defaults and read back into a dictionary object. I have two questions that I would really appreciate any help in,
1) why is the dictionary being read from user defaults empty? Since I added a key and a value to the dictionary shouldn't those be saved to the dictionary I retrieve from user defaults?
let defaults = UserDefaults.standard;
var myDict = [String: String]()
myDict["key"] = "value"
defaults.setValue(myDict, forKey: "myDict")
let mydict2 = defaults.object(forKey: "myDict") as? [String: String] ?? [String:String]()
print(mydict2)
2) What can I do to this code if the dictionary stores a custom class that I created as a value or a key so if the dictionary was like this:
class Car {
var engineSize: Int
var color: String
init() {
engineSize = 2000
color = "blue"
}
}
class Boat {
var surfaceArea: Int
var weight: Int
init() {
surfaceArea = 3500
weight = 4000
}
}
var myDict = [Car: Boat]()
how can I save that second dict to user defaults and read it from there?
Thank you
EDIT:
This is the answer suggested by ebby94:
var myDict = [String:String]()
myDict["key"] = "value";
let data = NSKeyedArchiver.archivedData(withRootObject: myDict)
UserDefaults.standard.set(data, forKey: "myDict")
func foo()
{
guard let archivedData = UserDefaults.standard.value(forKey: "myDict") as? Data
else
{
print("failed1")
return
}
guard var unarchivedDictionary = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? [String:String]
else
{
print("failed2")
return
}
print(unarchivedDictionary["key"]!)
}
foo()
However this prints failed1, I'm assuming the data wasn't archived correctly. Can this be because I'm running it in playground?
If you want to save custom object to userDefault first you need to encode & decode variable then save using archive & get data using unarchive.
class Car {
var engineSize: Int
var color: String
init() {
engineSize = 2000
color = "blue"
}
// Decode
required convenience public init(coder decoder: NSCoder)
{
self.init()
if let engineSize = decoder.decodeObject(forKey: "engineSize") as? Int
{
self.engineSize = engineSize
}
if let color = decoder.decodeObject(forKey: "color") as? String
{
self.color = color
}
}
// Encode
func encodeWithCoder(coder : NSCoder)
{
if let engineSize = self.engineSize
{
coder.encode(engineSize, forKey: "engineSize")
}
if let color = self.color
{
coder.encode(color, forKey: "weight")
}
}
}
class Boat {
var surfaceArea: Int
var weight: Int
init() {
surfaceArea = 3500
weight = 4000
}
// Decode
required convenience public init(coder decoder: NSCoder)
{
self.init()
if let surfaceArea = decoder.decodeObject(forKey: "surfaceArea") as? Int
{
self.surfaceArea = surfaceArea
}
if let weight = decoder.decodeObject(forKey: "weight") as? Int
{
self.weight = weight
}
}
// Encode
func encodeWithCoder(coder : NSCoder)
{
if let surfaceArea = self.surfaceArea
{
coder.encode(surfaceArea, forKey: "surfaceArea")
}
if let weight = self.weight
{
coder.encode(weight, forKey: "weight")
}
}
You can't save a dictionary directly in UserDefaults. You'll have to convert the dictionary into data and save it and then retrieve the data and unarchive it into dictionary.
Archive and save to UserDefaults
let myDict = [String:String]()
let data = NSKeyedArchiver.archivedData(withRootObject: myDict)
UserDefaults.standard.set(data, forKey: "myDict")
Retrieve and unarchive the data to dictionary
guard let archivedData = UserDefaults.standard.value(forKey: "myDict") as? Data
else{return}
guard let unarchivedDictionary = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? [String:String]
else{return}
Userdefaults not work in Playground. you need to implement and start it in an App in Simulator
I have the next class:
class UnreadMessages: NSObject {
let publicID: String
let messagesCount: Int
init(publicID: String, messagesCount: Int) {
self.publicID = publicID
self.messagesCount = messagesCount
}
required init(coder aDecoder: NSCoder) {
self.publicID = aDecoder.decodeObjectForKey("publicID") as! String
self.messagesCount = aDecoder.decodeObjectForKey("messagesCount") as! Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(publicID, forKey: "publicID")
aCoder.encodeObject(messagesCount, forKey: "messagesCount")
}
}
and I'm saving it in NSUserDefault as:
var chatList = [UnreadMessages]()
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(chatList), forKey: "ChatList")
and then I try to get these values as:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("ChatList") as? NSData {
let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
print(blog)
}
but when I try to print my blog.publicID it gives me the next error:
error: :2:10: error: 'UnreadMessages' is ambiguous for type lookup in this context blog as! UnreadMessages
How can I fix that?
I have this weird issue. I'm saving custom class object in NSUserDefaults, and while retrieving the data I get nil for int variable of the object. Below is the custom class
class User {
var name: String?
var user_id: Int?
var account_id: Int?
var location: String?
}
I'm saving the object as,
let defaults = NSUserDefaults.standardUserDefaults()
var data = NSKeyedArchiver.archivedDataWithRootObject([user]) // I can see the int values for the user objects here
defaults.setObject(data, forKey: "all_users")
Retrieving the data as,
let defaults = NSUserDefaults.standardUserDefaults()
let data = defaults.dataForKey("all_users")
var users = [Users]()
if data != nil {
let userData = NSKeyedUnarchiver.unarchiveObjectWithData(data!) as! [Users]
for usr in userData {
print("\(usr.name!)") // Prints the name
print("\(usr.user_id!)") // Nil value here
users.append(usr)
}
}
I have absolutely no idea about the reason for this behavior.
Custom classes that have none property list items need to conform to NSCoding to be able to be saved in NSUserDefaults.
Here is a guide to conforming to NSCoding: http://nshipster.com/nscoding/
You will need both of these functions:
init(coder decoder: NSCoder) {
self.name = decoder.decodeObjectForKey("name") as String
self.user_id = decoder.decodeIntegerForKey("user_id")
self.account_id = decoder.decodeIntegerForKey("account_id")
self.location = decoder.decodeObjectForKey("self.location") as String
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(self.user_id, forKey: "user_id")
coder.encodeInt(account_id, forKey: "account_id")
coder.encodeObject(self.location, forKey: "location")
}
I try to store a list of own classes into a plist. For that I also found a similar question already answered, but I still do not succeed doing this.
My code:
My Class:
class Bookmark: NSObject {
var sName:String="";
var sTextPosition:String=""; // beim Text scrollpos
init (coder aDecoder: NSCoder!) {
self.sName = aDecoder.decodeObjectForKey("name") as! String;
self.sTextPosition = aDecoder.decodeObjectForKey("textposition")as! String;
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sName, forKey:"name");
aCoder.encodeObject(sTextPosition, forKey:"textposition");
}
}
My Code for storing an array of bookmarks:
var bookmark1:Bookmark=Bookmark(); // <--"Missing argument for parameter 'coder' in call".
bookmark1.sName="myname";
bookmark1.sTextPosition="100";
let listofbookmarks:[Bookmark];
listofbookmarks.append(bookmark1)
let fileManager = (NSFileManager.defaultManager())
let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if (directorys != nil){
let directories:[String] = directorys!;
let pathToFile = directories[0]; //documents directory
let plistfile = "BookmarkArray.plist"
let plistpath = pathToFile.stringByAppendingPathComponent(plistfile);
if !fileManager.fileExistsAtPath(plistpath){
println("Declaring cocoaArray")
var cocoaArray : NSArray = listofbookmarks;
cocoaArray.writeToFile(plistpath, atomically: true)
}
}
I stuck in creating a Bookmark : var bookmark1:Bookmark=Bookmark();
"Missing argument for parameter 'coder' in call".
Can someone help ?
The only initializer that you have in your object is init (coder aDecoder: NSCoder!) so when the object is being created it's assuming that you are calling that method and are missing an NSCoder object.
You also need to use NSKeyedArchiver to perform the serialization and write the data to storage. NSKeyedUnArchiver performs the reverse. To use them you adhere to the NSCoding protocol. You also need to mark your class as #objc so that it can be deserialized properly.
Here's a version of your code with an initializer method for creation without a NSCoder, plus a few other fixes:
import Foundation
#objc(Bookmark) class Bookmark: NSObject, NSCoding { // <- added #objc and NSCoding
var sName = ""
var sTextPosition = "" // beim Text scrollpos
init(name:String, position:String) { // <- new initializer
self.sName = name
self.sTextPosition = position
}
required init (coder aDecoder: NSCoder) { // <- removed ! to adhere to NSCoding protocol
if let name = aDecoder.decodeObjectForKey("name") as? String {
sName = name
}
if let position = aDecoder.decodeObjectForKey("textposition") as? String {
sTextPosition = position
}
}
func encodeWithCoder(aCoder: NSCoder) { // <- removed ! to adhere to NSCoding protocol
aCoder.encodeObject(sName, forKey:"name")
aCoder.encodeObject(sTextPosition, forKey:"textposition")
}
}
var listofbookmarks = [Bookmark]() // <- needed to create a mutable array instance
listofbookmarks.append(Bookmark(name: "myname", position: "100")) // <- new initializer
let fileManager = NSFileManager.defaultManager()
if let directories = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String] {
if !directories.isEmpty {
let plistpath = directories[0].stringByAppendingPathComponent("BookmarkArray.plist")
if !fileManager.fileExistsAtPath(plistpath) {
NSKeyedArchiver.archiveRootObject(listofbookmarks, toFile: plistpath) // <- added serializer
}
if fileManager.fileExistsAtPath(plistpath) {
if let decoded = NSKeyedUnarchiver.unarchiveObjectWithFile(plistpath) as? [Bookmark] {
println(decoded[0].sName) // => "myname"
println(decoded[0].sTextPosition) // => "100"
}
}
}
}