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>)
Related
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
I'm trying to build a cart with core data I have created 3 objects to model my data Product, Quantity, and CartItem which holds a product and a quantity and I save them as a transformable object in core data.
I have created a class for my xcdatamodeld that have a CartItem attribute
#objc(CartItemContainer)
class CartItemContainer: NSManagedObject {
#NSManaged var cartItemAttribute: CartItem
}
I'm saving to core data with no problems, but whenever I try to update the quantity using the code below it doesn't change it would still be 1.
static func changeQuantity(product: Product) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<CartItemContainer>(entityName: "CartItemContainer")
var results: [CartItemContainer] = []
do {
results = try context.fetch(fetchRequest)
if let productToChangeQuantityOn = results.first(where: {$0.cartItemAttribute.product.productID == product.productID}) {
let oldQuantity = productToChangeQuantityOn.cartItemAttribute.quantity.value
productToChangeQuantityOn.cartItemAttribute.quantity.value = oldQuantity + 1
try context.save()
context.refresh(productToChangeQuantityOn, mergeChanges: false)
}
}
catch {
print("error executing fetch request: \(error)")
}
}
I have tried updating it without calling context.refresh(productToChangeQuantityOn, mergeChanges: false)
it would change the quantity at run time, but when the app is relaunched the quantity would still be 1.
What am I missing here?
Any kind of help will be appreciated.
Update:
Here is how I set up Product for example. Quantity, and CartItem have the same set up.
class Product: NSObject, NSCoding, Decodable {
let productID: String
let category: String
let images: Dictionary<String, String>
let name: Dictionary<String, String>
let price: Dictionary<String, String>
func encode(with aCoder: NSCoder) {
aCoder.encode(productID, forKey: "productID")
aCoder.encode(category, forKey: "category")
aCoder.encode(images, forKey: "images")
aCoder.encode(name, forKey: "name")
aCoder.encode(price, forKey: "price")
}
required init?(coder aDecoder: NSCoder) {
self.productID = aDecoder.decodeObject(forKey: "productID") as! String
self.category = aDecoder.decodeObject(forKey:"category") as! String
self.images = aDecoder.decodeObject(forKey: "images") as! Dictionary<String, String>
self.name = aDecoder.decodeObject(forKey: "name") as! Dictionary<String, String>
self.price = aDecoder.decodeObject(forKey: "price") as! Dictionary<String, String>
}
}
Welcome to Core Data, Ahmed. I'm happy you got it working. Before you go, I'd like to suggest a more conventional data model which will probably work out better for you in the long run. No transformables are needed.
Broadly, the reasons are given in the first page of Apple's Core Data Programming Guide states 11 features of Core Data, listed as bullet points. By using transformable attributes instead of relationships, I'd say you are only fully realizing the advantages of the first and fifth bullet points.
Regarding your particular design, I assume that the same product can be in many Carts. By giving CartItem a product attribute of type transformable, this same product must be somehow reproduced in each cart. If you want to change the attributes of a product, in your design, you must find all the carts with this product in it and change each one. With the conventional design, you just change the Product object. Your design requires more code (which is always bad), and your users' devices will use more resources and be slower executing your code.
Last but not least, you don't want other engineers to be scratching their heads when they look at your code or data model :) And you want to be able learn from Apple documentation, tutorials, blog posts, stackoverflow answers and sample code you find on the internet. These resources are not as applicable if you are doing things in a non-conventional way.
Transformable attributes are a immutable type, therefore cannot be changed. The only way of changing them is by creating a new object and saving it again to core data.
To solve this I deleted the property cartItemAttribute from my xcdatamodel which holds both a product and a quantity as one transformable attribute. I replaced it with a product attribute of type transformable and a quantity attribute of type Int and everything works fine now.
Here is my updated code
static func changeQuantity(product: Product) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<CartItemContainer>(entityName: "CartItemContainer")
do {
let results = try context.fetch(fetchRequest)
if let productToChangeQuantityOn = results.first(where: {$0.product.productID == product.productID}) {
let oldQuantity = productToChangeQuantityOn.quantity
productToChangeQuantityOn.quantity = oldQuantity + 1
try context.save()
context.refresh(productToChangeQuantityOn, mergeChanges: false)
}
}
catch {
print("error executing fetch request: \(error)")
}
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I got a task to store multiple user details.
#IBOutlet weak var userName: UITextField!
#IBOutlet weak var email_id: UITextField!
#IBOutlet weak var password: UITextField!
I know in user defaults we can store a value for a particular key and retrieve it but i would like to know how to save multiple values. like the key should be the email_id i have mentioned above and all the three fields should be its values, also i need to maintain all the details even if there is n number of users.
You can save textField Data in dictionary as a key-value and append that dictionary to Array.So you will get the list of users detail.It will not override, every time it make empty dictionary after click on save Button and before append the data.
like that:
class ViewController: UIViewController {
var userDict = [String:Any]()
var userArray = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
}
and save the data after save button click.
#IBAction func SaveBtnTapped(_ sender: Any) {
userDict = [:]
userDict.updateValue(userName.text, forKey: "username")
userDict.updateValue(email_id.text, forKey: "email")
userDict.updateValue(password.text, forKey: "password")
userArray.append(userDict)
}
The best way to do it in this situation is to use a local database, like sqlite. You can follow this tutorial for dealing with a local database. In your situation, you will need a user table to store your users.
If you prefer not to use a local database and use UserDefault instead, you can indeed store an array of objects in UserDefault. Simply use the following code
UserDefaults.standard.set([YourUserObj], forKey: "users")
let userArr = UserDefaults.standard.array(forKey: "users") as? [YourUserObj]
1.You can store multiple values using tuples.
Rule: A default object must be a property list—that is, an instance
of (or for collections, a combination of instances of): NSData,
NSString, NSNumber, NSDate, NSArray, or NSDictionary
If you want to store any other type of object, you should typically archive it to create an instance of NSData.
// you can create NSObject model to store into userdefaults
let users = [User(username: "user1", email: "user1#xxx.com", password: "******"), User(username: "user2", email: "user2#xxx.com", password: "******")]
var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: teams)
userDefaults.set(encodedData, forKey: "users")
userDefaults.synchronize()
Get data by unarchiving like below
let decodedUserDetail = userDefaults.object(forKey: "users") as! Data
let decodedUsers= NSKeyedUnarchiver.unarchiveObject(with: decodedUserDetail) as! [Team]
print(decodedUsers)
2.You can store multiple values using Array of objects.
let userDetails =
[
"username" : "user1",
"email": "user1#xxx.com",
"password" : "******"
]
before storing this get existing details
let userDefault = NSUserDefaults.standardUserDefaults()
var storedUserDetails = userDefault.objectForKey("userDetails") as? [[String:AnyObject]]
storedUserDetails.append(userDetails )
userDefault.setObject(storedCredentials, forKey: "credentials")
userDefault.synchronize()
Create a model
Class User: NSObject, NSCoding{
var userName: String
var email_id: String
var password: String
init(userName: String, email_id: String, password: String){
self.userName = userName
self.email_id = email_id
self.password = password
}
required conveniece init(coder decoder: NSCoder){
let userName = decoder.decodeObject(forKey: "userName") as? String ?? ""
let email_id = decoder.decodeObject(forKey: "email_id") as? String ?? ""
let password = decoder.decodeObject(forKey: "password") as? String ?? ""
self.init(
userName=userName,
email_id=email_id,
password=password
)
}
func encode(with coder: NSCoder){
coder.encode(self.userName, forKet="userName")
coder.encode(self.email_id, forKet="email_id")
coder.encode(self.password, forKet="password")
}
}
After you can instanciate your model with your data
let currentUser = User(userName: userName.text!, email_id: email_id.text!, password: password.text!)
Or a list :
let listUsers = [User(userName: "user_1", email_id: "email#abc.com", password: "password")]
Now you can store the currentUser or listUsers with archivedData
let encodedData = NSKeyedArchiver.archivedData(withRootObject: currentUser)
UserDefaults.standard.set(encodedData, forKey: "currentUser")
UserDefaults.standard.synchronize()
let encodedDataForList = NSKeyedArchiver.archivedData(withRootObject: listUsers)
UserDefaults.standard.set(encodedData, forKey: "listUsers")
UserDefaults.standard.synchronize()
To get the stored user :
if let data = UserDefaults.standard.data(forKey: "currentUser"){
let _currentUser = (NSKeyedUnarchiver.unarchiveObject(with: data) as? User)!
}
For saving user's data, using local db is the most appropriate solution.But if you don't want to go into depth of Core data / Sqlite, use Realm framework.
With Realm you just have to have a user class with dynamic properties & call basic functions like
Initialize RealmDb with
let objRealm = try! Realm()
Create a user class
class UserDataModel: Object {
dynamic var name:String = ""
dynamic var ID = ""
// MARK : Primary Key
override static func primaryKey() -> String? {
return "ID"
}
}
Add user into DB with write function
let obj = UserDataModel(value: ["name": userName,
"ID": userID])
try! objRealm.write {
objRealm.add(obj)
}
Read object from Realm DB
return objRealm.objects(UserDataModel.self)
I have created two types of custom objects:
The first object, called Contact, has 4 fields which store Strings.
The second object, called ContactList, has 1 field which stores a list of my first object [Contact].
In my View Controller, I have an instance of a ContactList that I would like to save into User Defaults. Looking through other questions, I have added the following methods into the Contact class (as well as making it inherit from NSObject, and NSCoding)
required init(coder aDecoder: NSCoder) {
self.firstName = (aDecoder.decodeObject(forKey: "first") as? String)!
self.lastName = (aDecoder.decodeObject(forKey: "last") as? String)!
self.phoneNumber = (aDecoder.decodeObject(forKey: "phone") as? String)!
self.email = (aDecoder.decodeObject(forKey: "email") as? String)!
}
func encode(with aCoder: NSCoder){
aCoder.encode(self.firstName, forKey: "first")
aCoder.encode(self.lastName, forKey: "last")
aCoder.encode(self.phoneNumber, forKey: "phone")
aCoder.encode(self.email, forKey: "email")
}
And then in my ContactList class, I have added two functions:
func saveData(){
let data = NSKeyedArchiver.archivedData(withRootObject: list)
let defaults = UserDefaults.standard
defaults.set(data, forKey:"contacts" )
}
func retrieveData(){
if let data = UserDefaults.standard.object(forKey: "contacts") as? NSData
{
list = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as! [Contact]
}
}
In my View Controller, I'm calling saveData() on my current instance of ContactList. In my ViewDidLoad method, I have an assignment statement to my variable which holds the instance of this by creating a new instance and then calling retrieveData() on it.
However, when I run my program and add elements to the list in ContactList's list field, exit out, and then come back into the app, the elements that I added is not there (I have a table that updates and displays the contents of the list in the ContactList).
Am I supposed to have ContactList inherit something, or am I just implementing these methods wrong? This is the first time that I'm using UserDefaults, so any help would be greatly appreciated!
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.