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
}
}
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 am trying to create a view controller displaying information of the clicked shop from a UITableView on the previous view controller. However, I cannot retrieve data in the new view controller and I don't know how to solve this issue. Here is my database structure. Thank you for the help.
import UIKit
import Firebase
import FirebaseDatabase
class ShopViewController: UIViewController {
var name :String? // This is the name of the cell clicked on the previous viewcontroller
var ref: DatabaseReference!
#IBOutlet weak var shopName: UILabel!
#IBOutlet weak var shopType: UILabel!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
shopName.text = name
let ref = Database.database().reference().child("shops").childByAutoId().child(name!).child("Details")
ref.observe(.childAdded, with: { snapshot in
let snapshotValue = snapshot.value as! NSDictionary
let imageUrlString = snapshotValue["imageURL"] as! String
print(imageUrlString)
let shoptype = snapshotValue["type"] as! String
self.shopType.text = shoptype
})
}
}
The reference that you have is wrong, childByAutoId() is used to generate a unique id in your database.
Also the id that you currently have is the userid, you need to retrieve the userid:
let user = Auth.auth().currentUser
let uid = user.uid
then the location should be:
let ref = Database.database().reference().child("shops").child(uid).child("Eat").child("Details")
So there's a couple of issues here, first off is your data structure.
Something along these lines would be much easier to read from. You shouldn't need the name field as far as I can tell, and is there a requirement to have the details nested?
Reading the data
Next up, you can refactor your data reference code. I'd firstly recommend extracting the logic into its own method rather than having it directly in viewDidLoad:
var ref: DatabaseReference!
override func viewDidLoad() {
shopName.text = name
self.ref = Database.database().reference()
getShopDetails()
}
func getShopDetails() {
guard let shopName = name else { return }
ref.child("shops").child(shopName).observe(.childAdded, with: { snapshot in
guard let shop = snapshot.value as? [String: AnyObject] else { return }
let imageUrlString = shop["imageURL"] as! String
print(imageUrlString)
let shoptype = shop["type"] as! String
self.shopType.text = shoptype
})
}
I've modified to set the database reference value as otherwise this would crash if you tried to use it elsewhere. I've also added a couple of guard statements to minimise the number of explicit unwraps.
I can't comment on your list code, but have you considered doing the call for all of the shops in this, and mapping the result to a data object, which you can then use to display the data in the screen we're detailing here, rather than doing a separate query every-time you open a detail screen.
I am creating a Save button where the user clicks and stores the information using NSUserDefaults. When the user loads the app again, and clicks Show button, all the information stored before should display. For example
Monday User inserts value 5 times: 20 , 30 ,40 ,50 ,60
Tuesday nothing.
Wednesday nothing.
Thursday same user inserts: 30, 20, 50, 80
Friday values are: 40, 20
Now on Saturday when the user click show then the data should display:
20, 30,40,50,60,30,20,50,80,40,20
SO far my coding is:
#IBOutlet weak var dayTB: UITextField!
#IBOutlet weak var numTB: UITextField!
#IBOutlet weak var showLabel: UILabel! // displays all the values stored
var Money = 0
#IBAction func saveBtn(_ sender: Any) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.showLabel.text = (string: MoneyNumberString)
let defaults: UserDefaults = UserDefaults.standard
defaults.set(MoneyNumberString, forKey: "money")
defaults.synchronize()
}
#IBAction func showbtnact(_ sender: Any) {
let defaults: UserDefaults = UserDefaults.standard
var money = defaults.value(forKey: "money") as? String
showLabel.text! = money!
}
Hope there is a solution for this problem :) Thanks
When you save values, what you need to do is:
var ary_Values = NSMutableArray()
if UserDefaults.standard.value(forKey: "money") != nil
{
let arr = UserDefaults.standard.value(forKey: "money") as! NSArray
for oldObj in arr
{
ary_Values.add(oldObj)
}
ary_Values.add(self.showLabel.text)
}
UserDefaults.standard.set(ary_Values, forKey: "money")
UserDefaults.standard.synchronize()
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
}
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")