Save Struct to UserDefaults
In Swift 4 this is pretty much trivial. Make your struct codable simply by marking it as adopting the Codable protocol:
struct Song:Codable {
var title: String
var artist: String
}
Now let's start with some data:
var songs: [Song] = [
Song(title: "Title 1", artist: "Artist 1"),
Song(title: "Title 2", artist: "Artist 2"),
Song(title: "Title 3", artist: "Artist 3"),
]
Here's how to get that into UserDefaults:
UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
And here's how to get it back out again later:
if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}
Saving a Codable Struct to UserDefaults with Swift
Here you are saving a Data
value (which is correct)
defaults.set(recode, forKey: "simulationConfiguration")
But here you are reading a String
defaults.string(forKey: "simulationConfiguration")
You cannot save Data
, read String
and expect it to work.
Let's fix your code
First of all you don't need to manually specify the Coding Keys. So your struct become simply this
struct Configuration : Codable {
var title : String?
var contents: [[Int]]?
}
Saving
Now here's the code for saving it
let configuration = Configuration(title: "test title", contents: [[1, 2, 3]])
if let data = try? JSONEncoder().encode(configuration) {
UserDefaults.standard.set(data, forKey: "simulationConfiguration")
}
Loading
And here's the code for reading it
if
let data = UserDefaults.standard.value(forKey: "simulationConfiguration") as? Data,
let configuration = try? JSONDecoder().decode(Configuration.self, from: data) {
print(configuration)
}
Saving a list using Codable or userDefaults
In Task
adopt Codable
struct Task : Codable, Identifiable {
var id = ""
var toDoItem = ""
var amount = 0.0
}
In TaskStore
add two methods to load and save the tasks and an init
method
class TaskStore : ObservableObject {
@Published var tasks = [Task]()
init() {
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "tasks"),
let savedTasks = try? JSONDecoder().decode([Task].self, from: data) else { tasks = []; return }
tasks = savedTasks
}
func save() {
do {
let data = try JSONEncoder().encode(tasks)
UserDefaults.standard.set(data, forKey: "tasks")
} catch {
print(error)
}
}
}
In the view call taskStore.save()
to save the data.
However: For large data sets UserDefaults
is the wrong place. Save the data in the Documents folder or use Core Data.
Side note: Never use value(forKey:)
in UserDefaults
, in your example there is string(forKey:)
How do I have to use UserDefaults to save and load my struct?
A nice way to design this is to extend UserDefaults
with a pair of helper methods:
extension UserDefaults {
func set<T: Encodable>(codable: T, forKey key: String) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(codable)
let jsonString = String(data: data, encoding: .utf8)!
print("Saving \"\(key)\": \(jsonString)")
self.set(jsonString, forKey: key)
} catch {
print("Saving \"\(key)\" failed: \(error)")
}
}
func codable<T: Decodable>(_ codable: T.Type, forKey key: String) -> T? {
guard let jsonString = self.string(forKey: key) else { return nil }
guard let data = jsonString.data(using: .utf8) else { return nil }
let decoder = JSONDecoder()
print("Loading \"\(key)\": \(jsonString)")
return try? decoder.decode(codable, from: data)
}
}
then use it like this in QuotesViewController
:
class QuotesViewController: UIViewController {
struct RandomItems: Codable {
var items : [String]
var seen = 0
init(items:[String], seen: Int) {
self.items = items
self.seen = seen
}
init(_ items: [String]) {
self.init(items: items, seen: 0)
}
}
var quotes: RandomItems! = RandomItems([
"James",
"John",
"William",
])
override func viewDidAppear(_ animated: Bool) {
// Code to load the struct again after the view appears.
let defaults = UserDefaults.standard
quotes = defaults.codable(RandomItems.self, forKey: "quotes")
}
override func viewWillDisappear(_ animated: Bool) {
// Code to save struct before the view disappears.
let defaults = UserDefaults.standard
if let quotes = quotes {
defaults.set(codable: quotes, forKey: "quotes")
}
}
}
Entering (and exiting) the above view controller prints:
Saving "quotes": {"items":["James","John","William"],"seen":0}
Loading "quotes": {"items":["James","John","William"],"seen":0}
Update. For what it's worth, below is the complete code I used to test this in a Xcode 9 playground:
import Foundation
extension UserDefaults {
func set<T: Encodable>(codable: T, forKey key: String) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(codable)
let jsonString = String(data: data, encoding: .utf8)!
print("Saving \"\(key)\": \(jsonString)")
self.set(jsonString, forKey: key)
} catch {
print("Saving \"\(key)\" failed: \(error)")
}
}
func codable<T: Decodable>(_ codable: T.Type, forKey key: String) -> T? {
guard let jsonString = self.string(forKey: key) else { return nil }
guard let data = jsonString.data(using: .utf8) else { return nil }
let decoder = JSONDecoder()
print("Loading \"\(key)\": \(jsonString)")
return try? decoder.decode(codable, from: data)
}
}
class UIViewController: NSObject {
func viewDidAppear(_ animated: Bool) { }
func viewWillDisappear(_ animated: Bool) { }
}
class QuotesViewController: UIViewController {
struct RandomItems: Codable {
var items : [String]
var seen = 0
init(items:[String], seen: Int) {
self.items = items
self.seen = seen
}
init(_ items: [String]) {
self.init(items: items, seen: 0)
}
}
var quotes: RandomItems! = RandomItems([
"James",
"John",
"William",
"Mary",
"Sarah",
"Michael",
"Steve",
"Lisa",
"Jeff",
"Chris",
])
override func viewDidAppear(_ animated: Bool) {
// Code to load the struct again after the view appears.
let defaults = UserDefaults.standard
quotes = defaults.codable(RandomItems.self, forKey: "quotes")
}
override func viewWillDisappear(_ animated: Bool) {
// Code to save struct before the view disappears.
let defaults = UserDefaults.standard
if let quotes = quotes {
defaults.set(codable: quotes, forKey: "quotes")
}
}
}
let vc = QuotesViewController()
vc.viewWillDisappear(false)
vc.quotes = nil
vc.viewDidAppear(true)
print(vc.quotes)
Saving an array of structs to userDefaults
Like this:
struct Item : Codable {
var itemType: clothingType
var itemName: String
}
enum clothingType : String, Codable {
case pants
case shirt
case jacket
case shoes
case hat
case extra
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
if let data = defaults.data(forKey: "SavedItemArray") {
let array = try! PropertyListDecoder().decode([Item].self, from: data)
}
}
}
Of course I suppose now you're wondering how we got the data into user defaults in the first place. Like this:
let array : [Item] = // whatever
if let data = try? PropertyListEncoder().encode(array) {
UserDefaults.standard.set(data, forKey: "SavedItemArray")
}
Update a value on stuct codable identifiable and only userdefault cache this value
If you are storing the student in UserDefault
in this way, then updating it would involve the three-step process of reading the student, updating its value, then writing it back.
// read
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
var savedStudent = defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
// update
savedStudent.firstName = "John"
// write
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(savedStudent) {
defaults.set(encoded, forKey: "SavedStudent")
}
To make this more convenient, you can extract this as a function:
func updateSavedStudent(updateBlock: (inout StudentProfileData) -> Void) {
// read
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
var savedStudent = defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
// update
updateBlock(&savedStudent)
// write
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(savedStudent) {
defaults.set(encoded, forKey: "SavedStudent")
}
}
// usage:
updateSavedStudent {
$0.firstName = "John"
$0.lastName = "Smith"
}
Alternatively, make a computed property for this saved student and put it in a utility class somewhere. Do note that this will encode and decode the student once for every property you update though.
static var savedStudent: StudentProfileData {
get {
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
return defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
}
set {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(newValue) {
defaults.set(encoded, forKey: "SavedStudent")
}
}
}
// usage
savedStudent.firstName = "John"
Related Topics
Generic Within a Generic in Swift
How to Add Document with Custom Id to Firebase (Firestore) on Swift
Ios: Ambiguous Use of Init(Cgimage)
Alamofire Returns Wrong Encoding
Is Gamescene.Sks Not Recommended for Game Building
How to Switch an Xcode Project to Use Swift Version 1.2 in the Xcode 7 Beta
How to Cast Up to Super Class When There Is an Override Function in the Sub Class
Swiftui Onmovecommand Actions Aren't Executed
Animate Path Stroke Drawing in Swiftui
Make Nstextfield in Nstablecellview Firstresponder()
Menuapp in Swift 4 to Run on Login for High Sierra
Swift - Encoding and Decoding String for Special Characters
How to Retrieve All Available Finder Tags
Check If a Character Is Lowercase or Uppercase