i already got data from JSON API. so i want to save data profile then i load if i want to use it.
this is my code parse json
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
// handle json...
}
DispatchQueue.main.async(
execute:self.LoginDone
)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
in
print(json)
i will get data profile like Name, Addres and etc. so, i will save this then load
but i want to save it with another file.
Let create a user object, conform NSCoding protocol and implement decode and encode functions, like this:
class User: NSObject, NSCoding {
var name: String!
var address: String!
init(name: String, address: String) {
self.name = name
self.address = address
}
func encode(with aCoder: NSCoder) {
if let name = name {
aCoder.encode(name, forKey: "name")
}
if let address = address {
aCoder.encode(address, forKey: "address")
}
}
required init?(coder aDecoder: NSCoder) {
if aDecoder.containsValue(forKey: "name") {
self.name = aDecoder.decodeObject(forKey: "name") as! String
}
if aDecoder.containsValue(forKey: "address") {
self.address = aDecoder.decodeObject(forKey: "address") as! String
}
}
}
And now, you can save and retrieve a user:
let user = User(name: "Danh", address: "1234 XYZ st")
// save user
let data = NSKeyedArchiver.archivedData(withRootObject: user)
UserDefaults.standard.set(data, forKey: "user")
// retrieve user
if let data = UserDefaults.standard.value(forKey: "user") as? Data,
let user = NSKeyedUnarchiver.unarchiveObject(with: data) as? User {
print("name: \(user.name), address: \(user.address)")
}
Related
I'm having trouble archiving and/or unarchiving (not sure where the problem is, exactly) a set of custom classes from the iOS documents directory. The set is saved to disk (or at least it appears to be saved) because I can pull it from disk but I cannot unarchive it.
The model
final class BlockedUser: NSObject, NSSecureCoding {
static var supportsSecureCoding = true
let userId: String
let name: String
let date: Int
var timeIntervalFormatted: String?
init(userId: String, name: String, date: Int) {
self.userId = userId
self.name = name
self.date = date
}
required convenience init?(coder: NSCoder) {
guard let userId = coder.decodeObject(forKey: "userId") as? String,
let name = coder.decodeObject(forKey: "name") as? String,
let date = coder.decodeObject(forKey: "date") as? Int else {
return nil
}
self.init(userId: userId, name: name, date: date)
}
func encode(with coder: NSCoder) {
coder.encode(userId, forKey: "userId")
coder.encode(name, forKey: "name")
coder.encode(date, forKey: "date")
}
}
Writing to disk
let fm = FileManager.default
let dox = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
let dir = dox.appendingPathComponent("master.properties", isDirectory: true)
do {
let userData: [URL: Any] = [
/* Everything else in this dictionary is a primitive type (string, bool, etc.)
and reads and writes without problem from disk. The only thing I cannot
get to work is the entry below (the set of custom classes). */
dir.appendingPathComponent("blockedUsers", isDirectory: false): blockedUsers // of type Set<BlockedUser>
]
for entry in userData {
let data = try NSKeyedArchiver.archivedData(withRootObject: entry.value, requiringSecureCoding: true)
try data.write(to: entry.key, options: [.atomic])
}
} catch {
print(error)
}
Reading from disk
if let onDisk = try? Data(contentsOf: dir.appendingPathComponent("blockedUsers", isDirectory: false)) {
if let blockedUsers = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(onDisk) as? Set<BlockedUser> {
print("success")
} else {
print("file found but cannot unarchive") // where I'm currently at
}
} else {
print("file not found")
}
The problem is that you are trying to decode an object instead of decoding an integer. Check this post. Try like this:
class BlockedUser: NSObject, NSSecureCoding {
static var supportsSecureCoding = true
let userId, name: String
let date: Int
var timeIntervalFormatted: String?
init(userId: String, name: String, date: Int) {
self.userId = userId
self.name = name
self.date = date
}
func encode(with coder: NSCoder) {
coder.encode(userId, forKey: "userId")
coder.encode(name, forKey: "name")
coder.encode(date, forKey: "date")
coder.encode(timeIntervalFormatted, forKey: "timeIntervalFormatted")
}
required init?(coder: NSCoder) {
userId = coder.decodeObject(forKey: "userId") as? String ?? ""
name = coder.decodeObject(forKey: "name") as? String ?? ""
date = coder.decodeInteger(forKey: "date")
timeIntervalFormatted = coder.decodeObject(forKey: "timeIntervalFormatted") as? String
}
}
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
}
I know this is a common error people post here but I can't find a post that matches to what I'm doing even if its the same fundamentally. I'm new to Swift and just trying to find my way, thank you.
The first time I open my app, a blog reader app that reads from a MYSQL database, it works as intended, I can follow the blogs that I chose and unfollow. When I follow a blog/cell it saves to User Defaults using KeyArchiver but when I double tap the home button to clear the app from memory and reopen the app, it crashes.
Something wrong is going on in my loadUserDefaults because I set up breakpoints and it crashes at this line self.followedIdentifiers = Set(UserDefaults.standard.stringArray(forKey: "followedID")!)
I know I have an optional but why is it crashing/ coming back nil if I saved it with saveUserDefaults. Is it not saving? or am I not loading it correctly?
The error is this
fatal error: unexpectedly found nil while unwrapping an Optional value
Code: This is MainController.swift
var mainArray = [Blog]()
var followedArray = [Blog]()
var filteredArray = [Blog]()
var followedIdentifiers = Set<String>()
override func viewDidLoad() {
super.viewDidLoad()
// Receiving Data from Server
retrieveDataFromServer()
// NSCoding - Unarchiving Data (followedID)
loadUserDefaults()
}
// NSCoding: Archiving UserDefaults
func saveUserDefaults() {
// Saving to UserDefaults
let encodedData = NSKeyedArchiver.archivedData(withRootObject: self.followedIdentifiers)
UserDefaults.standard.setValue(encodedData, forKey: "followedID")
UserDefaults.standard.synchronize()
}
// NSCoding: Unarchiving UserDefaults
func loadUserDefaults() { // --- Crash is Here ---
// Unarchiving Data
if let data = UserDefaults.standard.data(forKey: "followedID"), let myFollowedList = NSKeyedUnarchiver.unarchiveObject(with: data) as? Set<String> {
self.followedIdentifiers = myFollowedList
self.followedIdentifiers = Set(UserDefaults.standard.stringArray(forKey: "followedID")!) // CRASH
} else {
print("Error/ Empty: (Loading UserDefaults (followedID))")
}
}
// Retrieving Data from Server
func retrieveDataFromServer() {
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
// Clear the arrays
self.followedArray = [Blog]()
self.mainArray = [Blog]()
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {
// Check if identifiers match
if followedIdentifiers.contains(blog.blogID) {
self.followedArray.append(blog)
} else {
self.mainArray.append(blog)
}
}
}
} catch {
print("Error: (Retrieving Data)")
}
myTableView.reloadData()
}
This is Blog.swift which handles all the blogs objects and NSCoding
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
private init (name: String,status1: String,status2: String,url: String,id: String,type: String,date: String,pop: String) {
blogName = name
blogStatus1 = status1
blogStatus2 = status2
blogURL = url
blogID = id
blogType = type
blogDate = date
blogPop = pop
super.init()
}
convenience init?(jsonObject: [String:Any]) {
guard let bID = jsonObject["id"] as? String,
let bName = jsonObject["blogName"] as? String,
let bStatus1 = jsonObject["blogStatus1"] as? String,
let bStatus2 = jsonObject["blogStatus2"] as? String,
let bURL = jsonObject["blogURL"] as? String,
let bType = jsonObject["blogType"] as? String,
let bDate = jsonObject["blogDate"] as? String,
let bPop = jsonObject["blogPop"] as? String
else {
print("Error: (Creating Blog Object)")
return nil
}
self.init(name: bName, status1: bStatus1, status2: bStatus2, url: bURL, id: bID, type: bType, date: bDate, pop: bPop)
}
convenience required init?(coder aDecoder: NSCoder) {
guard let blogName = aDecoder.decodeObject(forKey: "blogName") as? String,
let blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as? String,
let blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as? String,
let blogURL = aDecoder.decodeObject(forKey: "blogURL") as? String,
let blogID = aDecoder.decodeObject(forKey: "blogID") as? String,
let blogType = aDecoder.decodeObject(forKey: "blogType") as? String,
let blogDate = aDecoder.decodeObject(forKey: "blogDate") as? String,
let blogPop = aDecoder.decodeObject(forKey: "blogPop") as? String else {
print("Error: (Creating Blog Object)")
return nil
}
self.init(name: blogName, status1: blogStatus1, status2: blogStatus2, url: blogURL, id: blogID, type: blogType, date: blogDate, pop: blogPop)
}
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")
}
}
I used File Manager to save my archived array and it was the best choice, so easy and simple. Using Swift 3. Credit to #GuyKogus, #Paulw11 & #MartinR
Used to save my object
NSKeyedArchiver.archiveRootObject(myObject, toFile: filePath)
Used to load my object
if let myFollowedList = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? Set<String> {
myObject = myFollowedList
}
Using Swift 3, blog reader app reading from a MYSQL database using JSON and PHP. User has the ability to save the blog they want to keep getting updates from using a follow button (as well as unfollow button). Instead of saving the entire array, just trying to save the followed blogs ID so the app just finds the blogs id and shows that specific blog that the user followed.
This is the error I'm getting when loading the user defaults
Initializer for conditional binding must have Optional type, not 'Void' (aka '()')
This error is in func loadUserDefaults() in MainController.swift at first line if let data = UserDefaults..
After the user clicks the follow button, I move the cells between arrays, between sections in the tableview and then I call saveUserDefaults()
This is MainController.swift
var mainArray = [Blog]()
var followedArray = [Blog]()
var filteredArray = [Blog]()
var followedIdentifiers = Set<String>()
override func viewDidLoad() {
super.viewDidLoad()
// Receiving Data from Server
retrieveDataFromServer()
// NSCoding - Unarchiving Data (followedID)
loadUserDefaults()
}
// NSCoding: Archiving UserDefaults
func saveUserDefaults() {
// Saving to UserDefaults
let encodedData = NSKeyedArchiver.archivedData(withRootObject: self.followedIdentifiers)
UserDefaults.standard.setValue(encodedData, forKey: "followedID")
UserDefaults.standard.synchronize()
}
// NSCoding: Unarchiving UserDefaults *** ERROR IS HERE ***
func loadUserDefaults() {
// Unarchiving Data -- ERROR: THIS FIRST LINE --
if let data = UserDefaults.standard.setValue(Array(self.followedIdentifiers), forKey: "followedID"),
let myFollowedList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Blog] {
self.followedIdentifiers = myFollowedList
self.followedIdentifiers = Set(UserDefaults.standard.stringArray(forKey: "followedID")!)
} else {
print("Error/ Empty: (Loading UserDefaults (followedID))")
}
}
// Retrieving Data from Server
func retrieveDataFromServer() {
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
// Clear the arrays
self.followedArray = [Blog]()
self.mainArray = [Blog]()
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {
// Check if identifiers match
if followedIdentifiers.contains(blog.blogID) {
self.followedArray.append(blog)
} else {
self.mainArray.append(blog)
}
}
}
} catch {
print("Error: (Retrieving Data)")
}
myTableView.reloadData()
}
This is Blog.swift which handles all the blogs objects and NSCoding
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
private init (name: String,status1: String,status2: String,url: String,id: String,type: String,date: String,pop: String) {
blogName = name
blogStatus1 = status1
blogStatus2 = status2
blogURL = url
blogID = id
blogType = type
blogDate = date
blogPop = pop
super.init()
}
convenience init?(jsonObject: [String:Any]) {
guard let bID = jsonObject["id"] as? String,
let bName = jsonObject["blogName"] as? String,
let bStatus1 = jsonObject["blogStatus1"] as? String,
let bStatus2 = jsonObject["blogStatus2"] as? String,
let bURL = jsonObject["blogURL"] as? String,
let bType = jsonObject["blogType"] as? String,
let bDate = jsonObject["blogDate"] as? String,
let bPop = jsonObject["blogPop"] as? String
else {
print("Error: (Creating Blog Object)")
return nil
}
self.init(name: bName, status1: bStatus1, status2: bStatus2, url: bURL, id: bID, type: bType, date: bDate, pop: bPop)
}
convenience required init?(coder aDecoder: NSCoder) {
guard let blogName = aDecoder.decodeObject(forKey: "blogName") as? String,
let blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as? String,
let blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as? String,
let blogURL = aDecoder.decodeObject(forKey: "blogURL") as? String,
let blogID = aDecoder.decodeObject(forKey: "blogID") as? String,
let blogType = aDecoder.decodeObject(forKey: "blogType") as? String,
let blogDate = aDecoder.decodeObject(forKey: "blogDate") as? String,
let blogPop = aDecoder.decodeObject(forKey: "blogPop") as? String else {
print("Error: (Creating Blog Object)")
return nil
}
self.init(name: blogName, status1: blogStatus1, status2: blogStatus2, url: blogURL, id: blogID, type: blogType, date: blogDate, pop: blogPop)
}
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")
}
}
The line of code:
let data = UserDefaults.standard.setValue(Array(self.followedIdentifiers), forKey: "followedID")
makes no sense since setValue method doesn't return any values.
Looks like you need to replace it with (Swift 3):
let data = UserDefaults.standard.data(forKey: "followedID")
Updated with #closetCoder comment
The setValue function is a void function so it can't be assigned to the variable data since it has no return value.
Here is the documentation for UserDefaults from Apple: https://developer.apple.com/documentation/foundation/userdefaults
I have a simple object which conforms to the NSCoding protocol.
import Foundation
class JobCategory: NSObject, NSCoding {
var id: Int
var name: String
var URLString: String
init(id: Int, name: String, URLString: String) {
self.id = id
self.name = name
self.URLString = URLString
}
// MARK: - NSCoding
required init(coder aDecoder: NSCoder) {
id = aDecoder.decodeObject(forKey: "id") as? Int ?? aDecoder.decodeInteger(forKey: "id")
name = aDecoder.decodeObject(forKey: "name") as! String
URLString = aDecoder.decodeObject(forKey: "URLString") as! String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(name, forKey: "name")
aCoder.encode(URLString, forKey: "URLString")
}
}
I'm trying to save an instance of it in UserDefaults but it keeps failing with the following error.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object for key jobCategory'
This is the code where I'm saving in UserDefaults.
enum UserDefaultsKeys: String {
case jobCategory
}
class ViewController: UIViewController {
#IBAction func didTapSaveButton(_ sender: UIButton) {
let category = JobCategory(id: 1, name: "Test Category", URLString: "http://www.example-job.com")
let userDefaults = UserDefaults.standard
userDefaults.set(category, forKey: UserDefaultsKeys.jobCategory.rawValue)
userDefaults.synchronize()
}
}
I replaced the enum value to key with a normal string but the same error still occurs. Any idea what's causing this?
You need to create Data instance from your JobCategory model using JSONEncoder and store that Data instance in UserDefaults and later decode using JSONDecoder.
struct JobCategory: Codable {
let id: Int
let name: String
}
// To store in UserDefaults
if let encoded = try? JSONEncoder().encode(category) {
UserDefaults.standard.set(encoded, forKey: UserDefaultsKeys.jobCategory.rawValue)
}
// Retrieve from UserDefaults
if let data = UserDefaults.standard.object(forKey: UserDefaultsKeys.jobCategory.rawValue) as? Data,
let category = try? JSONDecoder().decode(JobCategory.self, from: data) {
print(category.name)
}
Old Answer
You need to create Data instance from your JobCategory instance using archivedData(withRootObject:) and store that Data instance in UserDefaults and later unarchive using unarchiveTopLevelObjectWithData(_:), So try like this.
For Storing data in UserDefaults
let category = JobCategory(id: 1, name: "Test Category", URLString: "http://www.example-job.com")
let encodedData = NSKeyedArchiver.archivedData(withRootObject: category, requiringSecureCoding: false)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: UserDefaultsKeys.jobCategory.rawValue)
For retrieving data from UserDefaults
let decoded = UserDefaults.standard.object(forKey: UserDefaultsKeys.jobCategory.rawValue) as! Data
let decodedTeams = NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! JobCategory
print(decodedTeams.name)
Update Swift 4, Xcode 10
I have written a struct around it for easy access.
//set, get & remove User own profile in cache
struct UserProfileCache {
static let key = "userProfileCache"
static func save(_ value: Profile!) {
UserDefaults.standard.set(try? PropertyListEncoder().encode(value), forKey: key)
}
static func get() -> Profile! {
var userData: Profile!
if let data = UserDefaults.standard.value(forKey: key) as? Data {
userData = try? PropertyListDecoder().decode(Profile.self, from: data)
return userData!
} else {
return userData
}
}
static func remove() {
UserDefaults.standard.removeObject(forKey: key)
}
}
Profile is a Json encoded object.
struct Profile: Codable {
let id: Int!
let firstName: String
let dob: String!
}
Usage:
//save details in user defaults...
UserProfileCache.save(profileDetails)
Hope that helps!!!
Thanks
Swift save Codable object to UserDefault with #propertyWrapper
#propertyWrapper
struct UserDefault<T: Codable> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
if let data = UserDefaults.standard.object(forKey: key) as? Data,
let user = try? JSONDecoder().decode(T.self, from: data) {
return user
}
return defaultValue
}
set {
if let encoded = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(encoded, forKey: key)
}
}
}
}
enum GlobalSettings {
#UserDefault("user", defaultValue: User(name:"",pass:"")) static var user: User
}
Example User model confirm Codable
struct User:Codable {
let name:String
let pass:String
}
How to use it
//Set value
GlobalSettings.user = User(name: "Ahmed", pass: "Ahmed")
//GetValue
print(GlobalSettings.user)
Save dictionary Into userdefault
let data = NSKeyedArchiver.archivedData(withRootObject: DictionaryData)
UserDefaults.standard.set(data, forKey: kUserData)
Retrieving the dictionary
let outData = UserDefaults.standard.data(forKey: kUserData)
let dict = NSKeyedUnarchiver.unarchiveObject(with: outData!) as! NSDictionary
Based on Harjot Singh answer. I've used like this:
struct AppData {
static var myObject: MyObject? {
get {
if UserDefaults.standard.object(forKey: "UserLocationKey") != nil {
if let data = UserDefaults.standard.value(forKey: "UserLocationKey") as? Data {
let myObject = try? PropertyListDecoder().decode(MyObject.self, from: data)
return myObject!
}
}
return nil
}
set {
UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "UserLocationKey")
}
}
}
Here's a UserDefaults extension to set and get a Codable object, and keep it human-readable in the plist (User Defaults) if you open it as a plain text file:
extension Encodable {
var asDictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return try? JSONSerialization.jsonObject(with: data) as? [String : Any]
}
}
extension Decodable {
init?(dictionary: [String: Any]) {
guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else { return nil }
guard let object = try? JSONDecoder().decode(Self.self, from: data) else { return nil }
self = object
}
}
extension UserDefaults {
func setEncodableAsDictionary<T: Encodable>(_ encodable: T, for key: String) {
self.set(encodable.asDictionary, forKey: key)
}
func getDecodableFromDictionary<T: Decodable>(for key: String) -> T? {
guard let dictionary = self.dictionary(forKey: key) else {
return nil
}
return T(dictionary: dictionary)
}
}
If you want to also support array (of codables) to and from plist array, add the following to the extension:
extension UserDefaults {
func setEncodablesAsArrayOfDictionaries<T: Encodable>(_ encodables: Array<T>, for key: String) {
let arrayOfDictionaries = encodables.map({ $0.asDictionary })
self.set(arrayOfDictionaries, forKey: key)
}
func getDecodablesFromArrayOfDictionaries<T: Decodable>(for key: String) -> [T]? {
guard let arrayOfDictionaries = self.array(forKey: key) as? [[String: Any]] else {
return nil
}
return arrayOfDictionaries.compactMap({ T(dictionary: $0) })
}
}
If you don't care about plist being human-readable, it can be simply saved as Data (will look like random string if opened as plain text):
extension UserDefaults {
func setEncodable<T: Encodable>(_ encodable: T, for key: String) throws {
let data = try PropertyListEncoder().encode(encodable)
self.set(data, forKey: key)
}
func getDecodable<T: Decodable>(for key: String) -> T? {
guard
self.object(forKey: key) != nil,
let data = self.value(forKey: key) as? Data
else {
return nil
}
let obj = try? PropertyListDecoder().decode(T.self, from: data)
return obj
}
}
(With this second approach, you don't need the Encodable and Decodable extensions from the top)