How to save on/off value of a switch with User defaults? - ios

I have created a custom table view cell. In my app, there are two files. I have a separate file for the custom table view cell. Then I have a tableviewcontroller file for the table as a whole.
On my custom table view cell, there is a UISwitch. The initial value of this UISwitch is off. In the file for the custom table view cell, I have created a function for the switch. I have also created an outlet for the switch.
#IBOutlet var theSwitch: UISwitch!
#IBAction func mySwitch(_ sender: AnyObject) {
}
In my table view controller, I have already created the view did appear function. I am assuming what I need to do is save the value of the switch in
#IBAction func mySwitch(_ sender: AnyObject) {
}
Then in the table view controller, somehow call it in this view did appear function.
override func viewDidAppear(_ animated: Bool) {
}
What I am having trouble with is saving the value of the switch if it is changed. For example, if a user opens the app and turns the switch on, I want the app to save it, so that when the app is reopened, the value of the switch is still on. Hopefully, this can be done by using User Defaults. If someone can show me the code to do this, that would be great.

In your IBAction method you an save the value like this way
#IBAction func mySwitch(_ sender: UISwitch) {
UserDefaults.standard.set(sender.isOn, forKey: "Switch")
}
In your table view delegate method write this way to get the value from user default and set the status of you switch as per your previous value
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let yourCell = tableView.dequeueReusableCellWithIdentifier("ProfileCell", forIndexPath: indexPath) as! MenuProfileTableViewCell
yourCell.yourSwitch.setOn(UserDefaults.standard.bool(forKey: "Switch"), animated: true)
}

You can use setBoolForkey in swift 3 like this
UserDefaults.standard.set(true, forKey: "onoroff")
where you want to save and you can check the value in viewdidload() to change the status of switch according to the value of "onoroff"
theSwitch.setOn(UserDefaults.standard.bool(forKey: "onoroff"), animated: false)

As PiyushRathi said, you can do it with NSNumber, but you can do it via setting to Bool as well:
Swift 2.3
// saving switch value
NSUserDefaults.standardUserDefaults().setBool(theSwitch.on, forKey: "SwitchValue")
// getting back the value
theSwitch.setOn(NSUserDefaults.standardUserDefaults().boolForKey("SwitchValue"), animated: true)
For Swift 3 (As EmilioPelaez suggested):
// saving switch value
UserDefaults.standard.set(theSwitch.isOn, forKey: "SwitchValue")
// getting back the value
theSwitch.setOn(UserDefaults.standard.bool(forKey: "SwitchValue"), animated: true)
Also, if you have many UI components to save other than switch, you should also have a look at this UIViewController's "State Preserving Guide".

You can check value like this:
Swift 2
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("SwitchValue") != nil{
let object = userDefaults.valueForKey("SwitchValue") as? NSNumber
yourSwitch.on = (object?.boolValue)!
}
and for saving value to userDefaults:
userDefault.setValue(NSNumber(bool: yourSwitch.value), forKey:"SwitchValue")
Swift 3
let uDefault = UserDefaults.standard
if uDefault.value(forKey: "SwitchValue") != nil{
let object = uDefault .value(forKey: "SwitchValue") as? NSNumber
yourSwitch.isOn = (object?.boolValue)!
}
and set value
uDefault.set(NSNumber(bool: yourSwitch.value), forKey: "SwitchValue")
hope this helps

Related

How to reload data in UIViewController inside a UINavigationController

I have 2 child ViewContollers in a NavigationController. There is a UISwitch in the 2nd one that changes how I query a backend API in the 1st one.
This is my code in the 2nd one:
#IBAction func ordersTapped(_ sender: UISwitch) {
if sender.isOn {
UserDefaults.standard.set(true, forKey: "showPastOrders")
} else {
UserDefaults.standard.set(false, forKey: "showPastOrders")
}
}
This is my code in the 1st one:
override func viewDidLoad() {
super.viewDidLoad()
let orderList = UserDefaults.standard.bool(forKey: "showPastOrders")
loadOrderData(past: orderList)
}
The problem is, whenever I swap back and forth between the ViewControllers, the Userdefaults boolean value I set, is not read. I tried overriding the viewDidAppear to obtain the value but that did not help either.
So how would I obtain the boolean value each time when I jump back and forth between the VCs?
maybe you can fix it by setting up a didSet call on the Bool.
var showPastOrders: Bool {
didSet {
orderList = UserDefaults.standard.bool(forKey: "showPastOrders")
// view.layoutIfNeeded()
}
}
Now if setting the showPastOrders is not set correctly I would recommend delegation or passing the data with the segue, depending on how you set up your project.
Hope it helps.

When trying to segue to a view controller from a table view i get this error: Unexpectedly found nil while unwrapping

I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.

Save label.text in swift4

Im currently developing a iOS app in which im going to keep track of our warehouse stock. It's a pretty simple app an just contains a lable and an stepper. The app is pretty much finished, but i don't get how to save the changed value of the label. I want to save it automatically so that when someone presses the "+" on the stepper, the value should save without pressing a extra save button
Current code:
//montageplatte
#IBOutlet weak var lbl_montageplatte: UILabel!
#IBAction func stepper_montageplatte(_ sender: UIStepper)
{
lbl_montageplatte.text = Int(sender.value).description
}
you can save it in UserDefaults.
#IBAction func stepper_montageplatte(_ sender: UIStepper) {
lbl_montageplatte.text = Int(sender.value).description
UserDefaults.standard.set(String(sender.value), forKey: "lblMontageplatte")
}
To get back value you can do as follow...
if let lblValue = UserDefaults.standard.object(forKey: "lblMontageplatte") as? String {
print(lblValue)
lbl_montageplatte.text = lblValue
}
Simple solution:
In the custom cell create an outlet for the stepper and a NSKeyValueObservation property
#IBOutlet weak var stepper : UIStepper!
var stepperObservation : NSKeyValueObservation?
In cellForRow in the controller add the key value observer
cell.stepperObservation = cell.stepper.observe(\.value, options: [.new]) { (stepper, change) in
print(change.newValue!)
}
Rather than printing the new value update the model (the item for the particular index path) and save the datasource array if necessary.

Table view continues to add rows while observing .childAdded even when child was not added in Firebase

I have a UITableView that gets populated by the following firebase database:
"games" : {
"user1" : {
"game1" : {
"currentGame" : "yes",
"gameType" : "Solo",
"opponent" : "Computer"
}
}
}
I load all the games in viewDidLoad, a user can create a new game in another UIViewController, once a user does that and navigates back to the UITableView I want to update the table with the new row. I am trying to do that with the following code:
var firstTimeLoad : Bool = true
override func viewDidLoad() {
super.viewDidLoad()
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef =
Database.database().reference().child("games").child(currentUserID)
gamesRef.observe(.childAdded, with: { (snapshot) in
let game = snapshot
self.games.append(game)
self.tableView.reloadData()
})
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if firstTimeLoad {
firstTimeLoad = false
} else {
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef = Database.database().reference().child("games").child(currentUserID)
gamesRef.observe(.childAdded, with: { (snapshot) in
self.games.append(snapshot)
self.tableView.reloadData()
})
}
}
}
Lets say there is one current game in the data base, when viewDidLoad is run the table displays correctly with one row. However anytime I navigate to another view and navigate back, viewDidAppear is run and for some reason a duplicate game seems to be appended to the games even though no child is added.
The cells are being populated by the games array:
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("GameTableViewCell", owner:
self, options: nil)?.first as! GameTableViewCell
let game = games[indexPath.row]
if let gameDict = game.value as? NSDictionary {
cell.OpponentName.text = gameDict["opponent"] as? String
}
return cell
}
UPDATE:
Thanks to everyone for their answers! It seems like I misunderstood how firebase .childAdded was functioning and I appreciate all your answers trying to help me I think the easiest thing for my app would be to just pull all the data every time the view appears.
From what I can see, the problem here is that every time you push the view controller and go back to the previous one, it creates a new observer and you end up having many observers running at the same time, which is why your data appears to be duplicated.
What you need to do is inside your viewDidDisappear method, add a removeAllObservers to your gameRef like so :
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
guard let currentUserId = Auth.auth().currentUser?.uid else {
return
}
let gamesRef = Database.database().reference().child("games").child(currentUserId)
gamesRef.removeAllObservers()
}
I cannot see all your code here so I am not sure what is happening, but before adding your child added observer, you need to remove all the elements from your array like so :
games.removeAll()
Actually, as per best practices, you should not call your method inside your ViewDidLoad, but instead you should add your observer inside the viewWillAppear method.
I cannot test your code right now but hopefully it should work like that!
Let me know if it doesn't :)
UPDATE:
If you want to initially load all the data, and then pull only the new fresh data that is coming, you could use a combination of the observeSingleEvent(of: .value) and observe(.childAdded) observers like so :
var didFirstLoad = false
gamesRef.child(currentUserId).observe(.childAdded, with: { (snapshot) in
if didFirstLoad {
// add your object to the games array here
}
}
gamesRef.child(currentUserId).observeSingleEvent(of: .value, with: { (snapshot) in
// add the initial data to your games array here
didFirstLoad = true
}
By doing so, the first time it loads the data, .childAdded will not be called because at that time didFirstLoad will be set to false. It will be called only after .observeSingleEvent got called, which is, by its nature, called only once.
Try following code and no need to check for bool , Avoid using bool here its all async methods , it created me an issue in between of my chat app when its database grows
//Remove ref in didLoad
//Remove datasource and delegate from your storyboard and assign it in code so tableView donates for data till your array don't contain any data
//create a global ref
let gamesRef = Database.database().reference()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.games.removeAllObjects()
if let currentUserID = Auth.auth().currentUser?.uid {
gamesRef.child("games").child(currentUserID)observe(.childAdded, with: { (snapshot) in
self.games.append(snapshot)
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
})
gamesRef.removeAllObserver() //will remove ref in disappear itself
//or you can use this linen DidDisappear as per requirement
}
else{
//Control if data not found
}
}
//TableView Delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.games.count == 0{
let emptyLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height))
emptyLabel.text = "No Data found yet"
emptyLabel.textAlignment = .center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = .none
return 0
}
else{
return self.games.count
}
}
observe(.childAdded) is called at first once for each existing child, then one time for each child added.
Since i also encounter a similar issue, assuming you don't want to display duplicate objects, in my opinion the best approach, which is still not listed in the answers up above, is to add an unique id to every object in the database, then, for each object retrieved by the observe(.childAdded) method, check if the array which contains all objects already contains one with that same id. If it already exists in the array, no need to append it and reload the TableView. Of course observe(.childAdded) must also be moved from viewDidLoad() to viewWillAppear(), where it belongs, and the observer must be removed in viewDidDisappear. To check if the array already includes that particular object retrieved, after casting snapshot you can use method yourArray.contains(where: {($0.id == retrievedObject.id)}).

Updating label text based on cell textLabel

I am trying to capture the textLabel.text value of a cell in my tableView, and using nsuserdefaults, transferring it to another view. In this final view, a label should be updated with the captured value.
Here is my code from TableViewController.swift that captures the value:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//actions that will proceed immediately a cell row is clicked
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? PFTableViewCell
//here I do the capture
let captureCellVals = NSUserDefaults.standardUserDefaults()
captureCellVals.setValue(cell?.textLabel?.text, forKey: "restoname")
//code that transitions to the final view
let view2 = self.storyboard?.instantiateViewControllerWithIdentifier("finalView") as IndividualViewController
self.navigationController?.pushViewController(view2, animated: true)
}
And this is the code in the final view that is supposed to set the label's text value:
override func viewDidLoad() {
super.viewDidLoad()
let values = NSUserDefaults.standardUserDefaults()
let resname = values.valueForKey("restoname")
Restaurant.text = resname as? String
// Do any additional setup after loading the view, typically from a nib.
}
#IBOutlet var Restaurant: UILabel!
But for some reason, when I run it in the simulator, it crashes. No error report or nothing. Just a crash. Any help would be appreciated, Thanks!
Try using the "??" nil coalescing operator to return an empty string in case you try to access it before storing any value to it:
Restaurant.text = NSUserDefaults().stringForKey("restoname") ?? ""
Note. NSUserDefaults has a specific method for loading your stored string called stringForKey()
So I figured it out.
It turns out that the problem was with the transition from the tableview to the final view. I needed to set the class for view2 as FinalView instead of IndividualViewController. Thanks nonetheless.

Resources