Use data from NSUserDefaults in UIPickerView - pickerView stays empty - ios

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")

Related

storing object in user defaults swift

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
}
}

How to save the date the user presses a button in swift?

I want the code to run once a day, but the way I want to accomplish this is by disabling the button after it is clicked and then reenabling when it has been more than 24 hours.
Would the code below be correct to just save the date the user pressed the button?
if distance < radius{
Total_Points += 10
pointsLabel.text = "Total Points: \(Total_Points)"
getPointsOutlet.isEnabled = false
let clickdate = UserDefaults.standard
if var timeList = clickdate.object(forKey: "timeList") as? [Date]{
timeList.append(Date())
clickdate.set(timeList, forKey: "timeList")
} else {
clickdate.set([Date()], forKey: "timeList")
}
clickdate.synchronize()
}
let PointsDefault = UserDefaults.standard
PointsDefault.setValue(Total_Points, forKey: "Total Points")
Your code
let clickdate = UserDefaults.standard
if var timeList = clickdate.object(forKey: "timeList") as? [Date]{
timeList.append(Date())
clickdate.set(timeList, forKey: "timeList")
} else {
clickdate.set([Date()], forKey: "timeList")
}
clickdate.synchronize()
Is fine for adding a date to an array of saved dates. You could pull that out into a separate function:
func addDateToDefaults(date: Date? = nil) {
let defaults = UserDefaults.standard
if date = nil {
date = Date()
}
if var timeList = defaults.object(forKey: "timeList") as? [Date]{
timeList.append(date)
defaults.set(timeList, forKey: "timeList")
} else {
defaults.set([date!], forKey: "timeList")
}
}
Then you could call that from a button action:
#IBAction func buttonClicked(_ sender: UIButton) {
addDateToDefaults()
//The rest of your button action code goes here
}
With the function addDateToDefaults() above, you can pass in a specific date, or leave off the date parameter and it will append the current date to your array of dates.
You can achieve in this way when you tapped on button save the date value using Userdefaults and then inside your ViewDidAppear, ViewDidload and UIApplicationWillEnterForeground notification method put the check to get dateValue from user defaults and then take the difference of current date and last stored date and accordingly enabled your button.
lazy var userDefaults: UserDefaults = {
return UserDefaults.standard
}()
func ViewDidLoad() {
UserDefaults.set(Date(), forKey:"date")
userDefaults.synchronize()
}
func someMethodWhereYouWantToGetValue() {
guard let value = userDefaults.object(forKey: "date") as? Date else
{return}
print(value)
}

App Crashing with EXC_BREAKPOINT Error

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
}

Appending an Array in NSUserDefaults Swift

So I want to append an array in the NSUserDefaults, not save an entirely new one. The issue is that I want the array to be a logged history of a certain type of user interaction so I need to be appending an existing array in the NSUserDefaults, and not overwriting it.
i.e Not:
var data = [String]()
var questionsAsked = [String]()
func storeData() {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(questionsAsked, forKey: "questionsAsked")
defaults.setObject(data, forKey: "data")
var storedQuestionsAsked = defaults.objectForKey("questionsAsked") as? [String] ?? [String]()
var storedData = defaults.objectForKey("data") as? [String] ?? [String]()
}
The array(s) are based in my Modal, and the appending is occurring by appending that array in the ViewController of that Modal
I tried just calling a function like this, but this is just calling an empty array because its never being re-saved to the NSUserDefaults:
func updateQuestionsAsked(questionAsked: String, answerGiven: String) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject([String](), forKey: "questionsAsked")
defaults.setObject([String](), forKey: "data")
var storedQuestionsAsked = defaults.objectForKey("questionsAsked") as? [String] ?? [String]()
var storedData = defaults.objectForKey("data") as? [String] ?? [String]()
storedData.append(questionAsked)
storedQuestionsAsked.append(answerGiven)
NSUserDefaults.standardUserDefaults().synchronize()
}
Is there some kind of synchronization thing I'm missing? I'm sure this is super simple but I'm just spacing out on what to do. Thanks!
YOu are setting empty data for keys "questionsAsked" & "data",
func updateQuestionsAsked(questionAsked: String, answerGiven: String) {
let defaults = NSUserDefaults.standardUserDefaults()
//remove this
//defaults.setObject([String](), forKey: "questionsAsked")
//defaults.setObject([String](), forKey: "data")
var storedQuestionsAsked = defaults.objectForKey("questionsAsked") as? [String] ?? [String]()
var storedData = defaults.objectForKey("data") as? [String] ?? [String]()
storedData.append(questionAsked)
storedQuestionsAsked.append(answerGiven)
// then update whats in the `NSUserDefault`
defaults.setObject(questionsAsked, forKey: "questionsAsked")
defaults.setObject(data, forKey: "data")
// call this after you update
defaults.synchronize()
}
Another is maybe you failed to set object here...
func storeData() {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(questionsAsked, forKey: "questionsAsked")
defaults.setObject(data, forKey: "data")
// call this after setting Objects
//--
defaults.synchronize()
//--
...
}

UIPickerView won't show the data loaded from NSUserDefaults

EDIT:
I tried to set up NSUserDefaults last night but an error keeps occurring:
ViewController3:
save data
#IBAction func addButtonTapped(sender: UIButton) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var itemList:NSMutableArray? = userDefaults.objectForKey("exercisesList") as? NSMutableArray
var dataSet:NSMutableDictionary = NSMutableDictionary()
dataSet.setObject(textField.text, forKey: "exercises")
if ((itemList) != nil){
var newMutableList:NSMutableArray = NSMutableArray();
for dict:AnyObject in itemList!{
newMutableList.addObject(dict as NSDictionary)
}
userDefaults.removeObjectForKey("exercisesList")
newMutableList.addObject(dataSet)
userDefaults.setObject(newMutableList, forKey: "exercisesList")
}else{
userDefaults.removeObjectForKey("exercisesList")
itemList = NSMutableArray()
itemList!.addObject(dataSet)
userDefaults.setObject(itemList, 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 itemListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("itemList") as? NSMutableArray
if ((itemListFromUserDefaults) != nil){
exercises = itemListFromUserDefaults!
}
}
Now I wanted to use the loaded data for UIPickerView
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
switch pickerView {
case pickerView1:
return exercises[row] as String
case pickerView2:
return reps[component][row]
case pickerView3:
return weight[component][row]
default:
assertionFailure("Unknown pickerView")
}
}
But at the point where the UIPickerView should return the exercises it is empty. Need some help here.
I think I've got this.
In your code, you read exercises from UserDefaults in viewDidAppear, which will be called after func pickerView(...) -> String! is called. That's why in func pickerView(...) -> String! the exercises is always empty.
To do this correctly, you need to initialize exercises in init() or viewDidLoad()
Don't use NSUserDefaults to pass variables.
Add a public property (in the .h file) to destinationViewController and set that property with the value (the array) in prepareForSegue.
To save the array between app launches do as #Martin suggests, writing the array to the Documents directory.There are many answers here with that information.
How to store your arrays using NSUserDefaults is pretty simple and I encourage you to try it.
var exercises = ["A","B","C"]
NSUserDefaults.standardUserDefaults().setObject(exercises, forKey: "exercises")
How to load your string arrays using NSUserDefaults
if let myLoadedStringArray = NSUserDefaults.standardUserDefaults().stringArrayForKey("exercises"){
println(myLoadedStringArray) // "[A, B, C]"
}
// Note: myLoadedStringArray has no value outside curly brackets
How to delete/reset a value stored using NSUserDefaults
NSUserDefaults.standardUserDefaults().removeObjectForKey("exercises")
println(NSUserDefaults.standardUserDefaults().stringArrayForKey("exercises")) // "nil"
If you prefer to store it as a file at the documents folder you can do as follow
let fileUrl = (NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL).URLByAppendingPathComponent("exercises.db")
(exercises as NSArray).writeToURL(fileUrl, atomically: true) // true
// forced unwrap if you are sure it won't return nil
let loadedArray = NSArray(contentsOfURL: fileUrl)! as [String]
// or use if let to safely unwrap your array
if let loadedArray = NSArray(contentsOfURL: fileUrl) as? [String] {
println(loadedArray) // "[A, B, C]"
}

Resources