I want to check the State of an UISwitch from another View Controller. And if the switch is on, then I want the backgroundColor of the View to be red, otherwise it should be green. I tried to use UserDefaults, but I have no idea what to start with. Any help would be greatly appreciated!
NOTE: THIS IS THE VERSION FOR SWIFT 3:
First of all, I've set the initial state of the switch to off. I have added a button, which leads us to the secondViewController and also checks the state of the switch. Here is my code(for the View Controller which contains the switch):
#IBOutlet weak var switch1: UISwitch!
let defaults = UserDefaults.standard
var switchON : Bool = false
#IBAction func checkState(_ sender: AnyObject) {
if switch1.isOn{
switchON = true
defaults.set(switchON, forKey: "switchON")
}
if switch1.isOn == false{
switchON = false
defaults.set(switchON, forKey: "switchON")
}
}
And for the secondView:
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
if defaults.value(forKey: "switchON") != nil{
let switchON: Bool = defaults.value(forKey: "switchON") as! Bool
if switchON == true{
self.view.backgroundColor = UIColor.red()
}
else if switchON == false{
self.view.backgroundColor = UIColor.green()
}
}
}
Related
I've declared a boolean with default value true, if my UISwitch is on I want boolean variable to be true else false, my code works fine until I try to store that boolean in UserDefaults when I reset the xCode Simulator I check with print method but it is not really saved...
any solution will be appericated.
var userDefaultSamarxvo:Bool = true
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
defaults.set(userDefaultSamarxvo,forKey: "Samarxvo") // set of userdefault
if userDefaultSamarxvo {
print("hello")
}else{
print("olleh")
}
}
#IBAction func samarxvoDidChange(_ sender: UISwitch) {
if sender.isOn {
userDefaultSamarxvo.toggle()
print("samarxo")
}else{
userDefaultSamarxvo.toggle()
print("samarxo 1 ")
}
}
In viewDidLoad you want to load the saved value from user defaults. When the switch changes you need to save the new value to user defaults.
Setting a local variable (userDefaultSamarxvo) doesn't change what is stored in the user defaults; Local variables don't bind to user defaults storage.
There is an added complication; bool(forKey) will return false if there is no value for the key in UserDefaults. If you want the initial value to be true then you need to handle that in some way. You can use object(forKey) which returns an optional
var userDefaultSamarxvo:Bool = true {
didSet {
UserDefaults.standard.set(userDefaultSamarxvo, forKey:switchKey)
}
}
let defaults = UserDefaults.standard
let switchKey = "Samarxvo"
#IBOutlet weak var theSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if defaults.object(forKey: switchKey) != nil {
userDefaultSamarxvo = defaults.bool(forKey: switchKey)
}
self.theSwitch.isOn = userDefaultSamarxvo
}
#IBAction func samarxvoDidChange(_ sender: UISwitch) {
userDefaultSamarxvo = sender.isOn
}
I have used a didSet clause to update the user defaults value when the property changes.
My intention is to add a tutorial to my app, which is supposed to show only once per user.
So far my approach has been to create a func which is being called when viewDidLoad:
func checkInitialVc() {
firstTime = defaults.bool(forKey: "firstTime")
if firstTime {
let initialData = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.show(initialData, sender: initialData)
}
}
The problem is that as I open the App, even tho it's the first time, nothing happens.
firstTime is declared global:
var firstTime: Bool = false
In the ViewController which is to be opened, but doesn't, I've got:
#IBAction func buttonStart(_ sender: UIButton) {
firstTime = false
defaults.set(firstTime, forKey: "firstTime")
performSegue(withIdentifier: "goToSecond", sender: self)
}
Another way is changing semantics of the bool retrieved from user defaults, for example from firstTime to tutorialShown since if the key doesn't exist user defaults will return false.
func checkInitialVc() {
tutorialShown = defaults.bool(forKey: "tutorialShown")
if !tutorialShown {
let initialData = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.show(initialData, sender: initialData)
}
}
When you start the application it loads every swift file including your boolean.
So in this case the steps it takes are:
var firstTime: Bool = False // Everytime you startup -> false
class YourViewController: UIViewController {
override func viewDidload(){
checkInitialVc()
}
func checkInitialVc() {
firstTime = defaults.bool(forKey: "firstTime")
if firstTime { // is False so never enters brackets
let initialData = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.show(initialData, sender: initialData)
}
}
}
So just try setting the global Bool as a UserDefault so that when you load the app it already has the value that will be set with your button after the view has loaded and the button has been pressed:
var firstTime = UserDefaults.standard.bool(forKey: "firstTime")
class YourViewController: UIViewController {
override func viewDidload(){
checkInitialVc() //calls your function when view is available
}
func checkInitialVc() {
if !UserDefaults.standard.bool(forKey:"firstTime"){ // UserDefaults.standard.bool is by default false
let initialData = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.show(initialData, sender: initialData)
}
#IBAction func buttonStart(_ sender: UIButton) {
UserDefaults.standard.set(true, forKey: "firstTime")
/*
or
*/
performSegue(withIdentifier: "goToSecond", sender: self)
}
}
The use of a global is wrong. Change
func checkInitialVc() {
firstTime = defaults.bool(forKey: "firstTime")
if firstTime {
let initialData = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.show(initialData, sender: initialData)
}
}
To:
func checkInitialVc() {
UserDefaults.standard.register(defaults:["firstTime":true]) // <--
firstTime = UserDefaults.standard.bool(forKey: "firstTime")
if firstTime {
UserDefaults.standard.set(false, forKey: "firstTime") // <--
let initialData = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.show(initialData, sender: initialData)
}
}
I am currently working on an app and I am stuck on the following: I have my mainVC (ReceiveInputVC), which after I enter an input, it goes to the secondVC (TimeLeftVC) and it updates all of its labels with results from the inputs received from the mainVC. My question is: How can I, after clicking on the arrow to go back to the mainVC or even if I close the app, when I click on the arrow from the mainVC to go to the secondVC have my labels showing the same values as before the user closed the application or returned to the main screen?
import UIKit
extension UIViewController {
func hideKeyboard() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
class ReceiveInputVC: UIViewController {
#IBOutlet weak var hourglassButton: UIButton!
#IBOutlet weak var whatIsYourAgeField: UITextField!
#IBOutlet weak var ageToDieField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.hideKeyboard()
}
#IBAction func arrowBtnPressed(_ sender: Any) {
// When pressed should show go to TimeLeftVC and show last result from the first time user entered the inputs, if nothing has been typed yet and no data has been saved an alert should pop up asking the user to enter an input on both fields
}
#IBAction func hourglassBtnPressed(_ sender: Any) {
let checkAgeField: Int? = Int(whatIsYourAgeField.text!)
let checkDyingAgeField: Int? = Int(ageToDieField.text!)
if (whatIsYourAgeField.text == "" || ageToDieField.text == "") || (whatIsYourAgeField.text == "" && ageToDieField.text == "") {
alert(message: "You must enter an input on both fields")
} else if checkAgeField! < 1 || checkDyingAgeField! > 100 {
alert(message: "You must enter an age higher than 1 and a dying age lower than 100")
} else if (checkAgeField! > checkDyingAgeField!) || (checkAgeField! == checkDyingAgeField!) {
alert(message: "You must enter an age lower than a dying age")
} else {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
}
}
func alert(message: String, title: String = "Alert") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Passing the data entered from ReceiveInputVC to TimeLeftVC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondScreen" {
let destinationTimeLeftVC = segue.destination as! TimeLeftVC
destinationTimeLeftVC.ageReceived = whatIsYourAgeField.text
destinationTimeLeftVC.ageToDieReceived = ageToDieField.text
}
}
}
import CircleProgressBar
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
// Update Circle Progress Bar
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Project on GitHub: https://github.com/mvvieira95/Time-Life.git
You can use Coredata or another data base or user default
User default implementation:
#IBAction func arrowBtnPressed(_ sender: Any) {
UserDefaults.standard.set("your input values from text field or ...", forKey: "key")
}
In second view controller get it with
UserDefaults.standard.string(forKey: "key")
You can save and restore states with these methods
application:shouldSaveApplicationState and application:shouldRestoreApplicationStat.
Example:
func application(_ application: UIApplication,
shouldSaveApplicationState coder: NSCoder) -> Bool {
// Save the current app version to the archive.
coder.encode(11.0, forKey: "MyAppVersion")
// Always save state information.
return true
}
func application(_ application: UIApplication,
shouldRestoreApplicationState coder: NSCoder) -> Bool {
// Restore the state only if the app version matches.
let version = coder.decodeFloat(forKey: "MyAppVersion")
if version == 11.0 {
return true
}
// Do not restore from old data.
return false
}
You can explore the document in https://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches?language=objc
Thanks guys, I came up with a solution:
class ReceiveInputVC: UIViewController {
#IBAction func arrowBtnPressed(_ sender: Any) {
let defaults = UserDefaults.standard
if let _ = defaults.object(forKey: "yearsSaved"), let _ = defaults.object(forKey: "daysSaved"), let _ = defaults.object(forKey: "hoursSaved") {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
} else {
alert(message: "You must first enter an input")
}
}
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
yearsLeftLabel.text = defaults.object(forKey: "yearsSaved") as? String
daysLeftLabel.text = defaults.object(forKey: "daysSaved") as? String
hoursLeftLabel.text = defaults.object(forKey: "hoursSaved") as? String
}
override func viewWillAppear(_ animated: Bool) {
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
// Store Data
let defaults = UserDefaults.standard
defaults.set(yearsLeftLabel.text, forKey: "yearsSaved")
defaults.set(daysLeftLabel.text, forKey: "daysSaved")
defaults.set(hoursLeftLabel.text, forKey: "hoursSaved")
// Update Circle Progress Bar
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Having troubles now updating that progressBar when I go back to the view...
This question already has answers here:
How do i keep UISwitch state when changing ViewControllers?
(3 answers)
Closed 5 years ago.
I would like to save the State of an UISwitch after to change between View Controllers. Any help would be greatly appreciated!
I have a first View Controller with an UISwitch, to control the music in the background in different View Controllers:
#IBOutlet weak var SwitchMusic: UISwitch!
let defaults = UserDefaults.standard
var switchON : Bool = false
#IBAction func checkState(_ sender: UISwitch) {
if (sender.isOn == true)
{
switchON = true
defaults.set(switchON, forKey: "switchON")
MusicHelper.sharedHelper.playBackgroundMusic()
}
if (sender.isOn == false)
{
switchON = false
defaults.set(switchON, forKey: "switchON")
MusicHelper.sharedHelper.stopBackgroundMusic()
}
}
And a Second View Controller to load or no the music in the background if the switch is On or Off:
override func viewDidLoad() {
super.viewDidLoad()
if defaults.value(forKey: "switchON") != nil{
let switchON: Bool = defaults.value(forKey: "switchON") as! Bool
if switchON == true{
MusicHelper.sharedHelper.playBackgroundMusic()
}
else if switchON == false{
MusicHelper.sharedHelper.stopBackgroundMusic()
}
}
}
Also I have a class with the music:
class MusicHelper {
let defaults = UserDefaults.standard
static let sharedHelper = MusicHelper()
var musicBackgroundIntro:AVAudioPlayer = AVAudioPlayer()
func playBackgroundMusic() {
do {
let audioPath = Bundle.main.path(forResource: "Music", ofType: "mp3")
try musicBackgroundIntro = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
musicBackgroundIntro.numberOfLoops = -1
musicBackgroundIntro.prepareToPlay()
musicBackgroundIntro.play()
} catch {
print("Cannot play the file")
}
}
func stopBackgroundMusic() {
musicBackgroundIntro.stop()
}
}
Now it is working perfectly the music in the background between View Controllers, and it is possible to turn off and on... but unfortunately do not save the current state of the UISwitch, and always when I enter in the First View Controller the state of the Switch is On.
Also any idea that how will be possible to apply in a Slider too?
Any help would be greatly appreciated!
Thanks
The easiest way for you would be to create a static var isSwitchOn: Bool = false
This state will be preserved between back and forth transitions.
You should reflect the state, if music is playing...
class MusicHelper {
public isPlaying: Bool {
get {
return musicBackgroundIntro.isPlaying
}
}
// your stuff here..
}
That way in other view controllers:
SwitchMusic.isOn = MusicHelper.sharedHelper.isPlaying
If you need other view controllers to update in response to this, you can add a delegate event (aka observer) if necessary.
Try something like that: Use the UISwitch as an #IBOutlet.
#IBOutlet weak var checkState: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
self.checkState.addTarget(self, action: #selector(action(sender:)), for: .valueChanged)
}
// Save state
func action(sender: UISwitch) {
let userDefaults = UserDefaults.standard
userDefaults.set(sender.isOn, forKey:"identifier")
}
// Retrieve state
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let userDefaults = UserDefaults.standard.bool(forKey: "identifier")
self.checkState.setOn(userDefaults, animated: false)
}
You can give the switch a default value when it's created in viewcontroller1.
Assign the (default.value(forKey:"switchOn") as! Bool ) ?? false to that switch.
I am making a feature in my app that allows you to mute the music, i put a switch there so you can change it between on and off. However, the switch works fine in the app, when i close the vie controller and i pull it back up it will still show it how it was left, but when you close the app and open it back up the switch looks as if it off but it still plays the music.This is my code and i will attach a picture.
import Foundation
import UIKit
import SpriteKit
import AVFoundation
var bombSoundEffect: AVAudioPlayer!
var Ghost = SKSpriteNode()
class SecondViewController: UIViewController {
var sw = false
#IBOutlet var mySwitch: UISwitch!
#IBAction func switchpressed(_ sender: AnyObject) {
let defaults = UserDefaults.standard
if mySwitch.isOn{
defaults.set(true, forKey: "SwitchState")
if bombSoundEffect != nil {
bombSoundEffect.stop()
bombSoundEffect = nil
}
}
else{
defaults.set(false, forKey: "SwitchState")
let path = Bundle.main.path(forResource: "newmusic.wav", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOf: url)
bombSoundEffect = sound
sound.numberOfLoops = -1
sound.play()
} catch {
// couldn't load file :(
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
let defaults = UserDefaults.standard
if (defaults.object(forKey: "SwitchState") != nil) {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
and here is the picture
If you didn't understand what i said above please ask questions.
As i understand from your code, you are not checking the User Defaults properly. In your viewDidLoad you have to check the bool value of your user defaults is set to true or not.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
//check bool Value is set to true or not
if defaults.bool(forKey: "SwitchState") == true {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
} else {
print("false")
}
}
Please check this code. Hopefully it will helps you.
In viewDidLoad you need to check for when you need to pause the music:
// Keep this part the same
let defaults = UserDefaults.standard
if (defaults.object(forKey: "SwitchState") != nil) {
mySwitch.isOn = defaults.bool(forKey: "SwitchState")
}
// Check to see if the switch is off, if so, stop the music
if !mySwitch.isOn {
bombSoundEffect.stop()
bombSoundEffect = nil
}
You can also take out the duplicate call to super.viewDidLoad.
Note: It really looks like there is more code in your view controller, because there isn't anything currently there that would be playing the music, so this solution may not work given that you haven't provided enough information.