Ich habe eine Struktur, die ich in UserDefaults speichern möchte. Hier ist meine Struktur
struct Song {
var title: String
var artist: String
}
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
In einem anderen ViewController habe ich einen UIButton, der wie folgt an diese Struktur angehängt wird
@IBAction func likeButtonPressed(_ sender: Any) {
songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}
Ich möchte, dass die Struktur, wenn der Benutzer auch auf diese Schaltfläche klickt, in UserDefaults gespeichert wird, sodass sie gespeichert wird, wenn der Benutzer die App beendet und sie dann erneut öffnet. Wie würde ich das machen?
ios
swift
struct
nsuserdefaults
Jacob Cavin
quelle
quelle
Antworten:
In Swift 4 ist das ziemlich trivial. Machen Sie Ihre Struktur einfach codierbar, indem Sie sie als das Codable-Protokoll übernehmen:
struct Song:Codable { var title: String var artist: String }
Beginnen wir nun mit einigen Daten:
var songs: [Song] = [ Song(title: "Title 1", artist: "Artist 1"), Song(title: "Title 2", artist: "Artist 2"), Song(title: "Title 3", artist: "Artist 3"), ]
So bringen Sie das in UserDefaults:
UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
Und so können Sie es später wieder herausholen:
if let data = UserDefaults.standard.value(forKey:"songs") as? Data { let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data) }
quelle
func encode(to encoder: Encoder)
Methode in Ihrer Struktur implementieren und dann etwas tun wiefunc encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(artist, forKey: . artist) }
Dies ist meine UserDefaults-Erweiterung im Hauptthread , um get Codable- Objekt in UserDefaults festzulegen
// MARK: - UserDefaults extensions public extension UserDefaults { /// Set Codable object into UserDefaults /// /// - Parameters: /// - object: Codable Object /// - forKey: Key string /// - Throws: UserDefaults Error public func set<T: Codable>(object: T, forKey: String) throws { let jsonData = try JSONEncoder().encode(object) set(jsonData, forKey: forKey) } /// Get Codable object into UserDefaults /// /// - Parameters: /// - object: Codable Object /// - forKey: Key string /// - Throws: UserDefaults Error public func get<T: Codable>(objectType: T.Type, forKey: String) throws -> T? { guard let result = value(forKey: forKey) as? Data else { return nil } return try JSONDecoder().decode(objectType, from: result) } }
Update Dies ist meine UserDefaults-Erweiterung im Hintergrund , um get Codable- Objekt in UserDefaults zu setzen
// MARK: - JSONDecoder extensions public extension JSONDecoder { /// Decode an object, decoded from a JSON object. /// /// - Parameter data: JSON object Data /// - Returns: Decodable object public func decode<T: Decodable>(from data: Data?) -> T? { guard let data = data else { return nil } return try? self.decode(T.self, from: data) } /// Decode an object in background thread, decoded from a JSON object. /// /// - Parameters: /// - data: JSON object Data /// - onDecode: Decodable object public func decodeInBackground<T: Decodable>(from data: Data?, onDecode: @escaping (T?) -> Void) { DispatchQueue.global().async { let decoded: T? = self.decode(from: data) DispatchQueue.main.async { onDecode(decoded) } } } } // MARK: - JSONEncoder extensions public extension JSONEncoder { /// Encodable an object /// /// - Parameter value: Encodable Object /// - Returns: Data encode or nil public func encode<T: Encodable>(from value: T?) -> Data? { guard let value = value else { return nil } return try? self.encode(value) } /// Encodable an object in background thread /// /// - Parameters: /// - encodableObject: Encodable Object /// - onEncode: Data encode or nil public func encodeInBackground<T: Encodable>(from encodableObject: T?, onEncode: @escaping (Data?) -> Void) { DispatchQueue.global().async { let encode = self.encode(from: encodableObject) DispatchQueue.main.async { onEncode(encode) } } } } // MARK: - NSUserDefaults extensions public extension UserDefaults { /// Set Encodable object in UserDefaults /// /// - Parameters: /// - type: Encodable object type /// - key: UserDefaults key /// - Throws: An error if any value throws an error during encoding. public func set<T: Encodable>(object type: T, for key: String, onEncode: @escaping (Bool) -> Void) throws { JSONEncoder().encodeInBackground(from: type) { [weak self] (data) in guard let data = data, let `self` = self else { onEncode(false) return } self.set(data, forKey: key) onEncode(true) } } /// Get Decodable object in UserDefaults /// /// - Parameters: /// - objectType: Decodable object type /// - forKey: UserDefaults key /// - onDecode: Codable object public func get<T: Decodable>(object type: T.Type, for key: String, onDecode: @escaping (T?) -> Void) { let data = value(forKey: key) as? Data JSONDecoder().decodeInBackground(from: data, onDecode: onDecode) } }
quelle
class func getUser() -> User? { UserDefaults.standard.get(object: User.self, for: DefaultKeys.user) { user in return user } return nil }
Aber es gibt mir eine WarnungExpression of type 'User?' is unused
bei der Rückgabe des BenutzerwertsExpression of type 'User' is unused
Wenn die Struktur nur Eigenschaften enthält, die der Eigenschaftsliste entsprechen, empfehle ich, eine Eigenschaft
propertyListRepresentation
und eine entsprechendeinit
Methode hinzuzufügenstruct Song { var title: String var artist: String init(title : String, artist : String) { self.title = title self.artist = artist } init?(dictionary : [String:String]) { guard let title = dictionary["title"], let artist = dictionary["artist"] else { return nil } self.init(title: title, artist: artist) } var propertyListRepresentation : [String:String] { return ["title" : title, "artist" : artist] } }
Speichern einer Reihe von Songs zum
UserDefaults
Schreibenlet propertylistSongs = songs.map{ $0.propertyListRepresentation } UserDefaults.standard.set(propertylistSongs, forKey: "songs")
Array lesen
if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] { songs = propertylistSongs.flatMap{ Song(dictionary: $0) } }
Wenn
title
undartist
niemals mutiert werden, sollten Sie die Eigenschaften als Konstanten deklarieren (let
).Diese Antwort wurde geschrieben, während sich Swift 4 im Beta-Status befand. In der Zwischenzeit
Codable
ist die Anpassung die bessere Lösung.quelle
propertyListRepresentation
, es[String:Any]
könnte besser sein.String
. Das starke Typsystem von Swift ermutigt den Entwickler, so typspezifisch wie möglich zu sein.[String:Any]
könnte universeller sein, wenn Sie irgendwann "var count: Int" in denSong
oder einen anderen Typ einfügen. Das heißt nicht, dass es nicht sicher ist.Codable
Lösung.Hier ist ein moderner Swift 5.1
@propertyWrapper
, mit dem jedesCodable
Objekt in Form einer lesbaren JSON-Zeichenfolge gespeichert werden kann :@propertyWrapper struct UserDefaultEncoded<T: Codable> { let key: String let defaultValue: T init(key: String, default: T) { self.key = key defaultValue = `default` } var wrappedValue: T { get { guard let jsonString = UserDefaults.standard.string(forKey: key) else { return defaultValue } guard let jsonData = jsonString.data(using: .utf8) else { return defaultValue } guard let value = try? JSONDecoder().decode(T.self, from: jsonData) else { return defaultValue } return value } set { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] guard let jsonData = try? encoder.encode(newValue) else { return } let jsonString = String(bytes: jsonData, encoding: .utf8) UserDefaults.standard.set(jsonString, forKey: key) } } }
Verwendung:
extension Song: Codable {} @UserDefaultEncoded(key: "songs", default: []) var songs: [Song]
quelle
default(T)
, gibt es so etwas in Swift nicht. Ich denke, der Zweckdefault
besteht darin, dasdefault
Schlüsselwort als Parameter zu verwenden (wir nennen es wörtlich in C # und verwenden es@default
)default
hier kein Schlüsselwort ist.UserDefaults.standard.set(_, forKey:)
die verwenden kann@UserDefaultEncoded(key: "songs", default: [])
?Von hier aus:
Sie müssen verwenden
NSKeydArchiver
. Dokumentation finden Sie hier hier und Beispiele hier und hier .quelle
Wenn Sie nur versuchen, dieses Array von Songs in UserDefaults zu speichern, und nichts Besonderes, verwenden Sie Folgendes: -
//stores the array to defaults UserDefaults.standard.setValue(value: songs, forKey: "yourKey") //retrieving the array UserDefaults.standard.object(forKey: "yourKey") as! [Song] //Make sure to typecast this as an array of Song
Wenn Sie ein schweres Array speichern, empfehle ich Ihnen, das NSCoding-Protokoll oder das Codable-Protokoll in Swift 4 zu verwenden
Beispiel für ein Codierungsprotokoll: -
struct Song { var title: String var artist: String } class customClass: NSObject, NSCoding { //conform to nsobject and nscoding var songs: [Song] = [ Song(title: "Title 1", artist "Artist 1"), Song(title: "Title 2", artist "Artist 2"), Song(title: "Title 3", artist "Artist 3"), ] override init(arr: [Song]) self.songs = arr } required convenience init(coder aDecoder: NSCoder) { //decoding your array let songs = aDecoder.decodeObject(forKey: "yourKey") as! [Song] self.init(are: songs) } func encode(with aCoder: NSCoder) { //encoding aCoder.encode(songs, forKey: "yourKey") } }
quelle
Ich würde mir vorstellen, dass es durchaus üblich sein sollte, die Einstellungen eines Benutzers als beobachtbares Objekt darzustellen. Hier ist ein Beispiel dafür, wie beobachtbare Daten mit den Benutzerstandards synchronisiert und für xCode 11.4 aktualisiert werden. Dies kann auch im Kontext von Umgebungsobjekten verwendet werden.
import SwiftUI final class UserData: ObservableObject { @Published var selectedAddress: String? { willSet { UserDefaults.standard.set(newValue, forKey: Keys.selectedAddressKey) } } init() { selectedAddress = UserDefaults.standard.string(forKey: Keys.selectedAddressKey) } private struct Keys { static let selectedAddressKey = "SelectedAddress" } }
quelle
Wenn Sie struct in UserDefault nur mit Daten speichern möchten speichern .
Smaple struct
struct StudentData:Codable{ var id: Int? var name: String? var createdDate: String? // for decode the value init(from decoder: Decoder) throws { let values = try? decoder.container(keyedBy: codingKeys.self) id = try? values?.decodeIfPresent(Int.self, forKey: .id) name = try? values?.decodeIfPresent(String.self, forKey: .name) createdDate = try? values?.decodeIfPresent(String.self, forKey: .createdDate) } // for encode the value func encode(to encoder: Encoder) throws { var values = encoder.container(keyedBy: codingKeys.self) try? values.encodeIfPresent(id, forKey: .id) try? values.encodeIfPresent(name, forKey: .name) try? values.encodeIfPresent(createdDate, forKey: .createdDate) } }
Es gibt zwei Typen, die als Daten konvertiert werden können
Zuerst verwenden wir das Codable (Encodable and Decodable) , um die Struktur zu speichern
Beispiel für Wert speichern
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z") guard let data = try? JSONEncoder().encode(value) else { fatalError("unable encode as data") } UserDefaults.standard.set(data, forKey: "Top_student_record")
Wert abrufen
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else { // write your code as per your requirement return } guard let value = try? JSONDecoder().decode(StudentData.self, from: data) else { fatalError("unable to decode this data") } print(value)
Jetzt verwenden wir den PropertyListEncoder und den PropertyListDecoder , um die Struktur zu speichern
Beispiel für Wert speichern
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z") guard let data = try? PropertyListEncoder().encode(value) else { fatalError("unable encode as data") } UserDefaults.standard.set(data, forKey: "Top_student_record")
Wert abrufen
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else { // write your code as per your requirement return } guard let value = try? PropertyListDecoder().decode(StudentData.self, from: data) else { fatalError("unable to decode this data") } print(value)
Sie können den beliebigen Typ verwenden, um die Struktur in userDefault zu speichern.
quelle