Save and get details of Model class using userdefault - swift

I have one model object User and inside that another model object is Picture.
"user": {
"id": 1,
"email": "abc.k#gmail.com",
"user_profile_photo": {
"id": 997,
"user_id": 1,
"photo_url": "https://newproduction.s3.amazonaws.com/profile_image/RkUJAczv5nWpUyFTgyTgMLChR.jpeg",
}
}
I have two model class for this one is User and another is Picture inside user.
I am saving model user in userdefault as below
//Get Response
loginResponseObj = Mapper<LoginResponse>().map(JSONObject:(response.result.value))
//Save user Details
let userData = loginResponseObj.user!
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey:"user")
and when i am trying to get data from userdefaults, am getting User Model but inside details are nil.
Get Userdetails from userdefault Code is below
guard let data = UserDefaults.standard.object(forKey: "user") as? Data
else
{
return UserModel()
}
return (NSKeyedUnarchiver.unarchiveObject(with: data) as? UserModel)!
This return **<ABC.User: 0x7f84f740c440>**
But when i am trying to get Picture from User it return nil
In User Model
class User:NSObject,Mappable,NSCoding{
var email: String?
var picture: Picture?
required init?(coder aDecoder: NSCoder) {
self.email = aDecoder.decodeObject(forKey: "email") as? String
}
func initWithCoder(aDecoder:NSCoder) -> UserModel
{
self.email = aDecoder.decodeObject(forKey: "email") as? String
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(email, forKey: "email")
}
}
In Picture Model
class Picture:Mappable,NSCoding{
var id: String?
var photoURL: String?
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
}
func initWithCoder(aDecoder:NSCoder) -> Picture
{
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
}
}
Note: I am using MVVM Pattern and Object Mapper
So, How can i get the whole details of user including photo_url (User.Picture.photo_url) ?

In User Model
class User:NSObject,Mappable,NSCoding{
var email: String?
var picture: Picture?
required init?(coder aDecoder: NSCoder) {
self.email = aDecoder.decodeObject(forKey: "email") as? String
self.picture = aDecoder.decodeObject(forKey: "picture") as? Picture
}
func initWithCoder(aDecoder:NSCoder) -> UserModel
{
self.email = aDecoder.decodeObject(forKey: "email") as? String
self.picture = aDecoder.decodeObject(forKey: "picture") as? Picture
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(email, forKey: "email")
aCoder.encode(picture, forKey: "picture")
}
}
In Picture Model
class Picture: NSObject,Mappable,NSCoding {
var id: String?
var photoURL: String?
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
}
func initWithCoder(aDecoder:NSCoder) -> Picture
{
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(photoURL, forKey: "photoURL")
}
Get Details From User Defaults
guard let data = UserDefaults.standard.object(forKey: "user") as? Data
else
{
return User()
}
return (NSKeyedUnarchiver.unarchiveObject(with: data) as? User)!
above code will return User Model
Using this model access its variables
print(user.email)
print(user.picture.photoURL)

Related

How to unarchive object using NSKeyedUnarchiver?

**I'm using this class: **
class Person: NSObject, NSCoding {
var name: String
var image: String
init(name: String, image: String) {
self.name = name
self.image = image
}
required init(coder aDecoder: NSCoder){
name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
image = aDecoder.decodeObject(forKey: "image") as? String ?? ""
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(image, forKey: "image")
}
}
For archiving, i used this method:
if let savedData = try? NSKeyedArchiver.archivedData(withRootObject: people, requireSecureCoding: false)
whrere people is Person class array [Person]
As for unarchiving, this method:
NSKeyedUnarchiver.unarchivedTopLevelObjectWithData()
is deprecated... which method should i use now ?
You now must tell the system what type you expect rather than simply unarchiving whatever is found:
try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: Person.self, from: savedData)

NSKeyedArchiver: casting Data returning nil - Swift

Well, investigated several similar topics here, done everything as suggested, but my computed property "previousUserData" returns me nil, when trying to cast the decoded object to my type. What's wrong?
#objc class PreviousUserData: NSObject, NSCoding {
var name: String
var phone: String
var email: String
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(phone, forKey: "phone")
aCoder.encode(email, forKey: "email")
}
required convenience init?(coder aDecoder: NSCoder) {
guard
let name = aDecoder.decodeObject(forKey: "name") as? String,
let phone = aDecoder.decodeObject(forKey: "phone") as? String,
let email = aDecoder.decodeObject(forKey: "email") as? String
else {
return nil
}
self.init(name: name, phone: phone, email: email)
}
init(name: String, phone: String, email: String) {
self.name = name
self.phone = phone
self.email = email
}
}
unarchived returns me nil, but data for key "userdata" is exists
var previousUserData: PreviousUserData? {
get {
if let object = UserDefaults.standard.object(forKey: "userdata") as? Data {
let unarchived = NSKeyedUnarchiver.unarchiveObject(with: object) as? PreviousUserData
return unarchived
}
return nil
}
set {
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: previousUserData as Any)
UserDefaults.standard.setValue(encodedData, forKey: "userdata")
}
}
Actually you can't get valid data because the setter is wrong. You have to save newValue rather than previousUserData.
This is an slightly optimized version
var previousUserData: PreviousUserData? {
get {
guard let data = UserDefaults.standard.data(forKey: "userdata") else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: data) as? PreviousUserData
}
set {
guard let newValue = newValue else { return }
let encodedData = NSKeyedArchiver.archivedData(withRootObject: newValue)
UserDefaults.standard.set(encodedData, forKey: "userdata")
}
}
NSCoding is pretty heavy. In this case I'd recommend to use Codable to serialize the data as JSON or Property List. It gets rid of #objc, class and NSObject and reduces the entire code to
struct PreviousUserData : Codable {
var name: String
var phone: String
var email: String
}
var previousUserData: PreviousUserData? {
get {
guard let data = UserDefaults.standard.data(forKey: "userdata") else { return nil }
return try? JSONDecoder().decode(PreviousUserData.self, from: data)
}
set {
guard let newValue = newValue, let encodedData = try? JSONEncoder().encode(newValue) else { return }
UserDefaults.standard.set(encodedData, forKey: "userdata")
}
}

UserDefaults Custom Object of Custom Objects

Part of my settings I want to save with UserDefaults.
I already found this solution here:
Save custom objects into NSUserDefaults
But I could not figure out how to save if I have custom objects in a custom object.
My classes look like this:
class ConfigLabelMainList: NSObject, NSCoding {
var labelMiddleFirst: StatsIntervalModel
var labelMiddleSecond: StatsIntervalModel
var labelMiddleThird: StatsIntervalModel
var labelRightFirst: StatsIntervalModel
var labelRightSecond: StatsIntervalModel
init(labelMiddleFirst: StatsIntervalModel, labelMiddleSecond: StatsIntervalModel, labelMiddleThird: StatsIntervalModel, labelRightFirst: StatsIntervalModel, labelRightSecond: StatsIntervalModel) {
self.labelMiddleFirst = labelMiddleFirst
self.labelMiddleSecond = labelMiddleSecond
self.labelMiddleThird = labelMiddleThird
self.labelRightFirst = labelRightFirst
self.labelRightSecond = labelRightSecond
}
func encode(with aCoder: NSCoder) {
}
required convenience init?(coder aDecoder: NSCoder) {
}
}
class StatsIntervalModel: NSObject, NSCoding {
var stat: String
var interval: String
init(stat: String, interval: String) {
self.stat = stat
self.interval = interval
}
required convenience init?(coder aDecoder: NSCoder) {
let stat = aDecoder.decodeObject(forKey: "stat") as! String
let interval = aDecoder.decodeObject(forKey: "interval") as! String
self.init(stat: stat, interval: interval)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(stat, forKey: "stat")
aCoder.encode(interval, forKey: "interval")
}
}
How would a solution look like?
Add given code in your class ConfigLabelMainList
required convenience init?(coder aDecoder: NSCoder) {
let midFirst = aDecoder.decodeObject(forKey: "midFirst") as! StatsIntervalModel
let midSecond = aDecoder.decodeObject(forKey: "midSecond") as! StatsIntervalModel
let midThird = aDecoder.decodeObject(forKey: "midThird") as! StatsIntervalModel
let rightFirst = aDecoder.decodeObject(forKey: "rightFirst") as! StatsIntervalModel
let rightSecond = aDecoder.decodeObject(forKey: "rightSecond") as! StatsIntervalModel
self.init(labelMiddleFirst: midFirst, labelMiddleSecond: midSecond, labelMiddleThird: midThird, labelRightFirst: rightFirst, labelRightSecond: rightSecond)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(labelMiddleFirst, forKey: "midFirst")
aCoder.encode(labelMiddleSecond, forKey: "midSecond")
aCoder.encode(labelMiddleThird, forKey: "midThird")
aCoder.encode(labelRightFirst, forKey: "rightFirst")
aCoder.encode(labelRightSecond, forKey: "rightSecond")
}
Now simply archive data and save it in defaults.

custom class storage - cannot encode structs

I am trying to store an array of a custom class using UserDefaults. The custom class is for annotations using a mixture of strings and CLLocationCoordinate2D.
I am calling ArchiveUtil.savePins(pins: pins) when I perform a long press gesture in the Map View.
However, I am getting an error
-[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs'
Any ideas what I am doing wrong?
Thanks, code below:
class PinLocation: NSObject, NSCoding, MKAnnotation {
var title: String?
var subtitle: String?
var coordinate: CLLocationCoordinate2D
init(name:String, description: String, lat:CLLocationDegrees,long:CLLocationDegrees){
title = name
subtitle = description
coordinate = CLLocationCoordinate2DMake(lat, long)
}
required init?(coder aDecoder: NSCoder) {
title = aDecoder.decodeObject(forKey: "title") as? String
subtitle = aDecoder.decodeObject(forKey: "subtitle") as? String
coordinate = aDecoder.decodeObject(forKey: "coordinate") as! CLLocationCoordinate2D
}
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(subtitle, forKey: "subtitle")
aCoder.encode(coordinate, forKey: "coordinate")
}
}
class ArchiveUtil {
private static let PinKey = "PinKey"
private static func archivePins(pin: [PinLocation]) -> NSData{
return NSKeyedArchiver.archivedData(withRootObject: pin as NSArray) as NSData
}
static func loadPins() -> [PinLocation]? {
if let unarchivedObject = UserDefaults.standard.object(forKey: PinKey) as? Data{
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [PinLocation]
}
return nil
}
static func savePins(pins: [PinLocation]?){
let archivedObject = archivePins(pin: pins!)
UserDefaults.standard.set(archivedObject, forKey: PinKey)
UserDefaults.standard.synchronize()
}
}
The error is pretty clear: CLLocationCoordinate2D is a struct and this archiver cannot encode structs.
A simple workaround is to en- and decode latitude and longitude separately.
By the way, since both String properties are initialized with non-optional values declare them also as non-optional. If they are supposed not to be changed declare them even as constant (let)
var title: String
var subtitle: String
...
required init?(coder aDecoder: NSCoder) {
title = aDecoder.decodeObject(forKey: "title") as! String
subtitle = aDecoder.decodeObject(forKey: "subtitle") as! String
let latitude = aDecoder.decodeDouble(forKey: "latitude")
let longitude = aDecoder.decodeDouble(forKey: "longitude")
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(subtitle, forKey: "subtitle")
aCoder.encode(coordinate.latitude, forKey: "latitude")
aCoder.encode(coordinate.longitude, forKey: "longitude")
}

How do you save a custom class as an attribute of a CoreData entity in Swift 3?

I have a CoreData Entity SavedWorkout. It has the following attributes:
completionCounter is an array of Bool, and workout is a custom class called Workout.
I am saving my data like so:
let saveCompletionCounter = currentCompletionCounter
let saveDate = Date() as NSDate
let saveRoutineIndex = Int16(currentWorkoutRoutine)
let saveWorkout = NSKeyedArchiver.archivedData(withRootObject: workout)
item.setValue(saveDate, forKey: "date")
item.setValue(saveWorkout, forKey: "workout")
item.setValue(saveRoutineIndex, forKey: "routineIndex")
item.setValue(saveCompletionCounter, forKey: "completionCounter")
do {
try moc.save()
print("save successful")
} catch {
print("saving error")
}
where moc is an instance of NSManagedObjectContext, and item is an instance of NSManagedObject:
moc = appDelegate.managedObjectContext
entity = NSEntityDescription.entity(forEntityName: "SavedWorkout", in: moc)!
item = NSManagedObject(entity: entity, insertInto: moc)
In accordance with this and this and this , I have made my Workout class conform to NSObject and NSCoding, so it now looks like this:
class Workout: NSObject, NSCoding {
let name: String
let imageName: String
let routine: [WorkoutRoutine]
let shortDescription: String
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as! String
imageName = aDecoder.decodeObject(forKey: "imageName") as! String
routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine]
shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as! String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(imageName, forKey: "imageName")
aCoder.encode(routine, forKey: "routine")
aCoder.encode(shortDescription, forKey: "shortDescription")
}
init(name: String, imageName: String, routine: [WorkoutRoutine], shortDescription: String) {
self.name = name
self.imageName = imageName
self.routine = routine
self.shortDescription = shortDescription
}
}
However I always get an error on the line routine: aDecoder.decodeObject....
The error says:
NSForwarding: warning: object 0x60800002cbe0 of class 'App.WorkoutRoutine' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[FitLift.WorkoutRoutine replacementObjectForKeyedArchiver:]
Why does this give me an error and not the other Transformable attribute? How do I save a custom class as a property of a CoreData entity?
The issue is that WorkoutRoutine is itself a custom class and as of your error it is not NSCoding compliant, therefore aCoder.encode(routine, forKey: "routine") doesn't really know how to encode it, as well as routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine] doesn't know how to decode it.
Not really related, but please try a safer approach for your coder and encoder initializer as the force unwrap might cause crashes if the encoder does not contain the keys you are looking for (for any reason)
required init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let imageName = aDecoder.decodeObject(forKey: "imageName") as? String,
let routine = aDecoder.decodeObject(forKey: "routine") as? [WorkoutRoutine],
let shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as? String else {
return nil
}
self.name = name
self.imageName = imageName
self.routine = routine
self.shortDescription = shortDescription
}