I have an app I am making in Xcode 7.2. I have a main menu with a button that has a segue going to a settings view controller. The settings view controller has the following code and every time I get to that view controller it crashes with an EXC_BREAKPOINT error. The funny thing is that this only happens on my iPhone 4 running iOS 7. It does not crash in the iOS Simulator iPhone 6 running iOS 9.
Anyways, here is the code.
//
// SettingsController.swift
// Arbor Hills iOS
//
// Created by Andrew on 11/22/15.
// Copyright © 2015 Arbor Hills Vet. All rights reserved.
//
import UIKit
class SettingsController: UITableViewController {
#IBOutlet weak var username: UITextField!
#IBOutlet weak var password: UITextField!
#IBOutlet weak var notifications: UISwitch!
override func viewDidLoad(){
super.viewDidLoad()
let defaults = NSUserDefaults.standardUserDefaults()
let dusername = defaults.valueForKey("username")!
let dpassword = defaults.valueForKey("password")!
let dnotifications = defaults.valueForKey("notifications")!
username!.text = String(dusername)
password!.text = String(dpassword)
if(dnotifications as! Bool == true){
notifications.setOn(true, animated: true)
}else{
notifications.setOn(false, animated:true)
}
}
#IBAction func saveUsername(sender: AnyObject) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(username.text!, forKey: "username")
}
#IBAction func savePassword(sender: AnyObject) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(password.text!, forKey: "password")
}
#IBAction func notificationsModified(sender: AnyObject) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(notifications.on, forKey: "notifications")
}
}
NSUserDefaults returns nil for an object if no default value is registered. When you unwrap a nil value you'll get an runtime error (crash).
Apple recommends to register a default value for any key as a placeholder until a custom value is saved the first time.
In AppDelegate add in applicationDidFinshLaunching
let defaults = NSUserDefaults.standardUserDefaults()
let defaultValues = ["username" : "", "password" : "", "notifications" : false]
defaults.registerDefaults(defaultValues)
Swift 3+
let defaults = UserDefaults.standard
let defaultValues : [String : Any] = ["username" : "", "password" : "", "notifications" : false]
userDefaults.register(defaults: defaultValues)
That registers empty strings and a boolean false.
Then you can safely retrieve the values
let defaults = NSUserDefaults.standardUserDefaults()
let dusername = defaults.stringForKey("username")!
let dpassword = defaults.stringForKey("password")!
let dnotifications = defaults.boolForKey("notifications")
Swift 3+
let defaults = UserDefaults.standard
let dusername = defaults.string(forKey:"username")!
let dpassword = defaults.string(forKey:"password")!
let dnotifications = defaults.bool(forKey:"notifications")
Use always stringForKey for String values and boolForKey for Bool values.
Never use valueForKey with NSUserDefaults which is a special KVC method.
Then this code could be also simplified:
username!.text = dusername
password!.text = dpassword
notifications.setOn(dnotifications, animated: true)
When writing to user defaults use setBool:forKey for the Bool value, however there is no String equivalent, setObject:forKey is correct.
You should not use valueForKey to fetch objects from NSUserDefaults. Thats a key-value coding method. (See the link on NSKeyValueCoding. you want objectForKey, and you need to check for nil afterwords:
let defaults = NSUserDefaults.standardUserDefaults()
if let dusername = defaults.objectForKey("username") as String
{
//code to use the username
}
else
{
//code to handle the case where there is no username
}
Related
func applicationDidFinishLaunching(_ application: UIApplication) {
let defaults = UserDefaults.standard
defaults.set(memes, forKey: "HI")
}
var memes = [ViewController.Meme]()
I wrote this code in my AppDelegate and reload the values in the tableview using this code.
override func viewDidLoad() {
let defaults = UserDefaults.standard
let token = defaults.array(forKey: "HI")?.count
print(token ?? -1)
}
But I can't see the array is filling with anything I'm using it wrong?
You seem to have the ordering wrong. In your applicationDidFinishLaunching(_:) you first need to add items to your array and then store it as follows:
var memes = [ViewController.Meme]()
let defaults = UserDefaults.standard
defaults.set(memes, forKeyL "HI")
And then you can read it as you currently are in your view controller
It looks like you saved an empty array.
AppDelegate.swift:
// if (by whether your array is full)
let defaults = UserDefaults.standard
private let memesKey = "memesHI"
defaults.set(memes, forKey: memesKey)
viewDidLoad():
var memes = [ViewController.Meme]()
let defaults = UserDefaults.standard
private let memesViewKey = "memesHI"
let memesArray = [Any]()
memesArray = memes
let token = defaults.array(memesArray, forKey:memesViewKey)?.count
I'm new to swift and trying to figure out the best way to store user defaults. Here are my 3 initial VC classes of my app where i pass data from the on boarding view, to the start your plan view, to the user info (profile view) - which is where i need to store defaults for users. My issue is - the only user default that is storing is "name" and "monthly" pay seems to work as well. But the "daily budget default" and "daily savings default" (which are doubles) don't seem to store and appear on the 3rd screen, (users profile). I am not getting the correct reference.
1st snippet of code is from the on boarding view controller where i collect their info - to keep things short I'm only showing the calculateData IBAction. As of now - this is where i am trying to grab their user defaults.
2nd VC of info is the view directly after on boarding, where the user can review their info - once they press start from this VC- i create an new user object (should i just create an array of new User objects and store it under NSUserDefaults here?)
3rd VC - this is the actual user profile, where i need all of the user defaults to show. I am going to set this as the initial view controller & set up in app delegate, to load this page first if a user has previously done the on boarding screen.
Please and thank you for any help!
import UIKit
class ViewController: UIViewController, UITextFieldDelegate{
#IBAction func calculateDataButtonPressed(_ sender: UIButton) {
customerName = nameTextField.text!
if let payTotal = Double(monthlyAmountTextField.text!){
monthlyEarning = payTotal
}
if let savingsNumber = Double(desiredSavingsTextField.text!){
desiredSavingsAmount = savingsNumber
}
else {
displayLabel.text = "Enter input as so: 1299.39"
}
budgetForEachDay = ((monthlyEarning - desiredSavingsAmount) / 4.0) / 7.0
savingsForEachDay = (desiredSavingsAmount / 4.0) / 7.0
UserDefaults.standard.set(nameTextField.text, forKey: "name")
UserDefaults.standard.set(monthlyAmountTextField.text, forKey: "monthlyPay")
UserDefaults.standard.set(savingsForEachDay, forKey: "dailySavingsDefault")
UserDefaults.standard.set(budgetForEachDay, forKey: "dailyBudgetDefault")
//THE 4 USER DEFAULTS I NEED TO SAVE
Here is the NEXT CLASS THAT THE SEGUE LOADS Too- i create a new User object here - should i perhaps store User Defaults in a newUserObject array instead of how i am trying to go about it ?
import UIKit
class StartYourPlanViewController: UIViewController {
var nameFieldPassedOver : String?
var monthlyEarningPassedOver : Double?
var desiredSavingsPassedOver : Double?
var budgetToSpend : Double = 55.3
var saveEachDay : Double = 55.5
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var monthtlyEarningLabel: UILabel!
#IBOutlet weak var desiredSavingsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = nameFieldPassedOver
monthtlyEarningLabel.text = "\(monthlyEarningPassedOver!)"
monthtlyEarningLabel.isHidden = true
desiredSavingsLabel.text = "\(desiredSavingsPassedOver!)"
desiredSavingsLabel.isHidden = true
}
func makeNewUserObject(){
let newUser = UserInfo(name: nameFieldPassedOver!, iWantToSave : desiredSavingsPassedOver!, monthlyIncome: monthlyEarningPassedOver!, budgetEachDay : budgetToSpend, youSaveEachDay : saveEachDay)
newUser.printUserBio()
}
#IBAction func startPlanButtonPressed(_ sender: UIButton) {
makeNewUserObject()
performSegue(withIdentifier: "GoToYourUserInfoView", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "GoToYourUserInfoView"{
let userInfoVC = segue.destination as! UserInfoViewController
userInfoVC.userNamePassedOver = nameFieldPassedOver
userInfoVC.userDailyBudgetPassedOver = budgetToSpend
userInfoVC.userMonthlyEarningsPassedOver = monthlyEarningPassedOver
userInfoVC.userDesiredSavingsPassedOver = desiredSavingsPassedOver
userInfoVC.userDailySavingsPassedOver = saveEachDay
}
}
}
AND here will end up being my main and initial VC- where i display the users information for them. I need to Set the default values of user defaults so that they appear on this page everytime an existing user opens the app
class UserInfoViewController : ViewController {
var userNamePassedOver : String?
var userDailyBudgetPassedOver : Double?
var userDailySavingsPassedOver : Double?
var userMonthlyEarningsPassedOver : Double?
var userDesiredSavingsPassedOver : Double?
var newAmountPassedBack : Double = 0.0
let monthlyPay = "monthlyPay"
let name = "name"
let dailySavingsDefault = "dailySavingsDefault"
let dailyBudgetDefault = "dailyBudgetDefault"
#IBOutlet weak var dailySavingsNumberLabel: UILabel!
#IBOutlet weak var userNameLabel: UILabel!
#IBOutlet weak var dailySpendingLimitLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let name = UserDefaults.standard.value(forKeyPath: name) as? String{
userNameLabel.text = name
}
if let monthlyPay = UserDefaults.standard.value(forKeyPath: monthlyPay) as? String{
userMonthlyEarningsPassedOver = Double(monthlyPay)
}
if let dailySavingsDefault = UserDefaults.standard.value(forKeyPath: dailySavingsDefault) as? String{
dailySavingsNumberLabel.text = dailySavingsDefault
}
if let dailyBudgetDefault = UserDefaults.standard.value(forKeyPath: dailyBudgetDefault) as? String {
dailySpendingLimitLabel.text = dailyBudgetDefault
}
}
Initially, You have to archive the objects into NSData then we can save it to the UserDefault and retrieve it from UserDefault when needed.
Example is shown below:-
1) Save object to UserDefault
let groups = [Group(id: 1, name: "group1", shortname: "g1"), Group(id: 2, name: "group2", shortname: "g2")]
var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: groups)
userDefaults.set(encodedData, forKey: "groups")
userDefaults.synchronize()
2) Retrieve Object from UserDefault
let decoded = userDefaults.object(forKey: "groups") as! Data
let decodedGroups = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [Group]
When you set the data is userdefault after it you have to synchronise your userdefault so that userdefault saves the pending data. Make sure you won't have any typo mistake with userdefault identifier.
If you want to store structs on UserDefaults:
// Save the selected timeZone on UserDefaults
if let encodedCity = try? JSONEncoder().encode(cityAndTimeZone) {
UserDefaults.standard.set(encodedCity, forKey: "cityAndTimeZone")
}
// To restore struct from UserDefaults
if let decodedData = UserDefaults.standard.object(forKey: "cityAndTimeZone") as? Data {
if let cityAndTimeZone = try? JSONDecoder().decode(CityAndTimeZone.self, from: decodedData) {
timezoneLabel.text = cityAndTimeZone.city
}
}
Whenever I open my app, it doesn't load my array values because the != nil function isn't called. Is there anything I can do about this?
Code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var toDoData = NSUserDefaults.standardUserDefaults()
if (toDoData.valueForKey("TDDATA") != nil){
todos = toDoData.valueForKey("TDDATA") as! NSArray as! [TodoModel]
}
if todos.count != 0{
toDoData.setValue(todos, forKeyPath: "TDDATA")
toDoData.synchronize()
}
}
Don't worry about the table. It populates perfectly. I just need the loading data issue fixed.
Code included in your answer helps a lot!
Thanks.
UPDATE:
Here is the TodoModel:
import Foundation
import UIKit
class TodoModel : NSObject, NSCoding {
var id: String
var image: String
var title: String
var desc: String
var scores: String
init (id: String, image: String, title: String, desc: String, scores: String) {
self.id = id
self.image = image
self.title = title
self.desc = desc
self.scores = scores
}
}
valueForKey and setValue:forKeyPath are KVC (Key Value Coding) methods (read here and here). It will not help you read/write to the user defaults database.
Looking in the NSUserDefaults documentation, there are a number of methods available for getting and setting values in the defaults database. Since you are using arrays, we will use:
arrayForKey to get.
setObject:forKey to set. (There is no array-specific setter)
EDIT: Try this in your viewDidAppear. Here we check if we have data, and if we do, we store it. If we don't have data, then check if the defaults database has some saved. If it does, use it instead. It would be advantageous to only load data from the defaults database in viewDidLoad, and then save in viewDidAppear or even better, a function which is called when a todo is added.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let defaults = NSUserDefaults.standardUserDefaults()
if todos.count > 0 {
// Save what we have
let data = NSKeyedArchiver.archivedDataWithRootObject(todos)
defaults.setObject(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.dataForKey("TDDATA"),
storedTodos = NSKeyedUnarchiver.unarchiveObjectWithData(storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
}
}
In addition, we must implement the NSCoding protocol in your model. This should be something like this:
class TodoModel: NSObject, NSCoding {
var myInt: Int = 0
var myString: String?
var myArray: [String]?
required init?(coder aDecoder: NSCoder) {
myInt = aDecoder.decodeIntegerForKey("myInt")
myString = aDecoder.decodeObjectForKey("myString") as? String
myArray = aDecoder.decodeObjectForKey("myArray") as? [String]
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(myInt, forKey: "myInt")
aCoder.encodeObject(myString, forKey: "myString")
aCoder.encodeObject(myArray, forKey: "myArray")
}
}
(Of course, replace myInt, myString, myArray, etc, with whatever properties your model might have.)
I am trying to share [[String:String]] object between my app and Today Extension.
In my app, I am saving an object like this:
override func viewWillDisappear(animated: Bool) {
// Save all data and update Today Extension
// 1) Save selectedStations as Dictionary to NSUserDefaults
if var sharedDefaults = NSUserDefaults(suiteName: "group.xxx.xxx") {
// Save to userDefaults (1) stationName, (2) stationLine, (3) id, (4) statnId, (5) subwayId
var arrayOfDic:[[String:String]] = []
for station in selectedStations {
// Organize data to pass to widget
let name: String = station.name ?? ""
let line: String = station.line ?? ""
let id: String = station.id ?? ""
let statnId: String = station.statnId ?? ""
let subwayId: String = station.subwayId ?? ""
// Create dictionary
let dic = [
"name" : name,
"line" : line,
"id" : id,
"statnId" : statnId,
"subwayId" : subwayId
]
// Append to array
arrayOfDic.append(dic)
}
// Save to NSUserDefaults
sharedDefaults.setObject(arrayOfDic, forKey: "selectedStations")
println(" saved:\(arrayOfDic)")
sharedDefaults.synchronize()
}
// 2) Refresh Today Extension
}
And within the same .swift file, I am retrieving the data in 'ViewDidLoad' like this:
override func viewDidLoad() {
super.viewDidLoad()
if var sharedDefaults = NSUserDefaults(suiteName: "group.xxx.xxx") {
sharedDefaults.synchronize()
if let arrayOfDic = NSUserDefaults().objectForKey("selectedStations") as? [[String:String]] {
println(" fetched:\(arrayOfDic)")
}
}
}
But I realize that
println(" saved:\(arrayOfDic)")
logs what I am trying to save, but
println(" fetched:\(arrayOfDic)")
is not logging anything. This part seems to be nil:
if let arrayOfDic = NSUserDefaults().objectForKey("selectedStations") as? [[String:String]]
Is my code wrong??
The problem is in this line of code:
if var sharedDefaults = NSUserDefaults(suiteName: "group.xxx.xxx")
Think again what are you actually doing in here? You are creating a new instance of NSUserDefaults. While what you need is to get reference of the one created in viewWillDisappear method.
My suggestion would be is to implement
sharedDefaults = NSUserDefaults(suiteName: "group.xxx.xxx") outside of both functions (make it lets say instance variable)
and later on in viewDidLoad implement the following:
if let sharedDefaults = sharedDefaults
instead of sharedDefaults = NSUserDefaults(suiteName: "group.xxx.xxx")
I tried to set up NSUserDefaults last night but an error keeps occurring:
ViewController3:
save data
#IBAction func tappedAddButton(sender: AnyObject) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var exercisesList:NSMutableArray? = userDefaults.objectForKey("exercisesList") as? NSMutableArray
var dataSet:NSMutableDictionary = NSMutableDictionary()
dataSet.setObject(textField.text, forKey: "exercises")
if ((exercisesList) != nil){
var newMutableList:NSMutableArray = NSMutableArray();
for dict:AnyObject in exercisesList!{
newMutableList.addObject(dict as NSDictionary)
}
userDefaults.removeObjectForKey("exercisesList")
newMutableList.addObject(dataSet)
userDefaults.setObject(newMutableList, forKey: "exercisesList")
}else{
userDefaults.removeObjectForKey("exercisesList")
exercisesList = NSMutableArray()
exercisesList!.addObject(dataSet)
userDefaults.setObject(exercisesList, forKey: "exercisesList")
}
userDefaults.synchronize()
self.view.endEditing(true)
textField.text = ""
}
ViewController1:
load data
var exercises:NSMutableArray = NSMutableArray();
...
override func viewDidAppear(animated: Bool) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var exercisesListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("exercisesList") as? NSMutableArray
if ((exercisesListFromUserDefaults) != nil){
exercises = exercisesListFromUserDefaults!
}
}
While adding some data to the variable "exercises", the pickerView stays empty.
You are setting userDefaults with the key "exercisesList" but attempting to get the data back with a different key ("itemList")