Strange issue in saving class in NSUserDefaults - ios

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?

Related

Issues with userdefaults usage swift 3

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

IOS: Retrieving custom object from userDefaults and NSCoding gives different value

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

User Defaults not saving dictionary contents in swift 3

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

converting custom object to NSData Swift

I have been researching all over the interweb, especially Stack overflow, and my own references I have at home, but I have not been able to figure out what is wrong with my code. I have spent most of my day trying to figure this issue out, and I hope somebody here can help point me in the correct direction.
Setup:
I have a chess app that I am building in Swift 3.0, and the structure is as follows: BoardModel is the class that holds all of the data about the game, Piece is a class within board model which holds data about itself within the BoardModel, Piece also has a PieceType enum for .knight, .king, etc.
The BoardModel has a 2D array of Piece representing the chess board. Now, with each move, I want to save the game data in Game Center, but before I can even try to store the game data, the encoding throws an error and that is where I am. The error just points at AppDelegate with the statement: "Thread: 1 signal SIGABRT".
Here is the code that is the problem along with the Piece class:
let pieceData = NSKeyedArchiver.archivedData(withRootObject: board.board[0][0]) // where the error is thrown
class Piece: NSObject, NSCoding {
var isSelected: Bool
var type: PieceType
var isWhite: Bool
var isFirstMove: Bool
var symbol: String!
var position: (row: Int, col: Int)!
override init() {
isSelected = false
type = PieceType.empty
isWhite = true
isFirstMove = true
}
required init(coder aDecoder: NSCoder) {
isSelected = aDecoder.decodeBool(forKey: "isSelected")
type = aDecoder.decodeObject(forKey: "type") as! BoardModel.PieceType
isWhite = aDecoder.decodeBool(forKey: "isWhite")
isFirstMove = aDecoder.decodeBool(forKey: "isFirstMove")
symbol = aDecoder.decodeObject(forKey: "symbol") as! String
position = aDecoder.decodeObject(forKey: "position") as! (Int, Int)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(isSelected, forKey: "isSelected")
aCoder.encode(type, forKey: "type")
aCoder.encode(isWhite, forKey: "isWhite")
aCoder.encode(isFirstMove, forKey: "isFirstMove")
aCoder.encode(symbol, forKey: "symbol")
aCoder.encode(position, forKey: "position")
}
init(isSelected: Bool, type: PieceType, isWhite: Bool, isFirstMove: Bool, symbol: String, position: (Int, Int)) {
self.isSelected = isSelected
self.type = type
self.isWhite = isWhite
self.isFirstMove = isFirstMove
self.symbol = symbol
self.position = position
}
func setPosition(to newPosition: (row: Int, col: Int)) {
position = newPosition
}
}
if your Enum is like this PieceType and type is Int
enum PieceType : Int {
case empty
case notEmpty
}
Then write encode and decode method like this way
required init(coder aDecoder: NSCoder) {
isSelected = aDecoder.decodeBool(forKey: "isSelected")
type = PieceType(rawValue: aDecoder.decodeObject(forKey: "type") as! Int)!
isWhite = aDecoder.decodeBool(forKey: "isWhite")
isFirstMove = aDecoder.decodeBool(forKey: "isFirstMove")
symbol = aDecoder.decodeObject(forKey: "symbol") as! String
position = aDecoder.decodeObject(forKey: "position") as! (Int, Int)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(isSelected, forKey: "isSelected")
aCoder.encode(type.rawValue, forKey: "type")
aCoder.encode(isWhite, forKey: "isWhite")
aCoder.encode(isFirstMove, forKey: "isFirstMove")
aCoder.encode(symbol, forKey: "symbol")
aCoder.encode(position.row, forKey: "position.row")
aCoder.encode(position.col, forKey: "position.col")
}
i have check bellow code and it work's
let pice = Piece(isSelected: true, type: .empty, isWhite: true, isFirstMove: true, symbol: "Test", position: (2, 10))
let pieceData = NSKeyedArchiver.archivedData(withRootObject:pice)
print(pieceData)
And Out put is
386 bytes

Storing and reading array of own classes into a plist?

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

Resources