Where to store user data persistently across app updates (iOS)? - ios

I am writing an app that contains a local database. I would like to give the user the possibility to bookmark some items in this database, and that these bookmarks do not disappear every time the app (and thus the database) is updated. What is the best solution in this case?

Simplest method of persisting data across app restarts is by using UserDefaults. In your case you can store custom data types in a UserDefaults key as follows:
Init defaults object
let defaults = UserDefaults.standard
Create a custom data type for your data (needs to be Codable)
struct Bookmark: Codable {
let name: String
let url: String
}
struct Bookmarks: Codable {
let bookmarks: [Bookmark]
}
Save data as follows
// data is of type Bookmarks here
if let encodedData = try? JSONEncoder().encode(data) {
defaults.set(encodedData, forKey: "bookmarks")
}
Retrieve data later
if let savedData = defaults.object(forKey: "bookmarks") as? Data {
if let savedBookmarks = try? JSONDecoder().decode(Bookmarks.self, from: savedData) {
print("Saved user: \(savedBookmarks)")
}
}
More info here and here

Related

Trying to save an array into UserDefaults

In my code, data is an array of format [String : Any], I'm trying to store it in userdefaults
let userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: data)
userDefaults.set(encodedData, forKey: "data")
userDefaults.synchronize()
but I get this error:
-[Features encodeWithCoder:]: unrecognized selector sent to instance
what I'm doing wrong?
You can only store several types of objects in UserDefaults like Int, String, Float, Double, Bool, URL, Data or collection of these types. However according to apple:
If you want to store any other type of object, you should typically
archive it to create an instance of NSData.
Luckily Apple released Codable so any class or struct conforming to Codable can be converted to and from Data.
First conform your dataModel to Codable
struct Features : Codable {
var feature1:String?
var feature2:String?
}
then after you create you objects and add them to the array
let feature1 = Features(feature1: "awesome", feature2: "World")
let feature2 = Features(feature1: "Hello", feature2: "World")
arrayOfFeatures = [feature1,feature2]
you can use save them like this:
func saveInUserDefault(with arrayOfFeatures:[Features]){
do{
UserDefaults.standard.set(try PropertyListEncoder().encode(arrayOfFeatures), forKey: "data")
UserDefaults.standard.synchronize()
}
catch
{
print(error.localizedDescription)
}
}
and get them with like this:
func getDataFromArray(){
if let savedData: Data = UserDefaults.standard.data(forKey: "data"){
do
{
arrayOfFeatures = try PropertyListDecoder().decode([Features].self, from: savedData)
for feature in arrayOfFeatures
{
print(feature.feature1)
print(feature.feature2)
}
}
catch
{
print(error.localizedDescription)
}
}
}
saveInUserDefault(with: arrayOfFeatures)

How to save a dictionary (or any other complex structure) into iCloud using Cloudkit?

I need to bind a date to a value and save it into Cloudkit. Rather than just save a new CKRecord for every object and subscript "date" and "value", I would prefer that the CKRecord is an array of dictionary: [(date, value)]. I've looked around and can't find any examples of this type of Cloudkit data storage. I would have thought by now that Codable would have been bridged over to Cloudkit but I don't see anything indicating that. There is a library that handles this but it doesn't handle this type of nesting. Is there anyway to do this?
Although CKRecord does have the ability to support an Array, it is primarily for storing simple data types like strings, numbers, and CKRecord.Reference. You don't specifically call out what your 'value' type is, but here is an example of using JSONEncoder/JSONDecoder to add support for writing/reading any codable type to a CKRecord. The encoder/decoder is simply converting the Encodable/Decodable type to/from a binary Data representation, which CKRecord also supports.
private let encoder: JSONEncoder = .init()
private let decoder: JSONDecoder = .init()
extension CKRecord {
func decode<T>(forKey key: FieldKey) throws -> T where T: Decodable {
guard let data = self[key] as? Data else {
throw CocoaError(.coderValueNotFound)
}
return try decoder.decode(T.self, from: data)
}
func encode<T>(_ encodable: T, forKey key: FieldKey) throws where T: Encodable {
self[key] = try encoder.encode(collection)
}
}
Usage would look like the following:
let collection: [[Date: String]] = [[:]]
let record = CKRecord(recordType: "MyRecord")
try? record.encode(collection, forKey: "collection")
let persisted = try? record.decode(forKey: "collection") as [[Date: String]]

Save a Codable to UserDefaults and matching the look of NSCoding

I have a struct called Station that supports the Codable protocol. Since it is a struct, it cannot support the NSCoding protocol. I would like to save an array of these Stations to the UserDefaults. I have code that does it without any problems.
func updateRecentStations(stations: [Station]) {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try! encoder.encode(stationsArray)
UserDefaults.standard.set(data, forKey: "RecentStations")
}
func readRecentStations() -> [Station] {
if let data = UserDefaults.standard.object(forKey: "RecentStations") as? Data {
let stations: [Station] = try! PropertyListDecoder().decode( [Station].self, from: data)
return stations
}
}
These both work. The roundtrip is happening without a problem. But if I examine the <<app>>.plist in Xcode, or call print(Array(UserDefaults.standard.dictionaryRepresentation())) the displayed data is illegible. The Data that has been written looks like it is stored in a hexadecimal format. That said, it I open the <<app>>.plist within a text editor, I can see the xml within there, but the .plist isn't meant to be read that way.
The UserDefaults does not appear to have support for writing a plist to the .plist. Everything works, but because the plist editor can't read what I've written, I feel I am not doing things in the proper way.
Is there a way to support a Codable being written to the UserDefaults in such a way that it maintains its structure in a human readable way within there? To be clear, I am looking for a way that replicates the way NSCoding would add to the NSUserDefaults hierarchy.
You are storing Data in UserDefaults. That will get encoded as a base64 string which is why it isn't human readable.
Since you are encoding to an XML format, the resulting Data will actually be the data of a string representing the XML. So create a String from data. Then store that string in UserDefaults. Then the XML will be readable when viewing the UserDefaults plist file.
Note: the following hasn't been tested. There may be typos.
func updateRecentStations(stations: [Station]) {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try! encoder.encode(stationsArray)
let xml = String(data: data, encoding: .utf8)!
UserDefaults.standard.set(xml, forKey: "RecentStations")
}
func readRecentStations() -> [Station] {
if let xml = UserDefaults.standard.object(forKey: "RecentStations") as? String {
let data = xml.data(using: .utf8)!
let stations: [Station] = try! PropertyListDecoder().decode( [Station].self, from: data)
return stations
}
}

can UserDefaults continue save new data after each .synchronize()?

I wrote an app includes a main program A and an action extension B.
Users can use action extension B to save some data into UserDefaults.
like this:
let defaults = UserDefaults(suiteName: "group.com.ZeKai")
defaults?.set(self.doubanID, forKey: "doubanID")
defaults?.set(self.doubanRating, forKey: "doubanRating")
defaults?.set(self.imdbRating, forKey: "imdbRating")
defaults?.set(self.rottenTomatoesRating, forKey: "rottenTomatoesRating")
defaults?.set(self.chineseTitle, forKey: "chineseTitle")
defaults?.set(self.originalTitle, forKey: "originalTitle")
defaults?.synchronize()
Wating to transfer this data to main program A while A is opened.
But if I use action extension twice to save data like data1 and data2, then I open the main program A, only data2 is received by A, means always new data overwrite the new data in UserDefaults.
so, I would like to know whether I can save multiple data in UserDefaults, and they will be all transferred to main program A?
Thanks in advance...
To save multiple data to UserDefaults you should use unique keys for that data. Also read about synchronization:
Because this method is automatically invoked at periodic intervals,
use this method only if you cannot wait for the automatic
synchronization (for example, if your application is about to exit) or
if you want to update the user defaults to what is on disk even though
you have not made any changes.
If the data is descriptive as in case you describe. Try to use structures to represent models.
Example of the user struct:
public struct User {
public var name: String
init(name: String) {
self.name = name
}
public init(dictionary: Dictionary<String, AnyObject>){
name = (dictionary["name"] as? String)!
}
public func encode() -> Dictionary<String, AnyObject> {
var dictionary : Dictionary = Dictionary<String, AnyObject>()
dictionary["name"] = name as AnyObject?
return dictionary
}
}
Usage of the structures and UserDefaults.
let user1 = User(name: "ZeKai").encode()
let user2 = User(name: "Oleg").encode()
let defaults = UserDefaults(suiteName: "User")
defaults?.set(user1, forKey: "User1")
defaults?.set(user2, forKey: "User2")
defaults?.synchronize()
let user1EncodeData = defaults?.dictionary(forKey: "User1")
let user = User(dictionary: user1EncodeData as! Dictionary<String, AnyObject>)

Swift - How to get last insert id in Core Data, store it in NSUserDefault and predicate

I already create a new entity and save it. Then I want to grab last insert id from first entity then save it into second entity as a reference id.
let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let userContext: NSManagedObjectContext = appDel.managedObjectContext!
let userEn = NSEntityDescription.entityForName("UserEntity", inManagedObjectContext: userContext)
var newUser = Users(entity: userEn!, insertIntoManagedObjectContext: userContext)
newUser.uname = "Nurdin"
userContext.save(nil)
Then how to save last insert id into NSUserDefault? Because I want to predicate with existing data inside CoreData for matching purpose.
you magically ask core data for the latest id...
but for your example. just add a line after the save to store the ID...
let string = user.objectID.URIRepresentation().absoluteString
NSUserDefaults.standardUserDefaults().setObject(string, forKey: "userID")
... vice-versa ...
let oidString = NSUserDefaults.standardUserDefaults().stringForKey("userID")
let url = NSURL(string: oidString)
let objectID = persistentStoreCoordinator.managedObjectIDForURIRepresentation(url)
Swift 5
There is no such feature in core data to get the last saved Id, but you can achive through NSUserDeafult
here we have created two function for save and retrive the last saved ID.
fileprivate func getLastId() -> Int {
let objUserDefault = UserDefaults.standard
if let userID = objUserDefault.value(forKey: "TaskId") as? Int {
print(userID + 1)
return userID + 1
}
return 1
}
fileprivate func setTaskId(id: Int) {
let objUserDefault = UserDefaults.standard
objUserDefault.setValue(id, forKey: "TaskId")
objUserDefault.synchronize()
}
In getLastId() we are retriving the id and increment by 1 for saving current record. If it saved for first time it will give you 1.
In setTaskId() we are just storing the updated value.

Resources