User defaults Only Working On Restart - ios

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

Related

Swift: How to check if UserDefaults exists and if not save a chosen standard value?

I am trying to check if a UserDefaults Key exists and if not set it to a standard value of my choice, but the answers here on stack overflow didn't help me to get this working.
Essentially I have a couple of UISwitches, one is on and the rest is set to off from the beginning. Now my problem is that I don't know how to save these initial states into UserDefaults when the viewController is loaded and those keys do not exists.
This is how I tried to check if the key for the UISwitch exists and if not set it to true (because that's the state I want it to be) and then check again what the bool for the key is and set the UISwitch to it (this is essentially important when the viewController is opened another time):
func setupSwitches() {
let defaults = UserDefaults.standard
//check if the key exists
if !defaults.bool(forKey: "parallax") {
switchParallax.setOn(true, animated: true)
}
//check for the key and set the UISwitch
if defaults.bool(forKey: "parallax") {
switchParallax.setOn(true, animated: true)
} else {
switchParallax.setOn(false, animated: true)
}
}
When the user presses the corresponding button I set the UserDefaults key like this:
#IBAction func switchParallax_tapped(_ sender: UISwitch) {
let defaults = UserDefaults.standard
if sender.isOn == true {
defaults.set(true, forKey: "parallax")
} else {
defaults.set(false, forKey: "parallax")
}
}
This is obviously working, but the problem is in the first code above.
First of all I am not sure how to check if it exists and if not set it to "true" and since the function is called setupSwitches() it is runs every time the viewController is shown.
So I don't know if there is a better way (eg. because of the memory issues) to check if a key exists, if not set it to true and if it already exists get the bool from UserDefaults and set the switch to the right state.
The problem is you can't determine exists with UserDefaults.standard.bool(forKey: key). UserDefaults.standard.object(forKey: key) returns Any? so you can use it to test for nil e.g.
extension UserDefaults {
static func exists(key: String) -> Bool {
return UserDefaults.standard.object(forKey: key) != nil
}
}

First time launch / segue swift

what i am trying to accomplish is once the app is launched it will check for first time use. if it is the first time use it will take you to a view controller to enter credentials, else it will take you to to the main menu of the app. this is what i have so far but every time i launch it will give me a blank page with the error message of "A segue must either have a performHandler or it must override -perform.
" i have both segues linked on storyboard. can any one please steer me in the right direction.
let defaults = UserDefaults.standard
if defaults.string(forKey: "isAppAlreadyLaunchedOnce") != nil{
print("first time")
self.performSegue(withIdentifier: "toToken", sender: nil)
}else{
defaults.set(true, forKey: "isAppAlreadyLaunchedOne")
defaults.synchronize()
print("not first")
self.performSegue(withIdentifier: "toMainMenu", sender: nil)
}
If your segue type is set to be Custom in Storyboard — you have to subclass UIStoryboardSegue with your own logic in order for it to work.
class MySegue: UIStoryboardSegue {
override func perform() {
// your custom transition logic
}
}
Otherwise just use one of the existing presets from iOS SDK.
Your method is very confusing. The way I go about this problem is shown below.
override viewDidLoad() {
super.viewDidLoad
let checkForFirstTimeLaunch = Userdefaults.standard.string(forKey: "Flag")
if checkForFirstTimeLaunch == false {
print("First time launch")
//if user launches app for the first time, it will go here
} else {
//otherwise, it will go here
}
}

How to save data in swift when changing view controllers?

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.

Check if user has opened every viewcontroller

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!

Best Way to Implement an Intro Sequence? Swift

I'm trying to add an intro sequence to my code so that if it's the first time the app is opened, the user can enter some basic information (which I can then store in UserDefaults).
The way that I was thinking of doing this is by having a variable called isFirstTime which is initially set to true. Every time the app is opened, it'll check if there is a value for isFirstTime in UserDefaults. If it isn't there, it'll trigger the View Controller that has my intro sequence to appear. Once the intro sequence is finished, isFirstTime will be set to false and then stored in UserDefaults.
Is this a correct implementation, or is there a more efficient way?
EDIT: If anyone is interested, this is the code I used to implement my intro sequence. I first assign a boolean variable outside of my View Controller that keeps track of whether it's the first time opening the app or not.
var isFirstTime = true
Then, in my ViewDidAppear (it does not work in the ViewDidLoad method), I added this code which checks whether or not I already have a UserDefault for my isFirstTime variable. If yes, I then execute the rest of my program, but if not, I start up my intro sequence's View Controller.
if UserDefaults.standard.object(forKey: "isFirstTime") != nil{
// Not the first time app is opened
isFirstTime = false // I use isFirstTime elsewhere in my code too.
} else {
let introVC = self.storyboard?.instantiateViewController(withIdentifier: "intro")
self.present(introVC!, animated: false, completion: nil)
}
In my intro sequence View Controller, when I am done with my gathering the user's basic information, I do two things: the first is changing the value of isFirstTime and setting it as a UserDefault, and the second is dismissing the View Controller.
isFirstTime = false
UserDefaults.standard.set(isFirstTime, forKey: "isFirstTime")
dismiss(animated: false, completion: nil)
You can achieve it easily. This is code which I have used for it.
Step 1 First create a file called UserDefaultManager.swift
import UIKit
// User Defaults Manager Constants
let kIsFirstTimeLaunch = "IsFirstTimeLaunch"
class UserDefaultsManager: NSObject {
// MARK: Setter Methods
class func setIsFirstTimeLaunch(flag: Bool) {
NSUserDefaults.standardUserDefaults().setBool(flag, forKey:kIsFirstTimeLaunch)
NSUserDefaults.standardUserDefaults().synchronize()
}
// MARK: Getter Methods
class func isFirstTimeLaunch() -> Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(kIsFirstTimeLaunch)
}
// MARK: Reset Methods
class func resetIsFirstTimeLaunch() {
NSUserDefaults.standardUserDefaults().removeObjectForKey(kIsFirstTimeLaunch)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
Step 2: In your Implementation file check it like below :
if(!UserDefaultsManager.isFirstTimeLaunch()) {
// Your code here.
let introVC = self.storyboard?.instantiateViewController(withIdentifier: "intro")
self.present(introVC!, animated: false, completion: nil)
// Update value in user defaults
UserDefaultsManager.setIsFirstTimeLaunch(true)
}

Resources