When I try to save to NSUserDefaults by adding a setter to my class variable (in this case the id and authToken variables, the values don't seem to be saved. When I run with a breakpoint on, the getter of id and authToken always return nil even after setting them with a value.
class CurrentUser {
static let defaultInstance = CurrentUser()
func updateUser(id id: String, authToken: String) {
self.id = id
self.authToken = authToken
}
var authToken: String? {
get {
if let authToken = NSUserDefaults.standardUserDefaults().objectForKey("userAuthToken") {
return (authToken as! String)
} else {
return nil
}
}
set {
NSUserDefaults.standardUserDefaults().setObject(authToken, forKey: "userAuthToken")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
var id: String? {
get {
if let id = NSUserDefaults.standardUserDefaults().objectForKey("userId") {
return (id as! String)
} else {
return nil
}
}
set {
NSUserDefaults.standardUserDefaults().setObject(id, forKey: "userId")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
However, when I pull the lines out to the updateUser function (one level higher), all works as expected.
class CurrentUser {
static let defaultInstance = CurrentUser()
func updateUser(id id: String, authToken: String) {
NSUserDefaults.standardUserDefaults().setObject(id, forKey: "userId")
NSUserDefaults.standardUserDefaults().synchronize()
NSUserDefaults.standardUserDefaults().setObject(authToken, forKey: "userAuthToken")
NSUserDefaults.standardUserDefaults().synchronize()
}
var authToken: String? {
get {
if let authToken = NSUserDefaults.standardUserDefaults().objectForKey("userAuthToken") {
return (authToken as! String)
} else {
return nil
}
}
}
var id: String? {
get {
if let id = NSUserDefaults.standardUserDefaults().objectForKey("userId") {
return (id as! String)
} else {
return nil
}
}
}
}
Why would this be? What am I missing? Does the { set } run on a different thread / mode where NSUserDefaults isn't accessible?
You must use newValue inside set method, value is still nil, or use didSet and then you can use variable.
As Alex said, you must use newValue in set method. Moreover, you can refer to this link for more detail:
Store [String] in NSUserDefaults (Swift)
Related
I want to set or retrieve value from UserDefaults that is an optional Int, meaning it is either nil or set to a value. I am confused in the setter however. In the code below, I set it to either nil or an integer. I am not sure if this is correct. Is there a better or cleaner way (assuming if at all what I am doing is correct)?
public var selectedOption:myOption? {
get {
if let opt = UserDefaults.standard.object(forKey: "myKey") as? Int {
if let val = myOption(rawValue: opt) {
return val
}
}
return nil
}
set {
if let newVal = newValue {
UserDefaults.standard.set(newVal.rawValue, forKey:"myKey")
} else {
UserDefaults.standard.set(nil, forKey:"myKey")
}
}
}
You can reduce the setter
set {
UserDefaults.standard.set(newValue?.rawValue, forKey:"myKey")
}
Due to optional chaining nil is written out if newValue is nil.
public var selectedOption:myOption? {
get {
if let opt = UserDefaults.standard.object(forKey: "myKey") as? Int {
return myOption(rawValue: opt)
}
return nil
}
set {
UserDefaults.standard.set(newValue?.rawValue, forKey: "myKey")
}
}
Or
Confirm to codable protocol and store the enum instance in UserDefaults
enum myOption: Int, Codable {
case a
case b
}
public var selectedOption1:myOption? {
get {
if let storedObject: Data = UserDefaults.standard.object(forKey: "myKey") as? Data {
return try? PropertyListDecoder().decode(myOption.self, from: storedObject)
}
return nil
}
set {
UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "myKey")
}
}
I'm trying to save a custom class array to UserDefaults but it doesn't work. I get nil back on if let. I looked everywhere online. I'm using Swift 4.2
extension UserDefaults {
func saveReciters(_ reciters: [Reciter]) {
do {
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: reciters, requiringSecureCoding: false)
self.set(encodedData, forKey: UD_RECITERS)
} catch {
debugPrint(error)
return
}
}
func getReciters() -> [Reciter] {
if let reciters = self.object(forKey: UD_RECITERS) as? Data {
return NSKeyedUnarchiver.unarchiveObject(with: reciters) as! [Reciter]
} else {
print("EMPTY RECITERS")
return [Reciter]()
}
}
}
UserInfo={NSDebugDescription=Caught exception during archival: -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600001babcc0
Thats my class:
class Reciter: NSCoding {
private(set) public var name: String
private(set) public var image: UIImage?
private(set) public var surahs: [Surah]
private(set) public var documentID: String
private let quranData = QuranData()
init(name: String, image: UIImage?, surahCount: Int?, documentID: String) {
self.name = name
self.image = image
self.documentID = documentID
if let surahCount = surahCount {
surahs = Array(quranData.getAllSurahs().prefix(surahCount))
} else {
surahs = quranData.getAllSurahs()
}
}
func encode(with aCoder: NSCoder) {
}
required init?(coder aDecoder: NSCoder) {
}
}
On my Surah class i get nil back. All other properties i get back succesfully
Most often I see developer's use codeable, here I am using user as an example:
YourDataModel.swift
struct User: Codable {
var userId: String = ""
var name: String = ""
var profileImageData: Data? }
UserDefaults.swift
import Foundation
extension UserDefaults {
/// The current user of the application, see `./Models/User.swift`
var currentUser: User? {
get {
guard let userData = self.object(forKey: #function) as? Data else { return nil }
return try? JSONDecoder().decode(User.self, from: userData)
}
set {
guard let newuser = newValue else { return }
if let userData = try? JSONEncoder().encode(newuser) {
self.set(userData, forKey: #function)
}
}
}
}
Transform the data into json data... #function is the function or value name i.e.
// For the case the user doesn't yet exist.
if ( UserDefaults.standard.currentUser == nil ) {
// Create a new user
user = User()
// Generate an id for the user, using a uuid.
user?.userId = UUID().uuidString
} else {
// otherwise, fetch the user from user defaults.
user = UserDefaults.standard.currentUser
}
I'm trying to save dictionary into UserDefaults And then I want to fetch data or delete.
class userDefaultsManager {
static func getAllUsers()->[UserModel]{
if let all = UserDefaults.standard.array(forKey: "usersKey") as? [Dictionary<String,Any>] {
return all.map{UserModel.init(dictionary: $0)}
}
return []
}
static func insertUser(name:String, email:String)->Bool {
let newUserModel = UserModel.init(name: name, email: email)
var all = getAllUser()
all.append(newUserModel)
UserDefaults.standard.set(all.map{$0.dictionary}, forKey: "usersKey")
return UserDefaults.standard.synchronize()
}
static func deleteUser(email:String)->Bool {
var all = getAllUser()
let index = all.index{$0.email == email}
if index != nil {
all.remove(at: index!)
UserDefaults.standard.set(all.map{$0.dictionary}, forKey: "usersKey")
UserDefaults.standard.synchronize()
return true
} else {
return false
}
}
}
class UserModel:NSObject{
var name: String!
var email: String!
init(name:String, email:String) {
self. name = name
self. email = email
super.init()
}
init(dictionary:[String:Any]) {
self.name = ""
self.email = ""
super.init()
self.setValuesForKeys(dictionary)
}
var dictionary:[String:Any] {
return self.dictionaryWithValues(forKeys: ["name","email"]) //Error here
}
}
The code working on swift 3 but I got error with swift 4 on var dictionary:[String:Any]
here is the error:
implicit Objective-C entrypoint -[Myapp.UserModel name] is
deprecated and will be removed in Swift 4
Please any help to fix this will be appreciated.
Your classes inherited from NSObject, and using objc KVC, it was fine for Swift3, because Swift3 assumed all NSObject subclasses as #objc by default, in Swift4 you need to declare your accessors #objc to make them available for obj-c KVC operations.
https://forums.developer.apple.com/thread/81789
This question already has answers here:
Saving array using NSUserDefaults crashes app
(1 answer)
How to store custom objects in NSUserDefaults
(7 answers)
Closed 7 years ago.
I tried several different method that I can to save this class in NSUserDefaults. I don't know how to save class with override function. How can I make it?
class CountryEntity: AnyObject {
private(set) var id: UInt = 0
private(set) var name = ""
override func cityData(data: Dictionary<String, AnyObject>!) {
id = data.uint(key: "id")
name = data.string(key: "name")
}
}
I tried like that but it doesn't help me
private static var _selectedCountryEntity: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey(countryNameKey) {
didSet {
let savedData = NSKeyedArchiver.archivedDataWithRootObject(selectedCountryEntity as! NSData)
NSUserDefaults.standardUserDefaults().setObject(savedData, forKey: countryNameKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
static var selectedCountryEntity: AnyObject? {
get {
return _selectedCountryEntity
}
set {
// if newValue != _selectedCountryTuple {
_selectedCountryEntity = newValue
// }
}
}
To store custom classes in NSUserDefaults, the data type needs to be a subclass of NSObject and should adhere to NSCoding protocol.
1) Create a custom class for your data
class CustomData: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String,String,String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObjectForKey("name") as! String
self.url = aDecoder.decodeObjectForKey("url") as! String
self.desc = aDecoder.decodeObjectForKey("desc") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: "name")
aCoder.encodeObject(self.url, forKey: "url")
aCoder.encodeObject(self.desc, forKey: "desc")
}
}
2) To save data use following function:
func saveData()
{
let data = NSKeyedArchiver.archivedDataWithRootObject(custom)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(data, forKey:"customArray" )
}
3) To retrieve:
if let data = NSUserDefaults().dataForKey("customArray"),
custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [CustomData] {
// Do something with retrieved data
for item in custom {
print(item)
}
}
Note: Here I am saving and retrieving an array of trhe custom class objects.
I'm trying to create an app that saves data to CloudKit, I have that all sorted but when trying to initialise a new object I get an Extra Argument when initialising error.
Code with error:
func submitTask(){
var task: Task
task = CloudKitTask(task: Task){
task.shortDescription = self.txtShortDesc.text!
task.type = self.txtType.text!
task.reminder = self.txtReminder.text!
task.priority = self.txtPriority.text!
task.dueTime = self.txtDueTime.text!
task.dueDate = self.txtDueDate.text!
task.completed = "False"
task.className = self.txtClass.text!
task.additionalDetails = self.txtDetails.text
}
print(task)
}
Error: Extra argument 'task' in call on the task = CloudK... line
Task Class:
protocol Task {
var id: String? { get }
var shortDescription: String { get set }
var className: String { get set }
var type: String { get set }
var dueDate: String { get set }
var dueTime: String { get set }
var priority: String { get set }
var reminder: String { get set }
var completed: String { get set }
var additionalDetails: String { get set }
}
class CloudKitTask: Task {
let record: CKRecord
init(record: CKRecord){
self.record = record
}
init(task: Task){
record = CKRecord(recordType: "Task")
shortDescription = task.shortDescription
className = task.className
type = task.type
dueDate = task.dueDate
dueTime = task.dueTime
priority = task.priority
reminder = task.reminder
completed = task.completed
additionalDetails = task.additionalDetails
}
var className: String {
get {
return record.objectForKey("ClassName") as! String
}
set {
record.setObject(newValue, forKey: "ClassName")
}
}
var completed: String {
get {
return record.objectForKey("Completed") as! String
}
set {
record.setObject(newValue, forKey: "Completed")
}
}
var dueDate: String {
get {
return record.objectForKey("DueDate") as! String
}
set {
record.setObject(newValue, forKey: "DueDate")
}
}
var dueTime: String {
get {
return record.objectForKey("DueTime") as! String
}
set {
record.setObject(newValue, forKey: "DueTime")
}
}
var priority: String {
get {
return record.objectForKey("Priority") as! String
}
set {
record.setObject(newValue, forKey: "Priority")
}
}
var reminder: String {
get {
return record.objectForKey("Reminder") as! String
}
set {
record.setObject(newValue, forKey: "Reminder")
}
}
var shortDescription: String {
get {
return record.objectForKey("ShortDescription") as! String
}
set {
record.setObject(newValue, forKey: "ShortDescription")
}
}
var type: String {
get {
return record.objectForKey("Type") as! String
}
set {
record.setObject(newValue, forKey: "Type")
}
}
var additionalDetails: String {
get {
return record.objectForKey("AdditionalDetails") as! String
}
set {
record.setObject(newValue, forKey: "AdditionalDetails")
}
}
var id: String? {
return record.recordID.recordName
}
var createdAt: NSDate {
return record.creationDate!
}
var lastModifiedAt: NSDate {
return record.modificationDate!
}
}
I'm fairly new to programming with Swift and can't seem to find an answer that fixes my problem, if you know what I am doing wrong or what I can do to fix it please do point me in the right direction your help is very much appreciated.
If you need to see any more of my code please do ask...
What you probably meant here was to construct a CloudKitTask without first creating a CKRecord. In that case you should have an initializer for that:
convenience init() {
self.init(record: CKRecord(recordType: "Task"))
}
Then you can use it:
func submitTask(){
let task = CloudKitTask()
task.shortDescription = self.txtShortDesc.text!
task.type = self.txtType.text!
task.reminder = self.txtReminder.text!
task.priority = self.txtPriority.text!
task.dueTime = self.txtDueTime.text!
task.dueDate = self.txtDueDate.text!
task.completed = "False"
task.className = self.txtClass.text!
task.additionalDetails = self.txtDetails.text
print(task)
}