NSUserDefaults not saving data (Swift) - swift

I am trying to save inputs from various text fields using NSUserDefaults:
#IBAction func continue2save(sender: AnyObject) {
let stringy1: NSString = mcweightTF.text!
let stringy2: NSString = mcnumTF.text!
NSUserDefaults.standardUserDefaults().setObject(stringy1, forKey: "savemcw")
NSUserDefaults.standardUserDefaults().setObject(stringy2, forKey: "savemcn")
NSUserDefaults.standardUserDefaults().synchronize()
}
#IBAction func calculate(sender: AnyObject) {
let load1: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("savemcw")
calcLabel.text = String(load1)
}
However "load1"'s value is always nil. I have attempted almost every configuration of implementing short-term storage through NSUserDefaults, however the value stored is always nil.

Try this,
#IBAction func continue2save(sender: AnyObject) {
let stringy1 = mcweightTF.text!
let stringy2 = mcnumTF.text!
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("stringy1", forKey: "savemcw")
defaults.setObject("stringy2", forKey: "savemcn")
}
#IBAction func calculate(sender: AnyObject) {
let defaults = NSUserDefaults.standardUserDefaults()
let stringy1 = defaults.stringForKey("savemcw")
// Optional Chaining for stringy1
if let stringy = stringy1 {
calcLabel.text = stringy
}
}

Related

Swift change language in runtime without restarting the app

The code I wrote changes the language only if I restart the app, but I would like it to change language immediately.
It's possible to do it?
The language switch works fine, but only after I restart the app.
Maybe the problem is in the extension string?
Localizable.strings (it)
"label.language" = "Lingua";
Localizable.strings (en)
"label.language" = "Language";
struct ConstantFile {
static let labelLanguage = NSLocalizedString("label.language".localized, comment: "")
}
extension String {
var localized: String {
if let _ = UserDefaults.standard.string(forKey: "UserDefaultLanguage") {} else {
// setting value default
UserDefaults.standard.set(Language.italian.rawValue, forKey: "UserDefaultLanguage")
UserDefaults.standard.synchronize()
}
let lang = UserDefaults.standard.string(forKey: "UserDefaultLanguage")
let path = Bundle.main.path(forResource: lang, ofType: "lproj")
let bundle = Bundle(path: path!)
return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}
}
class ViewController: UIViewController {
#IBOutlet weak var labelTitleLanguage: UILabel!
let userDefaults = UserDefaults.standard
let LANGUAGE_KEY = "UserDefaultLanguage"
override func viewDidLoad() {
super.viewDidLoad()
labelTitleLanguage.text = ConstantFile.labelLanguage
}
#objc func tapBtnConfirmation(_ sender: UIButton) {
let list = list[self.pickerView.selectedRow(inComponent: 0)]
buttonPickerViewLanguage.setTitle(list, for: .normal)
if list == ConstantFile.labelLanguageItalian {
userDefaults.set(Language.italian.rawValue, forKey: LANGUAGE_KEY)
} else if list == ConstantFile.labelLanguageEnglish {
userDefaults.set(Language.english.rawValue, forKey: LANGUAGE_KEY)
}
}
}
You'll need to have a notification sent when changing language.
Then in your views, you observe this notification, and when it's raised, you update all translated texts in the view.
That's what I do

How to execute two completion blocks in a single function and pass the data of the completion block to next view controller?

This is my database structure:
I'm using a function with closure, performing two completion blocks and store the data in two separate arrays. Once I get the data I want to pass the data to next view controller into different variables, but instead I'm getting same value for both arrays.
#IBAction func GoToAnswerPage(_ sender: Any) {
self.getData(refe:JoinCodeTextField.text!) { (array) in
self.performSegue(withIdentifier:"JoinToAnswerPage",sender:array)
}
}
func getData(refe: String, completion: #escaping (([Any]) -> ())) {
var questionArray = [Any]()
var answerArray = [Any]()
let ref = Database.database().reference(fromURL: "https://pollapp-30419.firebaseio.com/").child("Questions/\(refe)/")
ref.child("Question_And_Options").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
questionArray.append(value)
}
completion(questionArray)
})
ref.child("Answer_Key").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
answerArray.append(value)
}
completion(answerArray)
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let joinViewController = segue.destination as? JoinAnswerViewController
else {
return
}
joinViewController.answers = sender as! [String]
joinViewController.options = sender as! [String]
}
On the next view controller.
var options = [Any]()
var answers = [Any]()
This is the output I'm getting:
answers-["Test Q-1", "Test A-1", "Test A-2"]
questions-["Test Q-1", "Test A-1", "Test A-2"]
answers-["Test A-1"]
questions-["Test A-1"]
Instead I should get:
questions-["Test Q-1", "Test A-1", "Test A-2"]
answers-["Test A-1"]
Your completion handler will be called twice, once for "answers" and once for "questions". They could come in either order, so you should pass an additional type in the completion to know which you have received. Use a [String : [Any]] dictionary to collect the two arrays, and call self.performSegue(withIdentifier:sender:) when you've received both arrays and stored them in the dictionary arrays.
In prepare(for:sender:) unpack the sender dictionary and assign the values:
#IBAction func GoToAnswerPage(_ sender: Any) {
var arrays = [String : [Any]]()
self.getData(refe: JoinCodeTextField.text!) { (array, type) in
arrays[type] = array
if arrays.count == 2 {
self.performSegue(withIdentifier:"JoinToAnswerPage",sender: arrays)
}
}
}
func getData(refe: String, completion: #escaping (([Any], String) -> ())) {
var questionArray = [Any]()
var answerArray = [Any]()
let ref = Database.database().reference(fromURL: "https://pollapp-30419.firebaseio.com/").child("Questions/\(refe)/")
ref.child("Question_And_Options").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
questionArray.append(value)
}
completion(questionArray, "question")
})
ref.child("Answer_Key").observeSingleEvent(of: .value,with: { snapshot in
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot, let value = rest.value{
answerArray.append(value)
}
completion(answerArray, "answer")
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let joinViewController = segue.destination as? JoinAnswerViewController
else {
return
}
guard let arrays = sender as? [String : [Any]],
let answers = arrays["answer"] as? [String],
let questions = arrays["question"] as? [String]
else { return }
joinViewController.answers = answers
joinViewController.options = questions
}
Note: When the user presses a button, they should get an immediate response. Since you are loading the data from the network, there may be a delay making the user wonder if anything is happening. It would be better to pass JoinCodeTextField.text! to JoinAnswerViewController and let it load the question/answer data. JoinAnswerViewController could display a UIActivityIndicatorView (spinner) while the data is loading to let the user know the data is coming. Once you have both arrays, you can set up the JoinAnswerViewController.

Store Username and Password with UserDefaults

I want to keep the user name, password and Chip number in the memory of the application. It keeps it in memory right now, but when I open and close the application from the background, the data is lost. But if I close the application on the loginview page, the data is lost if I close the application on the MainView page. This means that if I close the application on another page Other than the loginview page, the data is deleted. What should I do?
I can remember or recall the user's data with Labelcheckbox.
override func viewDidLoad() {
super.viewDidLoad()
chipField.text = chipInfoString
let save = UserDefaults.standard.bool(forKey: "RememberMe")
self.circleBox.isChecked = save
self.labelcheckbox.text = UserDefaults.standard.string(forKey: "RememberMe1")
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector:#selector(setdata) , userInfo: nil, repeats: true)
rememberaction()
}
let mainTableVC = MainTableViewController()
mainTableVC.chip1InfoString = self.chipField.text
mainTableVC.emailString = self.emailField.text
mainTableVC.passwordString = self.passwordField.text
let navController = UINavigationController(rootViewController: mainTableVC)
self.present(navController, animated: true, completion: {
})
#objc func rememberaction(){
if labelcheckbox.text == "Remember" {
rememberme()
}
if labelcheckbox.text == "Don't Remember" {
deleteee((Any).self)
}
}
#objc func rememberme() {
let mail = UserDefaults.standard.string(forKey: "mail")
let password = UserDefaults.standard.string(forKey: "password")
if let x = mail {
self.emailField.text = x
}
if let y = password {
self.passwordField.text = y
}
let save3 = UserDefaults.standard.object(forKey: "chipnumber")
if let z = save3 as? String {
self.chipField.text = z
}
}
#objc func setdata()
{
if labelcheckbox.text == "Remember" {
UserDefaults.standard.set(true, forKey:"RememberMe");
UserDefaults.standard.set(labelcheckbox.text, forKey: "RememberMe1")
UserDefaults.standard.set(emailField.text, forKey: "mail")
UserDefaults.standard.set(passwordField.text, forKey: "password")
UserDefaults.standard.set(chipField.text, forKey: "chipnumber")
UserDefaults.standard.synchronize()
}
else{
if labelcheckbox.text == "Don't remember"{
UserDefaults.standard.set(false, forKey:"RememberMe");
UserDefaults.standard.set(labelcheckbox.text, forKey: "RememberMe1")
UserDefaults.standard.set(chipField.text, forKey: "chipnumber")
}
}
}
class MainTableViewController: UITableViewController, UITabBarControllerDelegate{
#objc func action(_ sender: Any){
UserDefaults.standard.set(chip1InfoString, forKey: "chipnumber")
UserDefaults.standard.set(emailString, forKey: "email")
UserDefaults.standard.set(passwordString, forKey: "password")
UserDefaults.standard.synchronize()
}
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
self.action((Any).self)
})

enable a button if matches the last date stored +1

I am fairly new to Swift, but getting better.
I have managed to disable a button and store the date. And at this point I have reached the end of my knowledge, so I am hoping someone can help.
The button then needs to be enabled the next day by checking against the date stored, so the user can only use the function once per day.
code is as follows;
import Foundation
import UIKit
class tryForAFiver : UIViewController {
#IBOutlet weak var drinkImage: UIImageView!
#IBOutlet weak var redeemButton: UIButton!
override func viewDidLoad() {
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
#IBAction func redeemButton(_ sender: Any) {
let cocktailNumber = arc4random_uniform(32)
drinkImage.image = UIImage(named: "cocktailList\(cocktailNumber)")
let userDefaults = UserDefaults.standard
if var timeList = userDefaults.object(forKey: "timeList") as? [NSDate]
{
timeList.append(NSDate())
userDefaults.set(timeList, forKey: "timeList")
}
else
{
userDefaults.set([NSDate()], forKey: "timeList")
}
userDefaults.synchronize()
if let timeList = UserDefaults.standard.object(forKey: "timeList") as? [NSDate]
{
print(timeList)
}
self.redeemButton.isEnabled = false
}
}
thanks in advance for any help.
I made some changes to your code. Is it OK to use Date() instead of NSDate()? It's easier to work with in Swift.
Button action:
#IBAction func redeemButton(_ sender: Any) {
let userDefaults = UserDefaults.standard
if var timeList = userDefaults.object(forKey: "timeList") as? [Date]
{
timeList.append(Date())
userDefaults.set(timeList, forKey: "timeList")
}
else
{
userDefaults.set([Date()], forKey: "timeList")
}
userDefaults.synchronize()
if let timeList = UserDefaults.standard.object(forKey: "timeList") as? [Date]
{
print(timeList)
}
self.redeemButton.isEnabled = false
}
And on viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let timeList = UserDefaults.standard.object(forKey: "timeList") as? [Date], let lastDay = timeList.last
{
if Calendar.current.isDateInToday(lastDay) {
self.redeemButton.isEnabled = false
}
else {
self.redeemButton.isEnabled = true
}
}
}
This should get you on the right track. A word of warning: neither UserDefaults() nor Date() are safe for doing this kind of thing. Both are easily modified by the client. You should do a server check also if it's important.

NSDictionary becomes NSCFString when I save to NSUserDefaults in Swift

NSDictionary becomes NSCFString when I save to NSUserDefaults:
class SecondViewController: UIViewController {
#IBOutlet var questionforsave: UITextField!
#IBOutlet var answerforsave: UITextField!
var answersSaved = [String:String]()
#IBAction func savePressed(sender: AnyObject) {
if questionforsave.text.isEmpty || answerforsave.text.isEmpty{
let alert = UIAlertView()
alert.title = "Empty fields"
alert.message = "Please Enter Text In Both Text Fields"
alert.addButtonWithTitle("Ok")
alert.show()
}else{
println("is ok")
var answerSave = answerforsave.text
var questionSave = questionforsave.text
answersSaved[questionSave] = answerSave
let alert = UIAlertView()
alert.title = "Success!"
alert.message = "It worked!"
alert.addButtonWithTitle("Awesome!")
alert.show()
NSUserDefaults.standardUserDefaults().setObject(answersSaved, forKey: "answersSaved")
println(answersSaved)
}
}
override func viewDidLoad() {
super.viewDidLoad()
println(answersSaved)
if NSUserDefaults.standardUserDefaults().objectForKey("answersSaved") != nil {
answersSaved = NSUserDefaults.standardUserDefaults().objectForKey("answersSaved") as! [String:String]
}else{
println("No Defaults")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
//do the stuff here
resignFirstResponder()
return true
}
}
Your issue is that you are setting a string and then trying to retrieve a dictionary, but I would recommend using if let to retrieve the dictionary and then optionally cast it to be [String: String]:
var answersSaved = [String: String]()
override func viewDidLoad() {
super.viewDidLoad()
if let answersFromDefaults = NSUserDefaults.standardUserDefaults().dictionaryForKey("answersSaved") as? [String: String] {
answersSaved = answersFromDefaults
} else {
// Failed to retrieve value from NSUserDefaults
}
answersSaved = ["test": "a"] // Set answersSaved somewhere in your code
}
#IBAction func saveButtonPressed(sender: UIBarButtonItem) {
NSUserDefaults.standardUserDefaults().setObject(answersSaved, forKey: "answersSaved")
}
NSUserDefaults.standardUserDefaults().setObject("answersSaved", forKey: "answersSaved")
You're writing the string "answersSaved" into NSUserDefaults. I assume you meant answersSaved.
You should cast it optionally as a [String:String] dictionary.
Try this:
if let answersFromDefaults = NSUserDefaults.standardUserDefaults().dictionaryForKey("answersSaved") as? [String: String] {
answersSaved = answersFromDefaults
} else {
// Failed to retrieve value from NSUserDefaults
}
When you load the dictionary into NSUserDefaults you should do it like this:
NSUserDefaults.standardUserDefaults().setObject(answersSaved, forKey: "answersSaved")
This saves it as a generic NSObject. When you want to read out the data though use the command:
answersSaved = NSUserDefaults.standardUserDefaults().dictionaryForKey("answersSaved")
This should specify that the object is a Dictionary