Values replacing each other using UserDefaults - ios

I have an app where I time myself and see how long it takes me to complete a bunch of questions. I have the time transferred to another VC and displayed in a label. I have it being stored by pressing a button but when i have a new variable(time) it replaces it. How do i store an Array of values and that can be displayed in a label?
Button to save the value:
#IBAction func saveScore(_ sender: Any) {
scoreLabel.text = label.text
UserDefaults.standard.set(scoreLabel.text, forKey: "score")
}
The code that permanently holds the data:
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "score") as? String {
scoreLabel.text = x
}
}
My scoreLabel displays all my scores and label shows the time you just got.

Use the following extentions on UserDefaults to store an array of times:
extension UserDefaults {
var times: [String] {
get {
if let times = UserDefaults.standard.object(forKey: "times") as? [String] {
return times
} else {
return []
}
}
set {
UserDefaults.standard.set(newValue, forKey: "times")
}
}
}
While you don't need to extend UserDefaults, using extension can simplify a bit working with persisted values and it makes the code cleaner.
Then at the point where you show the data, use the following line to access the array:
let arrayOfTimes = UserDefaults.standard.times
scoreLabel.text = "\(arrayOfTimes)" // or any other formatting you'd like
And instead of setting the times to persist a new score, just add the new score to the array, e.g.:
// This will not only add the scoreLabel.text to the array, but also persists it
UserDefaults.standard.times.append(scoreLabel.text)

In Swift 4,
To save an array to User Defaults you would do:
let defaults = UserDefaults.standard
let array = [25, 50]
defaults.set(array, forKey: "Scores")
And to access the array from User Defaults:
let defaults = UserDefaults.standard
let retrievedArray = defaults.array(forKey: "Scores") as? [Int] ?? []
And if you were to display a score of your array in a label then, you would just do:
scoreLabel.text = String(describing: retrievedArray[0])
If you are using integers for your scoring system, I would suggest you
storing your scores as Int in User Defaults.
If you prefer using Strings though, please note that you can use the User Defaults' stringArray(forKey:) method directly, instead of the array(forKey:) method, and therefore, in that case, you wouldn't need to type cast your array:
let someStringArray = defaults.stringArray(forKey: "ArrayOfStrings")
Note: To answer your question, I will consider that you are using Int scores, but feel free to use whichever you prefer.
If you want to store your array to the same key in User Defaults every time you get a new score, you could do it easily like this:
let defaults = UserDefaults.standard
// Your new score:
let newScore = 75
// Get your current scores list from User Defaults:
var currentArray = defaults.array(forKey: "Scores") as? [Int] ?? []
// Append your new score to the current array:
let updatedArray = currentArray.append(newScore)
// And save your updated array to User Defaults:
defaults.set(updatedArray, forKey: "Scores")
// In this example, your User Defaults now contains the updated array [25, 50, 75]
And that's it :).
Please note that there is no need to use an extension for that..
UPDATE: Also, if you want to add something inside your viewDidAppear method, don't forget to add super.viewDidAppear(animated). The same goes for viewDidLoad, etc.
The documentation states:
You can override this method to perform additional tasks associated
with presenting the view. If you override this method, you must call
super at some point in your implementation.
So you would have:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let retrievedArray = defaults.array(forKey: "Scores") as? [Int] {
print(retrievedArray)
// You can access your scores array safely here
}
}

Related

How to use static variable to keep track of the increment?

I am using static variable to keep track of the number of hotels added in firebase. Let's say, in start, the value of static variable is 1, then when data is added in firebase, the number is incremented to 2. But, when again the data is added and this view controller is loaded again, the value of static variable gets back to 1 and the new data posted replace the older data. How can I manage that thing? I know that pretty basic and silly question, but sometimes the brain just don't work. Below is the code.
class OwnerAddListing2ViewController: UIViewController {
static var numberOfHotels:Int = 1
let DataForCurrency : [String] = ["USD", "Rs"]
let DataForDays : [String] = ["PerNight", "PerWeek", "PerMonth"]
override func viewDidLoad() {
super.viewDidLoad()
currencyField.inputView = currencyPicker
daysField.inputView = daysPicker
}
#IBAction func nextButtonTapped(_ sender: UIButton) {
let currency = currencyField.text
let charges = chargesField.text
let days = daysField.text
let phone = phoneField.text
let email = emailField.text
//Get reference to firebase Database
let db = Firestore.firestore()
//Post data tw database
db.collection("Property").document("\(Auth.auth().currentUser!.uid)").collection("Hotel").document("\(OwnerAddListing2ViewController.numberOfHotels)").setData(["Currency": currency!, "Charges" : charges!, "Days" : days!, "Phone" : phone!, "EmailAddress" : email!], merge: true) {(error) in
if error != nil {
}
else {
print("Data Posted Succesfully")
OwnerAddListing2ViewController.numberOfHotels = OwnerAddListing2ViewController.numberOfHotels + 1
}
}
}
Static Variables only keep the data saved in One application Life cycle. As soon as you restart the application, the static variable will be initialized from the default value. If you want to persist the value of your variables throughout , may be you should consider using UserDefaults which can store small amount of information. But be careful not to store any sensitive data like passwords.

How to retrieve UseDefault saved data from another ViewController

I have 2 ViewControllers, one displays the UI and the 2nd one displays a segmented control used as a settings button. Im using the below code to save the segmented control state:
UserDefaults.standard.set(selectorLabel.selectedSegmentIndex, forKey: "stateSelected")
I then retrieve that usedefault on the viewdidload method:
if let value = UserDefaults.standard.value(forKey: "stateSelected"){
let selectedIndex = value as! Int
selectorLabel.selectedSegmentIndex = selectedIndex
}
So far this works as intended and the state of the segmented controlled is loaded properly each app load.
The segmented control has two text titles - one is "LBs & INs" and the second is "KGs & CMs".
How would I save those two segmented control text titles as UserDefaults and then call them on the first ViewController to set two labels on the viewdidload?
Define a model to represent data you want to store and restore:
struct SegmentedControlState: Codable {
let selectedIndex: Int
let titles: [String]
}
Initialize a model, encode and store it somewhere (like user default):
func saveState(of segmentedControl: UISegmentedControl) {
let state = SegmentedControlState(
selectedIndex: segmentedControl.selectedSegmentIndex,
titles: (0..<segmentedControl.numberOfSegments).map { segmentedControl.titleForSegment(at: $0) ?? ""})
let plist = try! PropertyListEncoder().encode(state)
UserDefaults.standard.set(plist, forKey: "SegmentedControlState")
//UserDefaults.standard.synchronize() //if targeting older iOS
}
for restoring, you should reverse the order like this:
func loadState(on segmentedControl: UISegmentedControl) {
guard let plist = UserDefaults.standard.value(forKey: "SegmentedControlState") as? Data else { return }
let state = try! PropertyListDecoder().decode(SegmentedControlState.self, from: plist)
for element in state.titles.enumerated() {
segmentedControl.setTitle(element.element, forSegmentAt: element.offset)
}
segmentedControl.selectedSegmentIndex = state.selectedIndex
}
usage:
// store `selectorLabel` data
saveState(of: selectorLabel)
// restore `selectorLabel` data
loadState(on: selectorLabel)
Note that it is not a good idea to store data like this to userdefaults at all. If you want to access some data from anywhere in code, you should follow singleton pattern and define your own singleton instance instead of standard userdefault.
Just store the value as a string, instead of an integer index.
UserDefaults.standard.set(selectorLabel.titleForSegment(at: selectorLabel.selectedSegmentIndex), forKey: "stateSelected")
And then to retrieve:
UserDefaults.standard.string(forKey: "stateSelected")
EDIT: #rmaddy is correct above - you should ideally be storing an index value like you're already doing, and then using an array to determine which title the index refers to (cleaner than just using a title as a reference). You could make this array global so you can access from anywhere, if you must.
segmentedControlTitles: [String] = ["LBs & INs", "KGs & CMs"]
And then call by
let index = UserDefaults.standard.integer(forKey: "stateSelected")
let title = segmentedControlTitles[index]

How to store user data as string, and load that string in viewDidLoad

I am trying to load a value that has been inputted by the user in the viewDidLoad via a String. I am using UserDefaults to save the users value that they input into a UITextField (userValue), I then save this to the String 'search'. I am able to print out the value of search in the GoButton function, and it works fine, but when I load my ViewController as new, the value of 'search' is equal to nil. The aim here is to have the users previous search saved, and loaded into the UITextField (that is used as a search box) upon loading the ViewController.
Code Below:
class ViewController: UIViewController {
#IBOutlet weak var userValue: UITextField!
var search: String!
}
viewDidLoad:
override func viewDidLoad() {
if (search != nil)
{
userValue.text! = String (search)
}
}
Button Function:
#IBAction func GoButton(_ sender: Any) {
let userSearch: String = userValue.text!
let perference = UserDefaults.standard
perference.set(userSearch, forKey: "hello")
perference.value(forKey: "hello")
let value = perference.value(forKey: "hello") as! String
search = value
print (search) // <<this works, it prints out the users search value
}
#VishalSharma has the right idea, but the code should probably look more like…
override func viewDidLoad() {
super.viewDidLoad()
if let search = UserDefaults.standard.string(forKey: "hello") {
userValue.text = search
}
}
or even more simply…
userValue.text = UserDefaults.standard.string(forKey: "hello")
When you load, search is effectively nil.
So either you read userDefaults in viewDidload or you come through a segue: then you can load search in the prepare.
I've always found it convenient and useful to store all UserDefault properties as an extension within the same file along with their getters and setters. It is far easier to maintain, use and read. by using the #function keyword for the key you are referencing the variable's name and not a string that can be accidentally changed somewhere else in code.
UserDefaults.swift
import Foundation
// An Extension to consolidate and manage user defaults.
extension UserDefaults {
/// A value Indicating if the user has finished account setup.
/// - Returns: Bool
var finishedAcountSetup: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
/// The hello text at the start of the application.
/// - Returns: String?
var helloText: String? {
get { return string(forKey: #function) }
set {set(newValue, forKey: #function) }
}
//etc...
}
When you use these values reference the standard settings:
//Setting
UserDefaults.standard.helloText = "Updated Hello Text"
// Getting
// for non-optional value you can just get:
let didCompleteSetup = UserDefaults.standard.finishedAcountSetup
// Otherwise, safely unwrap the value with `if-let-else` so you can set a default value.
if let text = UserDefaults.standard.helloText {
// Ensure there is text to set, otherwise use the default
label.text = text
} else {
// helloText is nil, set the default
label.text = "Some Default Value"
}
obviously, it provides nil because when view controller load the search is nil try this.
let perference = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
if (perference.value(forKey: "hello") != nil) {
search = perference.value(forKey: "hello") as! String
userValue.text! = String (search)
}
}

How do I save settings using NSUserdefaults

I am making a tip calculator and the requirement is to have a settings page to go along with the calculator. In the calculator there are three options implemented by a segmented control when each is selected, the value of the tip and the value of the total change. In my settings tab I would like to be able to have the user save their default tip percentage. I know I need to use NSUserdefaults, however I do not know how to do this using two different pages (or one page for that matter). If what I want to achieve is unclear, please let me know I tried my best explaining it thoroughly.
Here is the code for the view controller:
#IBAction func onEditingChanged(sender: AnyObject) {
var tipPercentages = [0.18, 0.2, 0.22]
let tipPercentage = tipPercentages[tipControl.selectedSegmentIndex]
let billAmount = billField.text!._bridgeToObjectiveC().doubleValue
let billAmt = billAmount
let tip = billAmt * tipPercentage
let total = billAmt + tip
tipLabel.text = String(format:"$%.2f", tip)
totalLabel.text = String(format:"$%.2f", total)
}
Here is what I have for the settingsViewController:
import UIKit
class SettingsViewController: UIViewController {
#IBOutlet weak var defaultTipControl: UISegmentedControl!
#IBAction func actDefaultTipCont(sender: AnyObject) {
var tipPercentages = [0.18, 0.2, 0.22]
var tipPercentage = [defaultTipControl.selectedSegmentIndex]
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(tipPercentage, forKey: "tippingDefault")
defaults.setInteger(123, forKey: "tippingInteger")
defaults.synchronize()
}
I have just been messing around with the settingsViewController trying to get it to work, it will likely all have to be redone. Here are pictures of the views of the two different pages:
Entry View
Settings Page
Thank you ahead of time for the help!
To save then retrieve the data, you want to do something like this:
let valueToSave = tipAmount;
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(valueToSave, forKey: "tipAmount")
Then to retrieve it and use it on your main control (for example) do this:
let defaults = NSUserDefaults.standardUserDefaults()
tipAmount = defaults.objectForKey("tipAmount")
the valueForKey method returns whatever you saved in that key previously
NSUserdefaults is basically just a dictionary that will persist over different launches, so just check if you have saved a value for a specific key to access it

Saving And Loading An Integer On Xcode Swift

I am trying to save an integer so it shows up after I switch the page or close the game. I made this to change the number but how do I save that number when I switch pages and load it when I go back to that page.
Change Code:
#IBAction func MoneyPress(sender: AnyObject) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.DollarsLabel.text = (string: MoneyNumberString)
}
If it isn't a lot of data, the strategy I use to save data, pass it between pages, and persist it between app runs is to store the value in NSUserDefaults.
Setting A Value: When you first get or when you change the data, store it in NSUserDefaults.
#IBAction func MoneyPress(sender: AnyObject) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.DollarsLabel.text = (string: MoneyNumberString)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults() //This class variable needs to be defined every class where you set or fetch values from NSUserDefaults
defaults.setObject(MoneyNumberString, forKey: "money")
defaults.synchronize() //Call when you're done editing all defaults for the method.
}
Loading A Value: When you need to get the values, just grab it from NSUserDefaults.
#IBAction func loadButton(sender: UIButton) {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var money = defaults.valueForKey("money") as? String
dollarLabel.text! = money
}
To remove the stored data, all you need to do is call the removeObjectForKey function for each key previously set.
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.removeObjectForKey("money")
defaults.synchronize()
Helpful Source on NSUserDefaults:
NSUserDefulats Class Reference: Link here.
You can use NSUserDefaults for this.
Save Value
NSUserDefaults.standardUserDefaults().setInteger(money, forKey: "MoneyKey");
Retrieve Value
NSUserDefaults.standardUserDefaults().integerForKey("MoneyKey");
So can retrieve the value in viewDidLoad and load the data:
override func viewDidLoad()
{
super.viewDidLoad()
loadWebView()
var money = NSUserDefaults.standardUserDefaults().integerForKey("MoneyKey");
}
When you come to the view for the first time the value of money will be 0.
Remove Value
If you need to remove a value from NSUserdefaults, you can use:
NSUserDefaults.standardUserDefaults().removeObjectForKey("MoneyKey")

Resources