I have a group of arrays that all contain a number of instances of a custom Task object that I have created. I am saving the arrays to NSUserDefaults as follows:
Custom Task Object
class Task:NSObject, NSCoding {
var name:String
var notes:String
var date:NSDate
var dateUse:Bool
var taskCompleted:Bool
init(name:String, notes:String, date:NSDate, dateUse:Bool, taskCompleted:Bool){
self.name = name
self.notes = notes
self.date = date
self.dateUse = dateUse
self.taskCompleted = taskCompleted
}
required init(coder decoder: NSCoder){
self.name = decoder.decodeObjectForKey("name") as! String
self.notes = decoder.decodeObjectForKey("notes") as! String
self.date = decoder.decodeObjectForKey("date") as! NSDate
self.dateUse = decoder.decodeObjectForKey("dateUse") as! Bool
self.taskCompleted = decoder.decodeObjectForKey("taskCompleted") as! Bool
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeObject(self.notes, forKey: "notes")
coder.encodeObject(self.date, forKey: "date")
coder.encodeObject(self.dateUse, forKey: "dateUse")
coder.encodeObject(self.taskCompleted, forKey: "taskCompleted")
}
}
Saving:
let defaults = NSUserDefaults(suiteName: "group.com.myGroupName")
let nowData = NSKeyedArchiver.archivedDataWithRootObject(nowTasks)
defaults!.setObject(nowData, forKey: "nowData")
Retrieving
let nowDataPull = defaults!.objectForKey("nowData") as? NSData
if let nowDataPull2 = nowDataPull{
let nowTasks2 = NSKeyedUnarchiver.unarchiveObjectWithData(nowDataPull2) as? [Task]
if let nowTasks21 = nowTasks2{
nowTasks = nowTasks21
}
}
The above method works fine for setting and retrieving data from the iPhone itself. However, this method does not work when trying to retrieve the data via the today extension.
When trying to retrieve from the today extension's .swift file I get the following errors:
Failed to inherit CoreMedia permissions from 52550: (null)
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver
decodeObjectForKey:]: cannot decode object of class (MyAppName.Task)
for key (NS.objects); the class may be defined in source code or a
library that is not linked'
I know that the extension can read the data because when I call:
if (defaults?.objectForKey("nowData") != nil){
print("there is data")
}
I get the printed response..
I can successfully save an Integer and retrieve via the today extension, but not objectForKey
I have tried various other saving methods including .plist files, but nothing seems to work. The same errors keep occurring. Any input would be greatly appreciated!
another way is to fix the name of the class used for NSCoding. You simply have to use:
NSKeyedArchiver.setClassName("Task", forClass: Task.self before serializing
NSKeyedUnarchiver.setClass(Task.self, forClassName: "Task") before deserializing
wherever needed.
Looks like iOSÂ extensions prefix the class name with the extension's name.
Set Object
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("iOS", forKey: "userNameKey")
defaults.setInteger(25, forKey: "Age")
defaults.setBool(true, forKey: "UseTouchID")
defaults.setDouble(M_PI, forKey: "Pi")
Reading
let defaults = NSUserDefaults.standardUserDefaults()
let name = defaults.stringForKey("userNameKey")
Workaround:
So I have found a workaround (which is actually more efficient) for my problem. Because the decoding of [Task] was causing problems, I decided to go ahead and retrieve the data from an array that did not have objects inside of it, but rather just NSDate() instances. Because of this I can now save and access the important aspects of data that the today extension needs. I urge anyone else who is having issues with custom class decoding to try and simplify your data if at all possible in order to retrieve and use it in your extension. I am not exactly sure what the problem was or is, but retrieving simple data from an array stored in NSUserDefaults works without problems.
Hope all of this nonsense can help someone else out there!
Related
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)")
}
}
I'm trying to store a custom class in user defaults but throughs an exception
terminating with uncaught exception of type NSException
My class is as follows: Transactions
import Foundation
import SwiftyJSON
class Transactions: NSObject,NSCoding {
var transOBJ = [JSON]()
init(transOBJ:[JSON]) {
self.transOBJ = transOBJ
}
required convenience init(coder aDecoder:NSCoder) {
let obj = aDecoder.decodeObject(forKey: "OBJ") as! [JSON]
self.init(transOBJ: obj)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(transOBJ, forKey: "OBJ")
}
}
The exception is throwing on aCoder.encode(transOBJ, forKey: "OBJ")
The code for saving in user defaults is:
let trans = [Transactions(transOBJ: self.transactionObjs)]
var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: trans)
userDefaults.set(encodedData, forKey: "transactions")
userDefaults.synchronize()
Update 1
I am calling the user defaults inside the Alamofire request function. Then it is giving an error
But when I call it outside Alamofire request function, then it runs just fine. What should I do?
I want to save it inside the success of alamofire but can't do it
Replace this code with your code then remove app and run again.
required init(coder aDecoder: NSCoder) {
transOBJ = = aDecoder.decodeObject(forKey: "OBJ") as! [JSON]
}
Thank you Tj3n. JSON also need to conform NSCoding. Just Transactions isn't enough. I created different variables for string and integers in the Transactions NSObject, extracted data from JSON, stored extracted data in the variables declared and then encoded and decoded them accordingly. It worked.
If there is another way, I'm all ears.
I'm introducing Realm into my swift project. I have a User class that I was saving an instance of into NSUserDefaults to keep track of the 1 logged in user.
After making User a subclass of Object, I get the following error when trying to unarchive (archiving seems to work OK):
Terminating app due to uncaught exception
'NSInvalidUnarchiveOperationException', reason: '***
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (RLMStandalone_User) for key (root); the class may be defined in
source code or a library that is not linked'
I have Realm installed as a Cocoapod, this are the relevant methods in the User clas
static var currentUser: User? {
get {
if let data = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaultKeys.kUserData) as? NSData,
let user = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? User {
return user
} else {
return nil
}
}
set {
if let user = newValue {
let data = NSKeyedArchiver.archivedDataWithRootObject(user)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: UserDefaultKeys.kUserData)
NSUserDefaults.standardUserDefaults().synchronize()
} else {
NSUserDefaults.standardUserDefaults().removeObjectForKey(UserDefaultKeys.kUserData)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
// MARK: NSCoding
convenience init?(coder decoder: NSCoder) {
self.init()
guard let firstName = decoder.decodeObjectForKey("firstName") as? String,
let lastName = decoder.decodeObjectForKey("lastName") as? String,
let email = decoder.decodeObjectForKey("email") as? String,
let icloud = decoder.decodeObjectForKey("icloudUserID") as? String,
let userType = decoder.decodeObjectForKey("userType") as? String
else {
return nil
}
self.firstName = firstName
self.lastName = lastName
self.email = email
self.profilePic = decoder.decodeObjectForKey("profilePic") as? String
self.icloudUserID = icloud
self.userType = userType
self.coverPhoto = decoder.decodeObjectForKey("coverPhoto") as? String
self.facebookID = decoder.decodeObjectForKey("facebookID") as? String
self.placeID = decoder.decodeObjectForKey("placeID") as? String
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.firstName, forKey: "firstName")
coder.encodeObject(self.lastName, forKey: "lastName")
coder.encodeObject(self.icloudUserID, forKey: "icloudUserID")
coder.encodeObject(self.userType, forKey: "userType")
coder.encodeObject(email, forKey: "email")
if let coverPhotoUrl = self.coverPhotoUrl {
coder.encodeObject(coverPhotoUrl, forKey: "coverPhoto")
}
if let profilePicUrl = self.profilePicUrl {
coder.encodeObject(profilePicUrl, forKey: "profilePic")
}
if let fbID = self.facebookID {
coder.encodeObject(fbID, forKey: "facebookID")
}
if let placeID = self.placeID {
coder.encodeObject(placeID, forKey: "placeID")
}
}
It's not possible to store a Realm Object in NSUserDefaults as (As you saw in that error message) they cannot be serialized or deserialized by the NSCoding protocol.
Instead, it might be better to add a primary key property to your User object (So you can use it to query that exact object from Realm), and store the primary key itself in NSUserDefaults instead.
Or better yet, instead of relying on NSUSerDefaults, it might be better to simply have a boolean property, isCurrent in your model, and using that to work out which user is the current one.
You should keep your NSUserDefaults and your Realm data separate. They are two different methods of persistent data storage. If you've converted something to a Realm Object, you no longer need to (or should) try and put that into NSUserDefaults.
How to delete data from NSUserDefaults? There is quite a few answers how to do it in Objective C, but how about Swift?
So I tried this:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.removeObjectForKey("myKey")
Didn't work. Maybe what I really want to delete is not NSUserDefaults?
This is how I save data:
class MySavedData: NSObject, NSCoding {
var image: String
init(name: String, image: String) {
self.image = image
}
required init(coder aDecoder: NSCoder) {
image = aDecoder.decodeObjectForKey("image") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(image, forKey: "image")
}
}
class ViewController: <...> {
var myData = [MySavedData]() //Later myData gets modified and then function save() is called
func save() {
let savedData = NSKeyedArchiver.archivedDataWithRootObject(myData)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(savedData, forKey: "myKey")
}
}
EDIT: Just to clear some things - data that is being saved is small (not even close to 100kb)
And maybe I am saving data not to NSUserDefaults (I am new to programming), so here is how I get it (load):
let defaults = NSUserDefaults.standardUserDefaults()
if let savedData = defaults.objectForKey("myData") as? NSData {
myData = NSKeyedUnarchiver.unarchiveObjectWithData(savedData) as! [UserLogin]
}
removeObjectForKey is the right way to go.
This will remove the value for the selected key. The following code sets a string value for a key in NSUserDefaults, prints it and then uses removeObjectForKey to remove and print the key value again. After removeObjectForKey the value is nil.
let prefs = NSUserDefaults.standardUserDefaults()
var keyValue = prefs.stringForKey("TESTKEY")
print("Key Value not set \(keyValue)")
let strHello = "HELLO WORLD"
prefs.setObject(strHello, forKey: "TESTKEY")
keyValue = prefs.stringForKey("TESTKEY")
print("Key Value \(keyValue)")
prefs.removeObjectForKey("TESTKEY")
keyValue = prefs.stringForKey("TESTKEY")
print("Key Value after remove \(keyValue)")
Returns:
Key Value not set nil
Key Value Optional("HELLO WORLD")
Key Value after remove nil
Update Swift 3:
let prefs = UserDefaults.standard
keyValue = prefs.string(forKey:"TESTKEY")
prefs.removeObject(forKey:"TESTKEY")
The code you have written will work fine, but NSUserDefaults synchronise at certain time interval.
As you want that should reflect in NSUserDefaults immediately ,so u need to write synchronise
let defaults = NSUserDefaults.standardUserDefaults()
defaults.removeObjectForKey("myKey")
defaults.synchronize()
Try This
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(NSBundle.mainBundle().bundleIdentifier!)
for Swift 3
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
But this will clear all values from NSUserDefaults.careful while using.
Removing UserDefaults for key in swift 3, based upon the top answer, just slightly different syntax:
UserDefaults.standard.removeObject(forKey: "doesContractExist")
Swift 4.x Remove all key in UserDefaults
let defaults = UserDefaults.standard
let dictionary = defaults.dictionaryRepresentation()
dictionary.keys.forEach
{ key in defaults.removeObject(forKey: key)
}
Use following for loop:
for key in NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys {
NSUserDefaults.standardUserDefaults().removeObjectForKey(key.description)
}
I would go for a solution which setting the value to nil for a key.
Swift 3
UserDefaults.standard.set(nil, forKey: "key")
Swift 2.x
NSUserDefaults.standardUserDefaults().setValue(nil, forKey: "key")
NOTE: that is a clear and straight statement, but bear in mind there is a limit to store information in NSUserDefaults, it is definitely not the right place to store large binary files (like e.g. images) – for that there is a Documents folder. however it is not defined how big the var image: String which you encode/decode.
To nuke all UserDefaults keys, you can use the following code in Swift 3.x:
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
In Swift 5.0, iOS 15 below single line of code is enough.
UserDefaults.standard.dictionaryRepresentation().keys.forEach(defaults.removeObject(forKey:))
Or try this
if let appDomain = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: appDomain)
}
func remove_pref(remove_key : String){
UserDefaults.standard.removeObject(forKey: remove_key)
UserDefaults.standard.synchronize()
}
Update code for Swift :
Used below line of code to Delete key values for NSUserDefaults in Swift
UserDefaults.standard.setValue(nil, forKey: "YouEnterKey")
What I'm trying to do is store my initial user defaults in a plist and then read them into NSUserDefaults when the application loads for the first time.
I've found a couple of posts which have helped me thus far however I can't seem to find an answer as to why I'm finding nil while unwrapping. Obviously I'm missing something, so my question is:
How do I properly register a plist to NSUserDefaults?
I've created a plist with my default settings. I'd like to read from the plist and register them into NSUserDefaults.
settings.plist
ViewController.swift
class ViewController: UIViewController {
let userDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let prefs = NSBundle.mainBundle().pathForResource("Settings", ofType: "plist")
let dict = NSDictionary(contentsOfFile: prefs!)
if let dictionary = dict {
println("Contents of file unwrapped: \(dictionary)") // <---------
let defaults : NSDictionary! = dictionary.valueForKey("Root") as? NSDictionary
println("defaults: \(defaults)") // <---------
userDefaults.registerDefaults(defaults as! [NSObject : AnyObject])
userDefaults.synchronize()
}
if let unwrapDict = dict {
var myValue = unwrapDict.objectForKey("Boolean Switch 1") as! Bool
println("pulled from pList \(myValue)")
}
}
}
I'm sure you've noticed that I am looking for the key 'Root' from my plist...
let defaults : NSDictionary! = dictionary.valueForKey("Root") as? NSDictionary
I have also tried passing it other values like "Boolean Switch 1" and casting to different types. But nothing I try changes the outcome.
Here's my console output from the two println() logs.
After a day of messing with this code I was able to finally get my plist entered into NSUserDefaults. I wasn't satisfied with the final outcome because I noticed that the registerDefaults wasn't actually doing anything.
So I decided to post my working code on Code Review to see if there were any parts of my code that weren't necessary. It turns out that registerDefualts indeed was not necessary, here's an excerpt from the Alex's answer to my question.
This line:
userDefaults.registerDefaults(dict as! [NSObject : AnyObject]) does
not actually set the information on the NSUserDefaults storage file on
disk. All it does is tell NSUserDefaults the default values to be used
when a key doesn't yet exist on disk.
So having said all of that, I'll post the working code. I should note, as it was pointed out to me by nhgrif, that the ViewController is really not the place to put this code and a better place is in the appDelegate in application(_:didFinishLaunchingWithOptions:).
ViewController:
class ViewController: UIViewController {
let userDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let prefs = NSBundle.mainBundle().pathForResource("Settings", ofType: "plist")
let dict = NSDictionary(contentsOfFile: prefs!)
userDefaults.setObject(dict, forKey: "defaults")
userDefaults.synchronize()
// this just toggles the aBool value
if let defaults = userDefaults.valueForKey("aBool") as? NSNumber {
if defaults as NSObject == 1 {
println("inside the conditional: \(defaults)")
userDefaults.setValue(0, forKey: "aBool")
userDefaults.synchronize()
println("Setting aBool to true")
} else {
userDefaults.setValue(1, forKey: "aBool")
userDefaults.synchronize()
println("setting aBool to false")
}
}
}
}
According to Apple document, If you don't change any value on settings, Values reading from NSUserDefaults are nil or 0. In your code, When you read a value from NSUserDefaults, you should set it a default value.