I have a label (in my secondVC) which is displaying a segued Double from the firstVC. In the secondVCs viewDidLoad I am printing the passedDouble and it is printing the correct amount, so I know my Double is being segued correctly. My UILabel is in the secondVC and only shows an amount if the passedDouble = 0.0.
SecondViewController:
#IBOutlet weak var totalLabel: UILabel!
var passedDouble = 0.0
override func viewDidLoad() {
super.viewDidLoad()
print("testing for a value \(passedDouble)")
totalLabel.text = String("\(passedDouble)")
}
If the passed value is 12.2 for example, it prints this
testing for a value 12.2
But the label completely disappears from view.
If the passed value is 0.0 however, it prints
testing for a value 0.0
and the label shows 0.0
In the storyboard I have left the labels standard text as Label. So, I know the label is connected properly as it's text changes IF the value is nil.
EDIT: Code for the firstVC where I am assigning the value
var totalPrice: Double = Double()
#IBAction func basketAction(_ sender: UIButton) {
for cost in priceDictionaryToBeSent {
totalPrice += Double(cost)!
}
performSegue(withIdentifier: "basket", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "basket" {
if let basketVC = segue.destination as? BasketViewController {
basketVC.passedDouble = totalPrice
//This is sending the correct price
}
}
}
My best guess is that in prepare(for:sender:) the basketVC's view is already loaded, so its viewDidLoad got called before you are setting basketVC.passedDouble = totalPrice.
I would rather use setter to update the label everytime the passedDouble gets updated. Change the BasketViewController code to this:
#IBOutlet weak var totalLabel: UILabel!
var passedDouble = 0.0 {
didSet {
self.totalLabel?.text = "\(passedDouble)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// no need to set totalLabel.text here
}
Thank you to Milan Nosáľ for saying the view could've already loaded, indeed it had, so I moved the code from viewDidLoad in my BasketViewController to viewDidAppear as seen below:
override func viewDidAppear(_ animated: true) {
super.viewDidAppear(true)
print("testing for total price £\(passedDouble)")
totalLabel.text = "Total price £\(passedDouble)"
}
Related
I have a TextLabel in the ViewController VC, which will receive the input of the user whenever the user has put text on there. Then, pressing a button, that text that was on the TextLabel, will pass onto a Label at the SecondaryView VC. But the thing is that I have tried multiple ways to set the text from the TextLabel to the Label on the SecondaryView VC.
This is the first way I tried:
This is my ViewController.swift file.
class ViewController: UIViewController {
var text: String = ""
#IBOutlet weak var mainViewTextLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondaryView {
let vc = segue.destination as? SecondaryView
vc?.text = "\(mainViewTextLabel!)"
}
}
}
#IBAction func onButtonTap(_ sender: Any) {
}
}
And this is my SecondaryView.swift file:
import UIKit
class SecondaryView: UIViewController {
var text:String = ""
#IBOutlet weak var secondaryViewLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
secondaryViewLabel?.text = text
}
}
When I run the app and type any text in the TextField and press the Button to go to the SecondaryView VC, there is no text on there.
If someone knowns another way to pass text from a View to another, or a way that the text can appear, I would appreciate it.
You have a couple of issues, I think.
Firstly, you are calling prepare(for:...) within viewDidLoad. This function isn't something you call yourself. It's something that you provide an implementation for and the system calls it just before the segue.
The second is that you are passing a reference to a UITextField rather than the text of that text field.
Maybe something like this would be better:
class ViewController: UIViewController {
var text: String = ""
#IBOutlet weak var mainViewTextLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do anything else you need to do when loading
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondaryView {
let vc = segue.destination as? SecondaryView
vc?.text = mainViewTextLabel.text ?? ""
}
}
}
It looks like you are not passing through the text of the UITextField. Instead you are passing through a reference to the UITextField as a string.
What you want to do is access the .text property:
vc?.text = mainViewTextLabel.text!
The .text property returns a String optional or String? and it is generally bad practice to force unwrap it, since it could crash your application. Instead, you can use a guard/let statement to make sure it is not null. So:
vc?.text = "\(mainViewTextLabel!)"
can be replaced by:
guard let textFromTextField = mainViewTextLabel.text else {
return
}
vc?.text = textFromTextField
Iv created a quiz app, that tracks the user's score till the user gets to the end. There is an if statement - when it reaches the end of the quiz, a results View controller pops up. My Question is I want to get the same score value presented at the end on the resultsViewContoller. I have connected it via segue and the identifier has been placed. The app runs but when i get to the end the segue opens up the page but the score doesnt change?
How can i solve this problem?
The user finishes the app. I call the segue method and the screen comes up.
Results View Controller
import UIKit
class ResultsViewController: UIViewController {
var fruitness = Fruitness()
#IBOutlet weak var finalScoreLabel: UILabel!
var finalScore: String!
override func prepare(for: UIStoryboardSegue, sender: Any?) {
func viewWillAppear(_ animated: Bool) {
finalScoreLabel.text = finalScore
if finalScore != nil {
finalScoreLabel.text = "FINALSCORE:\(String(describing: Int(finalScore!)))"
}
}
}
}
Game View Controller
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet private weak var fruitLabel: UILabel!
#IBOutlet private weak var scoreLabel: UILabel!
#IBOutlet weak var replayGame: UIButton!
#IBOutlet weak var optionButton0: UIButton!
#IBOutlet weak var optionButton1: UIButton!
#IBOutlet weak var optionButton2: UIButton!
#IBOutlet weak var optionButton3: UIButton!
#IBOutlet weak var optionButton4: UIButton!
#IBOutlet weak var optionButton5: UIButton!
#IBOutlet weak var optionButton6: UIButton!
#IBOutlet weak var optionButton7: UIButton!
#IBOutlet weak var optionButton8: UIButton!
#IBOutlet private var fruitButtons: [UIButton]!
var score = 0
var fruitness = Fruitness()
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
#IBAction func replayGame(_ sender: UIButton) {
fruitness.restartGame() //Calling restart
updateUI()
}
#IBAction func touchButton(_ sender: UIButton) {
let userAnswer = sender.currentTitle!
//The userGotItRight is = to the checkAnswer function which goes through the fruitOptions array to make sure the answer is corrct. T/F
let userGotItRight = checkAnswer(userAnswer: userAnswer)
if userGotItRight {
//Depending if the user got the answer correct the button turns green/red
sender.backgroundColor = UIColor.green
} else {
sender.backgroundColor = UIColor.red
}
nextFruit() //Calling the next Fruit untill all the fruit items have been displayed.
//This timer is responsible for the UIColors green, red and clear. Without this timer the color drags onto the next fruit.
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateUI), userInfo:nil, repeats: false)
}
// This check answer method needs an input to work. The input is the answer the user-choose (String).
func checkAnswer(userAnswer: String) -> Bool {
// Checks if the user got the answer correct. T/F
if userAnswer == fruitness.fruitOptions[fruitness.fruitNumber].fruit {
fruitness.score += 1 //We increase the value of score when we get the answer right.
// fruitness.finalScorez = fruitness.score
return true
} else {
return false
}
}
//Checks to make sure the eveytime it hits 0 it will shuffle.
func nextFruit() {
if fruitness.fruitNumber == 0 {
fruitness.fruitOptions.shuffle()
}
// print(fruitness.fruitOptions[fruitness.fruitNumber]) //Only gets printed in the consol
if fruitness.fruitNumber + 1 < fruitness.fruitOptions.count {
fruitness.fruitNumber += 1
} else {
self.performSegue(withIdentifier: "goToResultsVC", sender: self) //To call Segue
}
}
//Connecting and controlling oF the Segue and from GameView COntroller -> Results view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToResultsVC" {
let destinationVC = segue.destination as! ResultsViewController
destinationVC.finalScore = scoreLabel.text //Printing the final score at the end.
}
}
#objc func updateUI() {
//Controlls the background. Clearing ability between T/F answers.
optionButton0.backgroundColor = UIColor.clear
optionButton1.backgroundColor = UIColor.clear
optionButton2.backgroundColor = UIColor.clear
optionButton3.backgroundColor = UIColor.clear
optionButton4.backgroundColor = UIColor.clear
optionButton5.backgroundColor = UIColor.clear
optionButton6.backgroundColor = UIColor.clear
optionButton7.backgroundColor = UIColor.clear
optionButton8.backgroundColor = UIColor.clear
//The fruit name available that the user needs to match.
fruitLabel.text = fruitness.getFruitText()
//Displaying the progress of the user till they reach the end.
progressBar.progress = fruitness.getProgress()
//Displaying the score at all times
scoreLabel.text = "SCORE: \(fruitness.score)"
}
}
You have a number of problems with your code.
First off, the prepare(for:sender:) method gets called on the source view controller that triggers the segue, not the destination view controller.
Second, your ResultsViewController has a viewWillAppear(_:) method nested inside its prepare(for:sender:) method. Don't do that. The viewWillAppear(_:) method needs to be a top-level method of your view controller or it won't get called.
Also, viewWillAppear(_:) methods should call super.viewWillAppear(animated).
Your ResultsViewController's viewWillAppear method should look like this:
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
finalScoreLabel.text = finalScore
}
The prepare(for:sender:) method in your GameViewController should set finalScore in your ResultsViewController so that when its viewWillAppear is called, finalScore has a value.
Edit:
(It looks like you got that last part correct. Your GameViewController's prepare(for:sender) does appear to set finalScore. Note, however, that you are using scoreLabel to hold your finalScoreValue. You should not save state in view objects. You should have a var in GameViewController that holds your score. You could make GameViewController set your score var, and have a didSet() method on the score var that installs the updated score value into the scoreLabel.)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToResultsVC" {
let destinationVC = segue.destination as! ResultsViewController
destinationVC.finalScore = scoreLabel.text //Printing the final score at the end.
}
}
Maybe you wish to refactor your repetitive code to this one:
let optionButtonArray = [optionButton0,optionButton1 ...]
optionButtonArray.forEach { $0.backgroundColor = UIColor.clear }
I have a container view with multiple text boxes on it. I also have a button in Parent View controller(custom keypad). What I'm trying to do is select text box first & when I tap on the button I wanted some value to be populated to that last selected/focused textbox.
How can I do that? any alternative ways are welcome too. (I am having multiple container-views in the original code and try to use one keypad for all the views)
class MainViewController: UIViewController {
var weightVC : WeightViewController!
var focusedElement : UITextField
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "weight") {
weightVC = segue.destination as? WeightViewController
}
}
#IBAction func button1Clicked(_ sender: Any) {
if weightVC != nil {
weightVC.sampleTextBox1.text = "1"
//I want this sampleTextBox1 to be dynamic like weightVC.focusedInput = "1"
}
}
}
extension MainViewController:ChildToParentProtocol {
func setFocusedElement(with value: UITextField){
focusedElement = value
}
}
Container View Controller
protocol ChildToParentProtocol: class {
func setFocusedElement(with value:UITextField)
}
class WeightViewController: UIViewController {
weak var delegate: ChildToParentProtocol? = nil
#IBOutlet weak var sampleTextBox1: UITextField!
#IBOutlet weak var sampleTextBox2: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
// sampleTextBox1 Editing Did Begin event
#IBAction func editBeginSampleText1(_ sender: Any) {
print("edit begin")
delegate?.setFocusedElement(with: sampleTextBox1)
}
}
In other words, I simply want to keep a reference to last focused UITextFild when a button is tapped. Hope my requirement is clear enough. Please guide me if there is a way to achieve this.
Thanks
If I understood your question correctly you can keep track on which UITextField is tapped by using it's tag. And you can use UITextFieldDelegate to get the selected UITextField tag.
Consider the below code for WeightViewController
protocol ChildToParentProtocol: class {
//Changed value to Int for passing the tag.
func setFocusedElement(with value: Int)
}
import UIKit
class WeightViewController: UIViewController {
#IBOutlet weak var tf1: UITextField!
#IBOutlet weak var tf2: UITextField!
var selectedTFTag = 0
weak var delegate: ChildToParentProtocol? = nil
override func viewDidLoad() {
super.viewDidLoad()
//Assign delegate and tags to your TF
tf1.delegate = self
tf2.delegate = self
tf1.tag = 1
tf2.tag = 2
}
}
extension WeightViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
//Get the selected TF tag
selectedTFTag = textField.tag
//Pass tag to parent view
delegate?.setFocusedElement(with: selectedTFTag)
}
}
Now in your parent view ViewController you need to make some modification. I have added comments where I made changes to achieve your requirement.
import UIKit
//You need to confirm your ChildToParentProtocol with your UIViewController
class ViewController: UIViewController, ChildToParentProtocol {
var selectedTFTag = 0
var weightVC : WeightViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "weight" {
weightVC = segue.destination as? WeightViewController
//You need to pass delegate to containerview to make it working.
weightVC.delegate = self
}
}
#IBAction func btn1Tapped(_ sender: Any) {
//According to selected Tag perform your action
if selectedTFTag > 0 {
switch selectedTFTag {
case 1:
//set up first UITextField
weightVC.tf1.text = "First textfield was selected"
print("1")
case 2:
//set up second UITextField
weightVC.tf2.text = "Second textfield was selected"
default:
break
}
}
}
#IBAction func btn2Tapped(_ sender: Any) {
//According to selected Tag perform your action
if selectedTFTag > 0 {
switch selectedTFTag {
case 1:
//set up first UITextField
weightVC.tf1.text = "First textfield was selected"
print("1")
case 2:
//set up second UITextField
weightVC.tf2.text = "Second textfield was selected"
default:
break
}
}
}
func setFocusedElement(with value: Int) {
//Get selected TF tag with delegate
selectedTFTag = value
}
}
You can check THIS demo project for more info.
I am new to Swift and already tried to search on this topic, but I don't know how it's called, so I'm asking it now. I want to make a sort of Cookie Clicker. Every time I press the button there will be more money.
I already managed to save "the money" when I open the app, but now I want to add to that "old" money when I click the button. Does anyone know how to do this?
Here is my code:
import UIKit
import AVFoundation
var employers = 0
var money = 0
class ViewController: UIViewController {
#IBOutlet var score: UILabel!
#IBOutlet var moneysecond: UILabel!
#IBOutlet var employees: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func clickbutton(_ sender: Any) {
money = money + 20
score.text = "$\(money).-"
UserDefaults.standard.set(score.text, forKey: "dollar")
score.text = "$\(money).-"
}
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "dollar") as? String {
score.text = x
}
}
}
Do not store some String into the UserDefaults but the actual value of money, e.g. an Int. And set the value of money initially.
Right now you set the displayed text to the value you have stored, then on click you increment a money variable which does not have anything to do with the value read from the UserDefaults, therefore you lose the information of what was previously set during the last application run.
Proposal:
move money into the ViewController
change viewDidLoad to read a value of UserDefaults.standard.integer(forKey: "dollar") into money
in viewDidAppear use the value of money to set the text
in clickbutton remove the first or second score.text assignment and do not store score.text but money in the UserDefaults
Actually this is the same idea as luk2302's suggestion but in code
class ViewController: UIViewController {
#IBOutlet var score: UILabel!
#IBOutlet var moneysecond: UILabel!
#IBOutlet var employees: UILabel!
var money = 0
var employers = 0
override func viewDidLoad() {
super.viewDidLoad()
money = UserDefaults.standard.integer(forKey: "dollar")
}
#IBAction func clickbutton(_ sender: Any) {
money = += 20
score.text = "$\(money).-"
UserDefaults.standard.set(money, forKey: "dollar")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
score.text = "$\(money).-"
}
}
I am working on a settings view for a basic app. Basic, in there is just one switch in the settings view for the user. The switch setting is saved with NSUserDefault. I use a delegate to send the switch signal from the settings view to the main view. The delegation works properly.
The UI is basic. On the main view, a label will read On in green (if the switch is on) and Off in red (if the switch is off.) There is a setting button in the top right that will segue (settingsSegue) to the settings UITableViewController, where the UISwitch is located.
The problem is loading up the NSUserDefault once the app loads. In viewDidLoad, I check to see if there's a value saved for the switch key. If there is, load it up. If not, set it to false (in the storyboard, the switch is set to false as default.) The Switch Status loads up as Off every time. Even if the default value is On. This shouldn't be happening.
ViewController.swift:
import UIKit
var nsDefaults = NSUserDefaults.standardUserDefaults()
class ViewController: UIViewController, SettingsViewControllerDelegate {
var onFromMain = Bool()
#IBOutlet weak var switchStateLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let mySavedKey = nsDefaults.objectForKey("savedSwitchSettingDefault") {
// A value exists. Load it up.
nsDefaults.objectForKey("savedSwitchSettingDefault")
print("The switch is set! \(mySavedKey)")
checkSwitchState()
}
else {
// Nothing stored in NSUserDefaults yet. Set switch to False.
nsDefaults.setBool(false, forKey: "savedSwitchSettingDefault")
checkSwitchState()
}
}
func myVCDidFinish(controller: SettingsViewController, switchState: Bool) {
onFromMain = switchState.boolValue
checkSwitchState()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "settingsSegue" {
let nav = segue.destinationViewController as! UINavigationController
let secondVC = nav.topViewController as! SettingsViewController
secondVC.delegate = self
}
}
func checkSwitchState() {
if onFromMain {
switchStateLabel.text = "On"
switchStateLabel.textColor = UIColor.greenColor()
}
else {
switchStateLabel.text = "Off"
switchStateLabel.textColor = UIColor.redColor()
}
}
}
SettingsViewController.swift:
import UIKit
protocol SettingsViewControllerDelegate {
func myVCDidFinish(controller: SettingsViewController, switchState: Bool)
}
class SettingsViewController: UITableViewController {
var delegate: SettingsViewControllerDelegate? = nil
#IBOutlet weak var switchOutlet: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
switchOutlet.on = nsDefaults.boolForKey("savedSwitchSettingDefault")
}
#IBAction func closeSettingsPageBarButtonItemPressed(sender: UIBarButtonItem) {
if (delegate != nil) {
delegate!.myVCDidFinish(self, switchState: switchOutlet.on)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
#IBAction func switchPressed(sender: UISwitch) {
// Tap the switch to change the setting.
nsDefaults.setBool(switchOutlet.on, forKey: "savedSwitchSettingDefault")
}
}
I believe my problem lies somewhere in loading up the default key for "savedSwitchSettingDefault". Is this correct? Or does the issue lie elsewhere in the code?
You can tidy things up quite a bit by relying on the fact that the default you want is false and that boolForKey gives you false when the key isn't present.
Also, by accessing the setting in viewWillAppear you can avoid the need for the delegate callback.
ViewController.swift
import UIKit
class ViewController: UIViewController {
let nsDefaults = NSUserDefaults.standardUserDefaults()
var onFromMain = false
#IBOutlet weak var switchStateLabel: UILabel!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.onFromMain = self.nsDefaults.boolForKey("savedSwitchSettingDefault")
self.checkSwitchState()
}
func checkSwitchState() {
if self.onFromMain {
switchStateLabel.text = "On"
switchStateLabel.textColor = UIColor.greenColor()
}
else {
switchStateLabel.text = "Off"
switchStateLabel.textColor = UIColor.redColor()
}
}
}
SettingsViewController.swift:
import UIKit
class SettingsViewController: UITableViewController {
let nsDefaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var switchOutlet: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.switchOutlet.on = self.nsDefaults.boolForKey("savedSwitchSettingDefault")
}
#IBAction func closeSettingsPageBarButtonItemPressed(sender: UIBarButtonItem) {
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func switchPressed(sender: UISwitch) {
// Tap the switch to change the setting.
self.nsDefaults.setBool(self.switchOutlet.on, forKey: "savedSwitchSettingDefault")
}
}
When retrieving the bool value from User Defaults, boolForKey will return false if the value doesn't exist. So in this case there's no need for unwrapping. From the documentation:
If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, false is returned.
If the value is getting set (you are sure of it), and the behavior of the app is not working correctly your problem might lie elsewhere.
I would recommend using another approach, declare your "onFromMain" as an optional boolean, then unwrap it when you need it.
var onFromMain: Bool?
...
func checkSwitchState() {
//- This will unwrap your optional or set false if its nil
let switchSate = onFromMain ?? false
//- Then you can set the values based on the value (or the default false)
switchStateLabel.text = switchState ? "On" : "Off"
switchStateLabel.textColor = switchState ? UIColor.greenColor() : UIColor.redColor()
}
Then attach the debugger with a breakpoint and see if the value is being unwrapped or if its defaulting to false.
Also, you are setting your delegate only when the segue is called, depends of the scenario, and if i understand you correctly, you migt not get the value until you have actually navigated to the settings view. So when opening the app (without navigating to the settings view) the onFromMain will never get populated.
Alternatively you can fetch the value on the view did load method to get it straight away when you load the app.