UserDefault not updating in main view controller - ios

I am creating a simple counter application where the user can set a maximum number of patrons allowed in their place of business and when that number is reached they're given a notification that they're at maximum capacity. I'm using UserDefaults to store this data, however whenever I update the max number in the settings and return to the main view it's still using the previous max number. If I close the app and reopen it has the correct data. How do I make sure the data is being updated in the main view controller so it receives proper data?
Code
ViewController.swift (main view controller)
import UIKit
import AVFoundation
class ViewController: UIViewController {
var counter = 0;
var max = UserDefaults.standard.integer(forKey: "max");
// Max patron alert function
func showAlert() {
let alertController = UIAlertController(title: "Limit Reached", message: "Your place of business has reached maximum capacity, please stop patrons from entering until capacity dissipates.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .default))
self.present(alertController, animated: true, completion: nil)
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
}
#IBOutlet weak var count: UILabel!
#IBAction func increment(_ sender: UIButton) {
if counter < max {
counter += 1;
count?.text = String(counter);
}
if counter == max {
showAlert();
}
}
#IBAction func decrement(_ sender: UIButton) {
if (counter == 0) {
counter += 0;
}
else {
counter -= 1;
}
count?.text = String(counter);
}
#IBAction func reset(_ sender: UIButton) {
counter = 0;
count?.text = String(counter);
}
override func viewDidLoad() {
super.viewDidLoad()
// UserDefaults.standard.synchronize()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
SettingsViewController.swift
import UIKit
class SettingsViewController: UIViewController {
var mainViewController:ViewController?;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBOutlet weak var maxOccupancy: UITextField!
#IBAction func save(_ sender: UIButton) {
let max = Int(maxOccupancy.text!);
UserDefaults.standard.set(max, forKey: "max")
UserDefaults.standard.synchronize()
print(UserDefaults.standard.integer(forKey: "max"))
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

This line:
var max = UserDefaults.standard.integer(forKey: "max")
is executed only once, when ViewController is created. (BTW: no need for semicolons in Swift). Whatever happens to UserDefaults afterwards won't be reflected in its value.
Replace the line with
var max: Int {
UserDefaults.standard.integer(forKey: "max")
}
so that every access to it actually goes out to fetch the value from UserDefaults.
These constructs are called "Computed Properties", explained here.

This happens because you read the counter number only once when ViewController is allocated. You might try to re-read it when ViewController will appear on the screen:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
max = UserDefaults.standard.integer(forKey: "max")
}
Also, no need to use ; in every line's ending, Swift doesn't require it.

Related

Passing Data into an Array- Swift

I am looking to pass data through a text box, and into an array (Second View Controller), so it can be added to a pool of data and randomly chosen.
For instance, you would input your name (and/or another name) into the text field in the First VC and and it would go into an array that would tell them which color they are shown in the Second VC.
Essentially, I'm trying to pass the data to a variable (which will hold all the names inputed), and append the variable to the array, then click a button to generate and randomly pull from the array via arc4random. My problem is its not appending. Any help would be greatly appreciated.
Here is my code:
VC 1:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func StartTapped(_ sender: Any) {
performSegue(withIdentifier: "SecondViewControllerSegue", sender: self)
}
#IBOutlet weak var AddPlayerTextField: UITextField!
#IBAction func AddTexttoArrayBtn(_ sender: Any) {
if AddPlayerTextField.text == nil
{
performSegue(withIdentifier: "SecondViewControllerSegue", sender: self)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let SecondViewController = segue.destination as! ViewController2
SecondViewController.myString = AddPlayerTextField.text!
VC 2:
import UIKit
class ViewController2: UIViewController {
var myString = String ()
var TeamBuildingQuestions = [ "is Red", "is Blue", "is Green", "is Purple","is Teal","is Orange", "is Red", "is Grey", "is Pink"]
var MasterTeamBuildingQuestionsArray = [ "Hello", "beep bop bbeep", "Boop", "Bap","Bospop","bob the builder"]
#IBOutlet weak var DisplayArray: UILabel!
#IBAction func NextQuestionBtn(_ sender: Any) {
let RandomArrayNumber = Int (arc4random_uniform(UInt32(TeamBuildingQuestions.count)))
let QuestionDisplayed = TeamBuildingQuestions[RandomArrayNumber]
DisplayArray?.text = QuestionDisplayed
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
TeamBuildingQuestions.append(myString)
}
In VC2, Instead of didReceiveMemoryWarning, implement TeamBuildingQuestions.append(myString) in viewDidLoad.
Here is a bug:
if AddPlayerTextField.text == nil{
}
should be
if AddPlayerTextField.text != nil && AddPlayerTextField.text.count > 0{
}

How can I go about controlling 1 view controller from another?

So I have the following layout for my iOS app.
What I'm intending to do is put a table view in the purpleVC to control the Green viewcontroller...the top peachVC will have text in it which will need to change. I'm just not sure how to control one view controller from another. This includes having the purple slide in and out when a button on the GreenVC is clicked. I know there are classes out there to do this however I want to learn as well.
TESTING DELEGATES:
MAINVIEW CONTROLER
import UIKit
protocol Purpleprotocol {
func buttonpressed()
}
protocol Greenprotocol {
}
extension UIViewController {
func alert(message: String, title: String = "") {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
}
class MainViewController: UIViewController,Purpleprotocol,Greenprotocol {
weak var infoNav : UINavigationController?
weak var greenVC: GreenVC?
weak var purpleVC: PurpleVC?
weak var peachVC: PeachVC?
func buttonpressed() {
alert(message: "This is message")
print("buttonpressed")
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
greenVC?.greenlabel.text = String(hour) + ":" + String(minutes)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "contentSegue" {
let infoNav = segue.destination as! UINavigationController
}
}
}
PURPLEVIEW CONTROLER
class PurpleVC: UIViewController {
var delegate: Purpleprotocol?
#IBAction func butclick(_ sender: UIButton) {
alert(message: "infunction")
delegate?.buttonpressed()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Thanks
R
It does depend on the case but to see a few examples:
A) You can connect it through the delegates. Your main view controller has 3 child view controllers to which it should report changes. It should also assign itself as a delegate to all 3 child controllers where it will get all notifications for events. This would look like
func purpleViewController(sender: PVC, selectedItem: Item) {
self.greenViewController.showItem(item: selectedItem)
self.peachVC.showItem(item: selectedItem)
}
func purpleViewController(sender: PVC, shouldSetMenuClosed closed: Bool) {
self.menuConstraint.constant = closed ? targetWidth : 0.0
}
B) You may have a data model which controls the whole screen and has a delegate for each of the children. The model will report any changes to its delegates so they may react accordingly. Main view controller would create an instance of this model when it loads and pass it to all of the children view controllers. The children would then directly manipulate the model:
In green controller:
func onTap() {
mode.menuShown = !mode.menuShown
}
In model:
var menuShown: Bool = true {
didSet {
self.menuDelegate.model(self, changedMenuShownStateTo: menuShown)
}
}
In main view controller:
func model(_ sender: Model, changedMenuShownStateTo shown:Bool) {
self.menuConstraint.constant = shown ? 0.0 : targetWidth
}
C) You can use notifications where any of the controllers may post to notification center a custom notification and other controllers may observe the notifications and act accordingly.
There are many other ways in doing so but these probably most popular. See if any of them fits you...
Delegation.
Your MainViewController will become a delegate to each of the embedded VC's that want to pass back information. From your description you'll need two delegate relationships:
protocol PurpleProtocol {
func selected(row: Int, text: String)
}
protocol GreenProtocol {
func slideButtonPressed()
}
Have MainViewController implement these protocols. Give identifiers to the embed segues. You can find them in the Document Outline view. In prepareForSegue, retain pointers to the embedded VC's and pass your self as the delegate:
class MainViewController: UIViewController, PurpleProtocol, GreenProtocol {
weak var greenVC: GreenViewController?
weak var purpleVC: PurpleViewController?
weak var peachVC: PeachViewController?
func selectedRow(row: Int, text: String) {
// do something useful
}
func slideButtonPressed() {
// slide purple view in or out depending on current state
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedPurple" {
let dvc = segue.destination as! PurpleViewController
purpleVC = dvc
dvc.delegate = self
}
else if segue.identifier = "EmbedGreen" {
let nav = segue.destination as! UINavigationController
let dvc = nav.topViewController as! GreenViewController
greenVC = dvc
dvc.delegate = self
} else if segue.identifier = "EmbedPeach" {
peachVC = segue.destination as! PeachViewController
}
}
}
In your embedded VC's, add a delegate pointer and call the delegate with the protocol method when it is time:
class GreenViewController: UIViewController {
weak var delegate: GreenProtocol?
#IBAction slideButtonPressed(sender: UIButton) {
delegate?.slideButtonPressed()
}
}
class PurpleViewController: UITableViewController {
weak var delegate: PurpleProtocol?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.selected(row: indexPath.row, text: modelArray[indexPath.row])
}
}

Trying to get a quiz function to work in swift

I'm trying to get a quiz function to work, and my problem is that I'm trying to print the score on a second view controller. When i run the simulator it takes the user to the second view controller yet the score does not print.
import UIKit
var CurrentQuestion = "" // global variable to be accessed
class ViewController: UIViewController {
// create an array to store queations
let questions = ["favourite pet?", "favourite colour", "where was i born"]
// multiple arrays that contain strings, right anwser is first
let anwsers = [["dog", "cat", "bird"], ["blue", "black", "green"], ["tokyo", "tennese", "england"]]
// variables
// keeps track of what question were at
var currentQuestion = 0
var rightAnwserPlacement:UInt32 = 0 // where right anwser is located
var points = 0; // counts number of points - score
//lbl
#IBOutlet weak var lbl: UILabel!
//button
#IBAction func btn(_ sender: UIButton) {
if (sender.tag == Int(rightAnwserPlacement))
{
print ("right")
points += 1
}
else
{
print ("wrong")
}
if (currentQuestion != questions.count)
{
newQuestion()
}
else
{
performSegue(withIdentifier: "showScore", sender: self)
}
}
override func viewDidAppear(_ animated: Bool) {
newQuestion()
}
// function that displays a new question sets up interface for the new questions
func newQuestion()
{
lbl.text = questions[currentQuestion]
rightAnwserPlacement = arc4random_uniform(3)+1 // random button
// create a button
var button:UIButton = UIButton()
var x = 1
for i in 1...3
{
// create a button
button = view.viewWithTag(i) as! UIButton
if (i == Int(rightAnwserPlacement)) // checks to see if it matches the right anwser
{
button.setTitle(anwsers[currentQuestion][0], for: .normal)
}
else {
button.setTitle(anwsers[currentQuestion][x], for: .normal)
x = 2
}
}
currentQuestion += 1
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The second view controller is
import UIKit
class second: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
label.text = CurrentQuestion
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
You have set CurrentQuestion as a global string variable and later on try to increment its value by 1? What is that supposed to mean? Additionally, you have a local variable in the class with the same name as currentQuestion. Granted the two are different variables, but you should seriously consider renaming your variables.
Also, you are changing value of this local variable and no-where changing the value of the global variable. Most likely this mix-up is because of similar naming of variables.
And just a suggestion, avoid using global variables to pass data. Look into delegate-protocol way of passing data between VCs.

Disable Button from another view controller with a switch with swift

Ok so I am trying to make it when the user flips the cheat mode switch it will disable the answerButton. I did try to set the button to disable near the bottom however it does not appear to do anything. I added the print("") to see if it would print in the console however nothing was printed. I a unsure what is wrong here.
I have pasted my code below.
import Foundation
import UIKit
class SettingsController: UITableViewController {
#IBOutlet weak var cheatSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
cheatSwitch.isOn = UserDefaults.standard.bool(forKey: "switchState")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func saveSettings(_ sender: UISwitch) {
UserDefaults.standard.set(cheatSwitch.isOn, forKey: "switchState")
UserDefaults.standard.synchronize()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let mainView : ViewController = segue.destination as! ViewController
if cheatSwitch.isOn == true {
print("turn off button")
mainView.btnAnswer.isEnabled = false;
}
else {
print("turn on button")
mainView.btnAnswer.isEnabled = true;
}
}
}
You are saving the value UISwitch's state in UserDefaults so you can use its value in ViewController's method viewDidApper.
override func viewDidAppear(_ animated: Bool) {
self.btnAnswer.isEnabled = !UserDefaults.standard.bool(forKey: "switchState")
}
Note: There is no need to add prepareForSegue in your SettingsController because it will never called when you press back button of NavigationController so consider to remove it.

Error: Could not cast value of type 'FirstApp.ChoosePageViewController' to 'FirstApp.CreateAccountPageViewController'

I am using Xcode 6.3.2 creating a single view app.
I have it set up so that the first view controller is a home page (Figure 1.), and when it appears, it automatically goes to a login/register/guest page. (Figure 2.)
From there, if someone chooses register, it brings them to a quick screen (Figure 3.) which is sort of just a background view to check if the registration works. From that screen, it automatically brings up the registration page. (Figure 4.)
Then, if registration is completed or successful, the registration page is dismissed back to that checking screen which is then dismissed to go back to the login/register/guest page.
Can't add many links so I uploaded a zip folder of my project plus the 4 images.
My Project:
http://www.mediafire.com/download/dnv1vqlx3j741zv/FirstApp.zip
Everything works up to the point where registration is cancelled. If registration is cancelled it will go back to the checking screen, then is just stuck there. I looked around and just couldn't find anything that worked for me to solve the issue and I unfortunately could not figure out the solution from the error message. Thank you to anyone who helps!
My Error Message:
Could not cast value of type 'FirstApp.ChoosePageViewController' (0x101edd680) to 'FirstApp.CreateAccountPageViewController' (0x101edd380).
(lldb)
The Login/Register/Guest Page View Controller:
import UIKit
class ChoosePageViewController: UIViewController {
//Sets Button Outlets to Edit Displays
#IBOutlet weak var viewLoginButton: UIButton!
#IBOutlet weak var viewRegisterButton: UIButton!
#IBOutlet weak var viewGuestButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
viewLoginButton.layer.cornerRadius = 5.0
viewRegisterButton.layer.cornerRadius = 5.0
viewGuestButton.layer.cornerRadius = 5.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The Registration Check Page View Controller:
import UIKit
class RegistrationCheckPageViewController: UIViewController, DetailsDelegate {
#IBOutlet weak var viewRegistrationStatusLabel: UILabel!
#IBOutlet weak var viewRegistrationForceQuitLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Allow CreateAccountPage to Edit RegistrationCheckPage Labels
func labelDelegateMethodWithString(RegistrationStatus: String, RegistrationForceQuit: String) {
viewRegistrationStatusLabel.text! = RegistrationStatus
viewRegistrationForceQuitLabel.text! = RegistrationForceQuit
}
//Prepare RegistrationCheckPage Controller for CreateAccountPage Controller Takeover
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let controller = segue.destinationViewController as! CreateAccountPageViewController
controller.delegate = self
}
//Immediately Present Create Account Page, Dismiss When Account is Created
//Display Error and Return to Create Account Page if Account Creation Fails Unexpectedly
override func viewDidAppear(animated: Bool) {
if(viewRegistrationStatusLabel.text == "Complete!") {
self.performSegueWithIdentifier("toChoosePageFromRegistrationCheckPage", sender: self)
} else if(viewRegistrationForceQuitLabel.text == "Active") {
displayErrorAlert("Account not created.")
self.performSegueWithIdentifier("toChoosePageFromRegistrationCheckPage", sender: self)
} else if(viewRegistrationStatusLabel.text == "Checking..." && viewRegistrationForceQuitLabel.text == "Inactive"){
} else {
displayErrorAlert("An unknown error occurred.")
}
self.performSegueWithIdentifier("toCreateAccountPage", sender: self)
}
func displayErrorAlert(displayMessage: String) {
var myAlert = UIAlertController(title: "Error", message: displayMessage, preferredStyle: UIAlertControllerStyle.Alert)
let dismissAction = UIAlertAction(title: "Retry", style: UIAlertActionStyle.Default, handler: nil)
myAlert.addAction(dismissAction)
self.presentViewController(myAlert, animated: true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The Create Account Page View Controller:
import UIKit
//Allows RegistrationCheckPage and CreateAccountPage to Communicate
protocol DetailsDelegate {
func labelDelegateMethodWithString(RegistrationStatus: String, RegistrationForceQuit: String)
}
class CreateAccountPageViewController: UIViewController {
//Sets Button Outlets to Edit Displays
#IBOutlet weak var viewCreateButton: UIButton!
#IBOutlet weak var viewCancelButton: UIButton!
var registrationStatus = "Checking..."
var registrationForceQuit = "Inactive"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
viewCreateButton.layer.cornerRadius = 5.0
viewCancelButton.layer.cornerRadius = 5.0
}
//CreateAccountPage Allows or Disallows RegistrationCheckPage to Continue
var delegate: DetailsDelegate!
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
delegate.labelDelegateMethodWithString(registrationStatus, RegistrationForceQuit: registrationForceQuit)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var userUsernameTextField: UITextField!
#IBOutlet weak var userEmailTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
#IBOutlet weak var userRepeatPasswordTextField: UITextField!
#IBAction func didPressCreateAccountButton(sender: AnyObject) {
let userUsername = userUsernameTextField.text;
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
let userPasswordRepeat = userRepeatPasswordTextField.text;
func displayAlert(displayMessage:String) {
var myAlert = UIAlertController(title: "Wait!", message:displayMessage, preferredStyle: UIAlertControllerStyle.Alert)
let dismissAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil)
myAlert.addAction(dismissAction)
self.presentViewController(myAlert, animated: true, completion: nil)
}
//Check for Empty Fields
if(userUsername.isEmpty || userEmail.isEmpty || userPassword.isEmpty || userPasswordRepeat.isEmpty) {
displayAlert("All fields are required!")
return;
}
//Make Sure Username Is Not In Use
/*if(username is in use) {
displayAlert("Username is already in use.")
return;
}*/
//Make Sure Passwords Match
if(userPassword != userPasswordRepeat) {
displayAlert("The passwords did not match!")
return;
}
//Store Data
//Successful Registration
registrationStatus = "Complete!"
registrationForceQuit = "Inactive"
delegate.labelDelegateMethodWithString(registrationStatus, RegistrationForceQuit: registrationForceQuit)
self.dismissViewControllerAnimated(true, completion: nil)
}
//Cancel the Registration Without Finishing
#IBAction func didPressCancelRegistrationButton(sender: AnyObject) {
registrationStatus = "Failed"
registrationForceQuit = "Active"
delegate.labelDelegateMethodWithString(registrationStatus, RegistrationForceQuit: registrationForceQuit)
self.dismissViewControllerAnimated(true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
In this line:
let controller = segue.destinationViewController as! CreateAccountPageViewController
Your segue.destinationViewController is not what you expected it to be, it is actually a ChoosePageViewController.
You have two segues, "toChoosePageFromRegistrationCheckPage" and "toCreateAccountPage".
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let controller = segue.destinationViewController as! CreateAccountPageViewController
controller.delegate = self
}
Right here, you have to check if segue.identifier is equal to "toChoosePageFromRegistrationCheckPage" or "toCreateAccountPage" and based on the identifier, cast the destination to the appropriate view controller.

Resources