So I have my main view controller and I have my settings view controller. When I go into the settings and flip a switch and go back to the main, my settings view controller goes back to its default settings and same with the name. How can I make it so it will save the data while the app is open and not go back to its default values?
Thanks
I prefer to use delegates instead of checking the user defaults every time I leave the settings page.
protocol SettingsViewControllerDelegate: class {
func settingsDidChange()
}
class SettingsViewController: UIViewController {
weak var delegate: SettingsViewControllerDelegate?
func someSettingChanged(){
let defaults = UserDefaults.standard
//... get the new settings
defaults.set(newSettingsValue, forKey: "settingsKey")
defaults.synchronize()
delegate?.settingsDidChange()
}
}
class MainViewController: UIViewController {
func showSettingsVC(){
let settingsViewController = //Initialization method
settingsViewController.delegate = self
self.show(settingsViewController, sender: self)
}
}
extension MainViewController: SettingsViewControllerDelegate{
func settingsDidChange() {
let defaults = UserDefaults.standard
if let settingsValue = defaults.value(forKey: "settingsKey"){
//// do the appropriate changes
}
}
}
you can store your button state in userdefault
here is the example for swift 3:
you can get button state in actioin for valuechanged then you can store that in
UserDefaults.standard.set(false, forKey: "buttonState")
let buttonState = UserDefaults.standard.bool(forKey: "buttonState")
if buttonState == true {
}
UserDefaults.standard.synchronize()
1) When view will appear call for setting screen get your data from NSUserDefaults and then fill data to options.
2) When user changes something update your UserDefaults and dont forget to Synchronize it.
Related
I have created a dummy IOS Application to explain my questions well. Let me share it with all details:
There are 2 Pages in this dummy IOS Application: LoginPageViewController.swift and HomepageViewController.swift
Storyboard id values are: LoginPage, Homepage.
There is login button in Login page.
There are 3 labels in Homepage.
App starts with Login page.
And i have a class file: UserDetail.swift
And there is one segue from login page to home page. Segue id is: LoginPage2Homepage
UserDetail.swift file
import Foundation
class UserDetail {
var accountIsDeleted = false
var userGUID : String?
var userAge: Int?
}
LoginPageViewController.swift file
import UIKit
class LoginPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func loginButtonPressed(_ sender: UIButton) {
var oUserDetail = UserDetail()
oUserDetail.accountIsDeleted = true
oUserDetail.userAge = 38
oUserDetail.userName = "Dirk Kuyt"
UserDefaults.standard.set(oUserDetail, forKey: "UserCredentialUserDefaults")
UserDefaults.standard.synchronize()
performSegue(withIdentifier: "LoginPage2Homepage", sender: nil)
}
}
HomepageViewController.swift file
import UIKit
class HomepageViewController: UIViewController {
var result_userGUID = ""
var result_userAge = 0
var result_isDeleted = false
#IBOutlet weak var labelUserGuidOutlet: UILabel!
#IBOutlet weak var labelAgeOutlet: UILabel!
#IBOutlet weak var labelAccountIsDeletedOutlet: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.setVariablesFromUserDefault()
labelUserGuidOutlet.text = result_userGUID
labelAgeOutlet.text = String(result_userAge)
labelAccountIsDeletedOutlet.text = String(result_isDeleted)
}
func setVariablesFromUserDefault()
{
if UserDefaults.standard.object(forKey: "UserCredentialUserDefaults") != nil
{
// I need a help in this scope
// I have checked already: My UserDefault exists or not.
// I need to check type of the value in UserDefault if UserDefault is exists. I need to show print if type of the value in UserDefault is not belongs to my custom class.
// And then i need to cast UserDefault to reach my custom class's properties: userGUID, userAge, isDeleted
}
else
{
print("there is no userDefault which is named UserCredentialUserDefaults")
}
}
}
My purposes:
I would like to store my custom class sample(oUserDetail) in UserDefaults in LoginPageViewController with login button click action.
I would like to check below in home page as a first task: My UserDefault exists or not ( I did it already)
I would like to check this in home page as a second task: if my UserDefault exists. And then check type of the UserDefault value. Is it created with my custom class? If it is not. print("value of userdefault is not created with your class")
Third task: If UserDefault is created with my custom class. And then parse that value. Set these 3 variables: result_userGUID, result_userAge, result_isDeleted to show them in labels.
I get an error after I click the login button in Login Page. Can't I store my custom class in UserDefaults? I need to be able to store because I see this detail while I am writing it:
UserDefaults.standart.set(value: Any?, forKey: String)
My custom class is in Any scope above. Isn't it?
You can't store a class instance without conforming to NSCoding / Codable protocols
class UserDetail : Codable {
var accountIsDeleted:Bool? // you can remove this as it's useless if the you read a nil content from user defaults that means no current account
var userGUID : String?
var userAge: Int?
}
store
do {
let res = try JSONEncoder().encode(yourClassInstance)
UserDefaults.standard.set(value:res,forKey: "somekey")
}
catch { print(error) }
retrieve
do {
if let data = UserDefaults.standard.data(forKey:"somekey") {
let res = try JSONDecoder().decode(UserDetail.self,from:data)
} else {
print("No account")
}
}
catch { print(error) }
I want to take user settings details from this view controller and read these details to the previous view controller. I have tried many different ways, but I cannot take values until I visit this view controller
I have tried first method from this page Pass Data Tutorial
This method is also not working. I think it is very simple, but I cannot figure out the right way to do it.
class SetConvViewController: UIViewController {
var engS = "engS"
#IBOutlet weak var swithEnglish: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let eng2 = defaults.value(forKey: engS)
{
swithEnglish.isOn = eng2 as! Bool
}
}
let defaults = UserDefaults.standard
#IBAction func switchEng(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: engS)
}
}
If I understand you correctly from this part - „but I cannot take values until I visit this view controller” - your problem lies with the fact, that until you visit your settings, there is no value for them in UserDefaults.
If you are reading them using getObject(forKey:) method, I’d recommend you to switch to using getBool(forKey:), since it will return false even if the value has not been set yet for that key ( docs )
Anyhow, if you want to set some default/initial values you can do so in your didFinishLaunching method in AppDelegate :
if UserDefaults.standard.object(forKey: „engS”) == nil {
// the value has not been set yet, assign a default value
}
I’ve also noticed in your code that you used value(forKey:) - you should not do that on UserDefaults - this is an excellent answer as to why - What is the difference between object(forKey:) and value(forKey:) in UserDefaults?.
On a side note, if you are using a class from iOS SDK for the first time, I highly recommend looking through its docs - they are well written and will provide you with general understanding as to what is possible.
I would recommend you to store this kind of data as a static field in some object to be able to read it from any place. e.g.
class AppController{
static var userDefaults = UserDefaults.standard
}
and then you can save it in your SetConvViewController like
#IBAction func switchEng(_ sender: UISwitch) {
AppController.userDefaults.set(sender.isOn, forKey: engS)
}
and after that you can just read it from any other view controller just by calling
AppController.userDefaults
Using segues you can set to any destination whether it be next vc or previous:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PreviousVC" {
if let prevVC = segue.destination as? PreviousViewController {
//Your previous vc should have your storage variable.
prevVC.value = self.value
}
}
If you're presenting the view controller:
Destination vc:
//If using storyboard...
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DestinationViewController") as! DestinationViewController
destVC.value = self.value
self.present(destVC, animated: true, completion: nil)
Previous vc:
weak var prevVC = self.presentingViewController as? PreviousViewController
if let prevVC = prevVC {
prevVC.value = self.value
}
I am trying to implement a light and dark mode in my application. In the settingsViewController I have these lines of code:
//Sets user default for colour
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
//UISegment control for user to pick colour
#IBOutlet var colourSegment: UISegmentedControl!
//Updates lightMode based on user selection
#IBAction func didChangeColours(_ sender: Any) {
if colourSegment.selectedSegmentIndex == 0 {
UserDefaults.standard.set(true, forKey: "lightMode")
} else if colourSegment.selectedSegmentIndex == 1 {
UserDefaults.standard.set(false, forKey: "lightMode")
}
}
In my entryViewController, in my viewDidLoad, I have:
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
if lightMode == false {
Colours.darkMode()
}
customisations()
The issue that I'm running into is that for some reason, my application is only changing it's colour scheme after I restart it. That is, if the user selects the darkIndex of the colourSegment, the application only updates the colour after I restart. I am wondering what is the solution for this.
The problem is in the line -
//Sets user default for colour
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
this line is not for setting the Userdefaults but instead it gets the UserDefaults. Since you use it before setting the default Value, it doesn't reflect the right segmented choice. So your setting part is correct and you should fetch the value only after you have set it.
Also in your entryViewController, instead of using it from settingsVC, do below -
//get from UserDefaults
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
//Compare the previous fetched value
if lightMode == false {
Colours.darkMode()
}
//This function sets the colour for the elements
colours()
Because you are assigning the lightMode value ONLY 1 time during init, you don't reflect the changes to the variable, so it will always be that value
To always get the lastest value, use this:
let lightMode: Bool {
get {
return UserDefaults.standard.bool(forKey: "lightMode")
}
}
Also, you should call the color change immediatelly after change the value
I am assuming that you are returning to your entryViewController from your settingsViewController; Since you are returning to an existing view controller instance, the code in viewDidLoad is not executed when you return.
Move the code to viewWillAppear; This way your code will execute prior to the view controller appearing even when you return to the existing instance:
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let lightMode = UserDefaults.standard.bool(forKey: "lightMode")
if lightMode == false {
Colours.darkMode()
}
}
In my application, I use UserDefaults to store the user's login status (whether they are logged in or not), and their username. It works fine in that when I login, close the app, and open it again my app skips the login page and recognizes that I am already logged in. Although, I am now trying to install a logout button to a separate viewController. When clicked, this logout button needs to 1.) Reset UserDefaults.loginStatus to "False" 2.) Reset UserDefaults.username to nil 3.) Perform a segue to the login page.
Here is the related code from my ViewController.swift file. This is the first viewController which controls the loginPage.
import UIKit
import Firebase
let defaults = UserDefaults.standard
class ViewController: UIViewController {
func DoLogin(username: String, password: String) {
//I Am not including a lot of the other stuff that takes place in this function, only the part that involves the defaults global variable
defaults.setValue(username, forKey: "username")
defaults.setValue("true", forKey: "loginStatus")
defaults.synchronize()
self.performSegue(withIdentifier: "loginToMain", sender: self) //This takes them to the main page of the app
}
override func viewDidLoad() {
super.viewDidLoad()
if let stringOne = defaults.string(forKey: "loginStatus") {
if stringOne == "true" { //If the user is logged in, proceed to main screen
DispatchQueue.main.async
{
self.performSegue(withIdentifier: "loginToMain", sender: self)
}
}
}
}
Below is my code in SecondViewController.swift, particularly the logout function.
import UIKit
import Firebase
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let username = defaults.string(forKey: "username") {
checkAppSetup(username: username) //This is an unrelated function
//I included this because this works fine. Proving that I am able to read the defaults variable fine from this other viewController
}
}
#IBAction func logout(_ sender: Any) {
defaults.setValue("false", forKey: "username")
defaults.setValue("false", forKey: "loginStatus")
defaults.synchronize()
performSegue(withIdentifier: "logoutSegue", sender: nil)
}
When the logout function is run, the segue performs fine but the default values do not change. Can someone explain why and what I can do to get around this?
**Side note, I am not actually going to set the defaults to "false" and "false". That is just temporary for while I am debugging this issue.
Several things.
You should be using set(_:forKey:) and object(_:forKey) to read and write key/value pairs to defaults, not setValue(_:forKey). (Your use of defaults.string(forKey: "loginStatus") is correct, however.)
You should probably be writing a nil to the userName key:
defaults.set(nil, forKey: "username")
And your logout IBAction should almost certainly be setting loginStatus to false, not true.
Try changing those things.
Also, there is no reason to call synchronize unless you are terminating your app in Xcode rather than pressing the home button on the device/simulator in order to let it exit normally.
Hey i used the exactly same concept recently :
1) In your initial view, in the viewDidLoad() , check whether somebody is already logged in or not, and only one user can be logged in one device at a time, so we check like
let defaults = UserDefaults.standard
if defaults.object(forKey: "userName") != nil && defaults.object(forKey: "userPassword") != nil
{
let loginObject = self.storyboard?.instantiateViewController(withIdentifier: "YourSecondViewController") as! YourSecondViewController
//As someone's details are already saved so we auto-login and move to second view
}}
2) In your sign in button function , check whatever condition you want to check and then, inside the same, if condition satisfies then save data to userDefaults.
// If no details are saved in defaults, then control will come to this part, where we will save the entered userName and Password
let defaults = UserDefaults.standard
defaults.set(self.enteredUseName, forKey: "userName")
defaults.set(self.enteredPassword, forKey: "Password")
defaults.synchronize()
3) On logout button , delete the userDefaults and load the login view again :
let defaults = UserDefaults.standard
defaults.removeObject(forKey: "userName") //We Will delete the userDefaults
defaults.removeObject(forKey: "userPassword")
defaults.synchronize() //Sync. the defaults.
navigationController?.popToRootViewController(animated: true) //Move Back to initial view.
4) If you are using a navigation control, that you must be using :P then you will surely see the back button which will open the second view if clicked, for that you can hide the navigation bar in viewDidLoad() of your login view
self.navigationController?.navigationBar.isHidden = true
I would like to know, is it possible to check if user has opened every viewcontroller that application has?
I would like to do it because I give user badges and it is the one I would like to give.
I assume I have to store something into userDefaults and somehow gather the info and then do what I want to do, am I right? If I am right then should I do some global variable and add count every time user opens new viewcontroller?
Any info is appreciated.
Make an option set to represent every viewController. In each viewControllers ViewDidAppear, read and update a field from Userdefaults that stores the option set of displayed viewControllers then write it back to Userdefaults.
struct UserDefaultsKey {
static let displayedViewControllers = "displayedViewControllers"
}
struct DisplayedViewControllers: OptionSet {
let rawValue: Int
static let vc1 = DisplayedViewControllers(rawValue: 1 << 0)
static let vc2 = DisplayedViewControllers(rawValue: 1 << 1)
static let vc3 = DisplayedViewControllers(rawValue: 1 << 2)
static let vc4 = DisplayedViewControllers(rawValue: 1 << 3)
static let all = [vc1, vc2, vc3, vc4]
}
class vc1: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
var displayedViewControllers = DisplayedViewControllers(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKey.displayedViewControllers))
displayedViewControllers.insert(.vc1)
UserDefaults.standard.set(displayedViewControllers.rawValue, forKey: UserDefaultsKey.displayedViewControllers)
}
}
func haveAllViewControllersBeenDisplayed() -> Bool {
let displayedViewControllers = DisplayedViewControllers(rawValue: UserDefaults.standard.integer(forKey: UserDefaultsKey.displayedViewControllers))
for controller in DisplayedViewControllers.all {
if displayedViewControllers.contains(controller) == false {
return false
}
}
return true
}
You can do it in this way, if you are using UINavigationController then at the end of every UINavigationController Stack set a true key in UserDefaul like this
UserDefaults.standard.set(true, forKey: "NavigationStack1")
Now let us suppose your App has 4 diffrent type of Navigations then you can set those like this, with diffrent key
UserDefaults.standard.set(true, forKey: "NavigationStack1")
UserDefaults.standard.set(true, forKey: "NavigationStack2")
UserDefaults.standard.set(true, forKey: "NavigationStack3")
UserDefaults.standard.set(true, forKey: "NavigationStack4")
Then at the end of every UINavigationController's Stack you need to check whether user has visited all the Navigations like this
if UserDefaults.standard.bool(forKey: "NavigationStack1")&&UserDefaults.standard.bool(forKey: "NavigationStack2")&&UserDefaults.standard.bool(forKey: "NavigationStack3")&&UserDefaults.standard.bool(forKey: "NavigationStack4"){
// Give Badge to user
}
Also you can do it for each UIViewController, in the viewDidLoad of each controller set the key for that viewController to true then, check the result of all the key, in this way you will be able to know whether user has visited all the UIViewController of your app.
Assume you have three ViewControllers: ViewController1, ViewController2, ViewController3
Method 1: Array of ViewController names in the NSUserDefaults:
Maintain a Set of opened ViewController Names: (The Set can be serialized/deserialized to NSUserDefaults)
var openedViewControllers = Set<String>()
Once viewController1 has been opened, you insert it to the set.
openedViewControllers.insert(viewController1Name)
How to check if all viewController were opened:
if openedViewController.count == 3{
//All three viewControllers were opened
}
Method 2: Use Bit Masking: (will be save as normal UInt64)
You use an UInt64 = 0 and every view controller will be mapped to a bit of Int64.
Once you open that view controller you changed the corresponding bit from 0 to 1.
Example:
ViewController1 (opened), ViewController2(never opened), ViewController3(opened) => BitMask will be 1010000....
How to check if all viewController were opened:
if BitMask == 3{
//All three viewControllers were opened
}
N.B. With the second approach, you can only have 64 ViewControllers in you app
You could save an array of Bool in CoreData with the list of the View Controller name. And check it every time a ViewController is open.
You can also use UserDefaults.standard.setValue and stock your Dictionary or Array.
Hope it helps!