Save Struct to Userdefaults

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



Leave a reply



Submit