c'est une classe Place
j'ai défini:
class Place: NSObject {
var latitude: Double
var longitude: Double
init(lat: Double, lng: Double, name: String){
self.latitude = lat
self.longitude = lng
}
required init(coder aDecoder: NSCoder) {
self.latitude = aDecoder.decodeDoubleForKey("latitude")
self.longitude = aDecoder.decodeDoubleForKey("longitude")
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(latitude, forKey: "latitude")
aCoder.encodeObject(longitude, forKey: "longitude")
}
}
Voici comment j'ai essayé d'enregistrer un tableau de Place
s:
var placesArray = [Place]
//...
func savePlaces() {
NSUserDefaults.standardUserDefaults().setObject(placesArray, forKey: "places")
println("place saved")
}
Cela n'a pas fonctionné, voici ce que j'obtiens sur la console:
Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')
Je suis nouveau sur iOS, pourriez-vous m'aider?
DEUXIÈME ÉDITION
J'ai trouvé une solution pour sauvegarder les données:
func savePlaces(){
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
NSUserDefaults.standardUserDefaults().setObject(myData, forKey: "places")
println("place saved")
}
Mais j'obtiens une erreur lors du chargement des données avec ce code:
let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData
if placesData != nil {
placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData!) as [Place]
}
l'erreur est:
[NSKeyedUnarchiver decodeDoubleForKey:]: value for key (latitude) is not a double number'
Je suis sûr d'avoir archivé un Double, il y a un problème avec le processus de sauvegarde/chargement
Un indice?
À partir du Guide de programmation de la liste des propriétés :
Si un objet de liste de propriétés est un conteneur (c'est-à-dire un tableau ou un dictionnaire), tous les objets qu'il contient doivent également être des objets de liste de propriétés. Si un tableau ou un dictionnaire contient des objets qui ne sont pas des objets de liste de propriétés, vous ne pouvez pas enregistrer et restaurer la hiérarchie de données à l'aide des différentes méthodes et fonctions de liste de propriétés.
Vous devrez convertir l'objet vers et depuis une instance NSData
à l'aide de NSKeyedArchiver
et NSKeyedUnarchiver
.
Par exemple:
func savePlaces(){
let placesArray = [Place(lat: 123, lng: 123, name: "hi")]
let placesData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
NSUserDefaults.standardUserDefaults().setObject(placesData, forKey: "places")
}
func loadPlaces(){
let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData
if let placesData = placesData {
let placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData) as? [Place]
if let placesArray = placesArray {
// do something…
}
}
}
Swift 4
Nous devons sérialiser notre objet Swift
pour l'enregistrer dans userDefaults
.
Dans Swift 4, nous pouvons utiliser le protocole Codable, ce qui nous facilite la vie sur la sérialisation et l'analyse JSON
Workflow (Enregistrer Swift dans UserDefaults):
Workflow (Get Swift object from UserDefaults):
Code Swift 4:
class Place: Codable {
var latitude: Double
var longitude: Double
init(lat : Double, long: Double) {
self.latitude = lat
self.longitude = long
}
public static func savePlaces(){
var placeArray = [Place]()
let place1 = Place(lat: 10.0, long: 12.0)
let place2 = Place(lat: 5.0, long: 6.7)
let place3 = Place(lat: 4.3, long: 6.7)
placeArray.append(place1)
placeArray.append(place2)
placeArray.append(place3)
let placesData = try! JSONEncoder().encode(placeArray)
UserDefaults.standard.set(placesData, forKey: "places")
}
public static func getPlaces() -> [Place]?{
let placeData = UserDefaults.standard.data(forKey: "places")
let placeArray = try! JSONDecoder().decode([Place].self, from: placeData!)
return placeArray
}
}
Voici l'exemple de code complet dans Swift 3 & 4.
import Foundation
class Place: NSObject, NSCoding {
var latitude: Double
var longitude: Double
var name: String
init(latitude: Double, longitude: Double, name: String) {
self.latitude = latitude
self.longitude = longitude
self.name = name
}
required init?(coder aDecoder: NSCoder) {
self.latitude = aDecoder.decodeDouble(forKey: "latitude")
self.longitude = aDecoder.decodeDouble(forKey: "longitude")
self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(latitude, forKey: "latitude")
aCoder.encode(longitude, forKey: "longitude")
aCoder.encode(name, forKey: "name")
}
}
func savePlaces() {
var placesArray: [Place] = []
placesArray.append(Place(latitude: 12, longitude: 21, name: "place 1"))
placesArray.append(Place(latitude: 23, longitude: 32, name: "place 2"))
placesArray.append(Place(latitude: 34, longitude: 43, name: "place 3"))
let placesData = NSKeyedArchiver.archivedData(withRootObject: placesArray)
UserDefaults.standard.set(placesData, forKey: "places")
}
func loadPlaces() {
guard let placesData = UserDefaults.standard.object(forKey: "places") as? NSData else {
print("'places' not found in UserDefaults")
return
}
guard let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [Place] else {
print("Could not unarchive from placesData")
return
}
for place in placesArray {
print("")
print("place.latitude: \(place.latitude)")
print("place.longitude: \(place.longitude)")
print("place.name: \(place.name)")
}
}
savePlaces()
loadPlaces()
place.latitude: 12.0
place.longitude: 21.0
place.name: 'place 1'
place.latitude: 23.0
place.longitude: 32.0
place.name: 'place 2'
place.latitude: 34.0
place.longitude: 43.0
place.name: 'place 3'
Dans Swift 4.0+, nous pouvons utiliser l'alias de type Codable qui se compose de 2 protocoles: Decodable & Encodable.
Pour plus de commodité, j'ai créé un décodage générique et des méthodes de codage dont le type est contraint à Codable
:
extension UserDefaults {
func decode<T : Codable>(for type : T.Type, using key : String) -> T? {
let defaults = UserDefaults.standard
guard let data = defaults.object(forKey: key) as? Data else {return nil}
let decodedObject = try? PropertyListDecoder().decode(type, from: data)
return decodedObject
}
func encode<T : Codable>(for type : T, using key : String) {
let defaults = UserDefaults.standard
let encodedData = try? PropertyListEncoder().encode(type)
defaults.set(encodedData, forKey: key)
defaults.synchronize()
}
}
Utilisation - sauvegarde d'un objet/tableau/dictionnaire:
Disons que nous avons un objet personnalisé:
struct MyObject: Codable {
let counter: Int
let message: String
}
et nous en avons créé une instance:
let myObjectInstance = MyObject(counter: 10, message: "hi there")
En utilisant l'extension générique ci-dessus, nous pouvons maintenant enregistrer cet objet comme suit:
UserDefaults.standard.encode(for: myObjectInstance, using: String(describing: MyObject.self))
Enregistrement d'un tableau du même type:
UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject.self))
Enregistrement d'un dictionnaire avec ce type:
let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject.self))
Utilisation - chargement d'un objet/tableau/dictionnaire:
Chargement d'un seul objet:
let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject.self))
Chargement d'un tableau du même type:
let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject.self))
Chargement d'un dictionnaire avec ce type:
let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject.self))
Vous avez préparé Place pour l'archivage, mais vous supposez maintenant qu'un tableau de Place sera archivé automatiquement lorsque vous le stockez dans NSUserDefaults. Ce ne sera pas le cas. Vous devez l'archiver. Le message d'erreur vous le dit. Les seules choses qui peuvent être enregistrées dans NSUserDefaults sont des objets avec des types de liste de propriétés: NSArray, NSDictionary, NSString, NSData, NSDate et NSNumber. Un objet Place n'en fait pas partie.
Au lieu d'essayer d'enregistrer le tableau directement, l'archivez . Maintenant, c'est un NSData - et c'est l'un des types d'objets de liste de propriétés:
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
Vous pouvez maintenant stocker myData
dans NSUserDefaults, car il s'agit d'un NSData. Bien sûr, lorsque vous le retirez à nouveau, vous devrez également le désarchiver pour le transformer d'un NSData en un tableau de Place.
EDIT : Soit dit en passant, il me semble, après coup, que votre classe Place peut avoir à adopter explicitement le protocole NSCoding pour que cela fonctionne. Vous semblez avoir omis cette étape.
Voici le code que j'utilise. J'espère que cela vous aidera.
Considérez Sport est l'objet. Au début, nous devons confirmer la classe Codable à la classe d'objet comme ci-dessous.
class Sport: Codable {
var id: Int!
var name: String!
}
Ensuite, nous pourrions simplement utiliser le code suivant pour enregistrer le tableau de l'objet à l'aide de UserDefaults.
func saveSports(_ list:[Sport]) {
UserDefaults.standard..set(try? PropertyListEncoder().encode(list), forKey:"KEY")
UserDefaults.standard..synchronize()
}
Maintenant, nous pouvons simplement passer la liste des objets en tant que paramètre et enregistrer en utilisant la méthode saveSports ()
Pour récupérer les données enregistrées, nous pourrions utiliser le code suivant.
func getSports() -> [Sport]? {
if let data = UserDefaults.standard.value(forKey:"KEY") as? Data {
let decodedSports = try? PropertyListDecoder().decode([Sport].self, from: data)
return decodedSports
}
return nil
}
La méthode getSports () renvoie les données enregistrées.