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.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 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")
}
Load and save struct into UserDefaults
You didn't specify that T
should be Encodable
for load(resource:...
function of class Webservice:
Change this:
class Webservice {
func load(resource: Resource, caller: UIViewController ,completion: @escaping (Result) -> Void) {
To this:
class Webservice {
func load(resource: Resource, caller: UIViewController ,completion: @escaping (Result) -> Void) {
And also you need to encode value, not generic type here:
UserDefaults.standard.set(PropertyListEncoder().encode(T.self), forKey: "user")
should be
UserDefaults.standard.set(try PropertyListEncoder().encode(result), forKey: "user")
But another question is: Why do you encode from JSON and then encode it to PropertyList? Why not save JSON data in UserDefaults?
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(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(_ 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(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(_ 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 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:)
Related Topics
How to Handle Different Orientations in Ios
How to Compare Two Uiimage Objects
Navigation Controller Push View Controller
How to Add Objects to a Uiscrollview That Extend Beyond Uiview from Storyboard
How to Take a Full Screen Screenshot in Swift
Objective C Nsstring* Property Retain Count Oddity
How to Make Drawrect Work Right Now
Is This Possible to Apply the Alternative Icon to the iOS Application
Uitableviewcell, Show Delete Button on Swipe
How to Add an In-App Purchase to an iOS Application
Xcode, Where to Assign the Segue Identifier
Command Failed Due to Signal: Segmentation Fault: 11
Add Image to Uialertaction in Uialertcontroller
Best Way to Check If Uitableviewcell Is Completely Visible
Difference Between Viewdidload and Viewdidappear
Uitextfield's Numerical Pad: Dot Instead of Comma for Float Values