How to Ask for Initials for Game in Swift - swift

I am making a game in Swift that records the high score and initials (similar to pinball records) and displays it on the game screen as labels. I am using the code below to record the high score and initials on my phone and update them. For the time being, this program will only be on my phone so I am not concerned about storing or updating data on a remote database.
What I don't know how to do (yet) is make a little popup asking the user to enter their initials on a keyboard when the quit button is pressed, if they have the current high score. Ideally, I want it to be able to only accept 3 characters and to update the initials label immediately, before the user is taken to a different view controller.
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var highScoreLabel: UILabel!
#IBOutlet weak var highScoreInitialsLabel: UILabel!
var score : Int = 0
//Stores and sets high score initials
var oldHighScoreInitials : String = "AAA"
var highScoreInitials : String {
get {
return UserDefaults.standard.string(forKey: "highScoreInitials") ?? "AAA"
}
set {
UserDefaults.standard.set(newValue, forKey: "highScoreInitials")
}
}
//Stores and sets high score
var oldHighScore : Int = 0
var highScore : Int {
get {
return UserDefaults.standard.integer(forKey: "highScore")
}
set {
UserDefaults.standard.set(newValue, forKey: "highScore")
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Updates high score and initials labels with stored highest score and associated initials
highScoreLabel.text = String(highScore)
highScoreInitialsLabel.text = String(highScoreInitials)
oldHighScore = highScore
oldHighScoreInitials = highScoreInitials
}
//Asks for initials if new high score, and segues to Main VC
#IBAction func quitButtonPressed(_ sender: AnyObject) {
if (score > highScore){
highScore = score
print("Ask for initials")
print("Game over, thanks for playing!"
print("Segue to Main VC")
}
else {
print("Game over, thanks for playing!")
print("Segue to Main VC")
}
}
Thank you very much to anybody that offers assistance or advice.

Here you go, pretty simple using UIAlertViewController:
//Asks for initials if new high score, and segues to Main VC
#IBAction func quitButtonPressed(_ sender: AnyObject) {
if (score > highScore){
highScore = score
let alert = UIAlertController(title: "NEW HIGH SCORE", message: "Please enter your initials", preferredStyle: .alert)
alert.addTextField(configurationHandler: configurationTextField)
alert.addAction(UIAlertAction(title: "DONE", style: .default, handler:{ (action) in
//First example of updating initials
guard alert.textFields![0].text?.characters.count != 0 else{
return
}
self.oldHighScore = self.highScore
self.oldHighScoreInitials = alert.textFields![0].text
//Segue to Main VC
}))
present(alert, animated: true, completion: nil)
}
else {
print("Game over, thanks for playing!")
print("Segue to Main VC")
}
}
func configurationTextField(textField: UITextField!){
textField.delegate = self
textField.textAlignment = .center
textField.placeholder = "_ _ _"
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let count = text.count + string.count - range.length
return count <= 3
}
//Second example of updating initials
func textFieldDidEndEditing(_ textField: UITextField) {
oldHighScore = highScore
oldHighScoreInitials = textField.text
}
And finally your controller should conform to the UITextFieldDelegate protocol:
class yourController: UIViewController, UITextFieldDelegate

After working with Arie Pinto, we determined that my segue was preventing the alert from working and was presenting an error stating that my program was attempting to present an alert whose view was not in the window hierarchy.
I programmed my segue by ctrl+dragging my quit button to a different view controller, without writing any code.
My solution to this will be to write in performSegue code in the quitButtonPressed function to allow the alert and initial update code to perform their actions before changing windows.

Related

User defaults not saving, not retrieving data

I am successfully using user defaults on other pages of my app, it works like you would expect. In this specific controller I've called a method from a struct with parameters and assigned that as a constant. As far as I can tell, for some reason this configuration will not save or retrieve data from user defaults. I am sure there is a way but I don't know the proper way. Some guidance would help a lot here.
So, I'm trying to save textfields to user defaults and call textfield data as well as method once app reloads so that user has all old data back in tact. Right now nothing happens, I can't even print to troubleshoot if anything gets saved because I really don't know how to print saved state. Noob here.
So far I have tried moving the saving point to various places, before method call on button press, after call, tried inserting user defaults into struct method also, all no go! I've also tried various retrieval methods in view did load but no go so far.
Here is where my code sits currently, non-operational user default saving:
override func viewDidLoad() {
area.delegate = self
volume.delegate = self
height.delegate = self
// Check to see if there is a saved state, if there is then use those numbers and run method for even gauge
let savedArea = StateManager.retrieveClearCalcValue(key: StateManager.areaKey) as? Double
let savedVolume = StateManager.retrieveClearCalcValue(key: StateManager.volumeKey) as? Double
let savedHeight = StateManager.retrieveClearCalcValue(key: StateManager.lengthKey) as? Double
// If there is data in saved states, set text fields to saved data and call calculate
if (savedArea != nil) && (savedVolume != nil) && (savedHeight != nil) {
area.text = String(savedArea!)
volume.text = String(savedVolume!)
height.text = String(savedHeight!)
let result = zoneCalc.clearCalc(area: Double(area.text!), volume: Double(volume.text!), height: Double(height!))
areaBorder.text = String("\(result.0) mm")
areaBorderLabel.text = "Cut result:"
}
}
Button:
#IBAction func calcButtonPress(_ sender: Any) {
// Resigns keyboard once button pressed
self.view.endEditing(true)
// State save attempt
StateManager.saveClearZone(area: Double(area.text!), volume: Double(volume.text!), height: Double(height!))
let result = clearCalc.clearZoneCalc(area: Double(area.text!) ?? 1.0, volume: Double(volume.text!) ?? 1.0, height: Double(height!))
areaBorder.text = String("\(result.0) mm")
areaBorderLabel.text = "Cut result:"
}
EDIT (adding save struct):
struct StateManager {
static var unitAreaKey = "UnitArea"
static var unitVolumeKey = "UnitVolume"
static var unitHeightKey = "UnitHeight"
// Saving user data
static func saveClearCalcState(area: Any, volume: Any, height: Any) {
// Call reference to user defaults
let defaults = UserDefaults.standard
// Save state data
defaults.set(area, forKey: unitAreaKey)
defaults.set(volume, forKey: unitVolumeKey)
defaults.set(height, forKey: unitHeightKey)
}
// Retrieve user data
static func retrieveClearCalcValue(key: String) -> Any? {
//Call reference to user defaults
let defaults = UserDefaults.standard
return defaults.value(forKey: key)
}
}
My best guess would be that conversion from String to Double sometimes fails on this line (assuming that instead of saveClearZone you meant to write saveClearCalcState):
StateManager.saveClearZone(area: Double(area.text!), volume: Double(volume.text!), height: Double(height!))
The Double's initializer from a string is failable, meaning it can return nil if conversion from a string to a number fails. It can fail because of a wide range of issues like a trailing space or a non-strict format.
Below are a few recommendations how to fix it.
Use strictly typed function arguments and avoid Any
Currently, your save method accepts Any but if you know that you only want to save your data when the user typed in all values correctly, then use Double instead:
static func saveClearCalcState(area: Double, volume: Double, height: Double)
If you change the argument types to Double, your code will no longer compile because initializers like Double(area.text!) return Double? and you'll have to unwrap the optionals first to check that the values are valid, which is a good opportunity to let the user know if their input can't be processed.
If your intention is to just save whatever the user typed in just to preserve the state between launches, you may choose String? instead of Any. Whatever your choose instead of Any will declare the usage of the method much clearer.
Use NumberFormatter to convert strings to numbers
The NumberFormatter class provides much more flexibility for this task. For example, you can convert the numbers that the user typed in in their current locale. Say, if your user is in a country where a comma is used as a decimal separator, NumberFormatter will let them use their preferred way of typing numbers while the Double initializer always requires a full stop symbol as a decimal separator.
Use optional chaining instead of force unwrapping
Your code contains a lot of force unwrapping (the exclamation point operators).
Remember that an attempt to force-unwrap a nil will crash your app.
It may be fine in some cases, but it wouldn't be nice to crash the app just because a text field doesn't have any text in it.
Prefer optional chaining to force unwrapping.
Example of graceful nil handling
#IBAction func calcButtonPress(_ sender: Any) {
// Resigns keyboard once button pressed
self.view.endEditing(true)
guard let areaText = area.text, !areaText.isEmpty else {
// TODO: alert the user that area can't be empty
return
}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale.current
guard let areaValue = numberFormatter.number(from: areaText)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
//...
//... do the same checks for volume and height
//...
// State save attempt
StateManager.saveClearZone(area: areaValue, volume: volumeValue, height: heightValue)
let result = clearCalc.clearZoneCalc(area: areaValue, volume: volumeValue, height: heightValue)
areaBorder.text = String("\(result.0) mm")
areaBorderLabel.text = "Cut result:"
}
#Vadim, firstly thanks again for the detailed post. Learned a lot again and applied your optional handling, I will be using these methods from now on. Very succinct!
With regards to the problems with user defaults... STILL HAVING THEM. This is driving me NUTS. I have tried so many different things I can barely list them all but I will try here:
I have:
Re-wrote the State manager struct 3 times and changed many different things and attempted all sort of different data type handling
Moved the save state point around in the code many times and to many different points
Downloaded a cocopod "Defaults" which ended up having exactly the same problem as UserDefaults
I've changed my view did load method AT LEAST 10 times with many different configurations
Tried all sorts of optional nil handling as of now
What is going wrong:
No matter what configurations of code I get the same weird data retrieval, because I'm writing the values to the text fields on reboot if not nil I can see values (also printing them). And consistently the 1st textfield is correct but the second and third are incorrect. Sometimes the 2nd and 3rd will show the last data i entered but not most recent reboot data. Like it lags behind one reboot. Example:
Attempt one:
Field 1: 12345
Field 2: 1234
Field 3: 123
Reboot--
Load:
Show-
Field 1: 12345
Field 2:
Field 3:
New data entry attempt:
Field 1: 54321
Field 2: 5432
Field 3: 543
Reboot--
Show-
Field 1: 54321
Field 2: 1234
Field 3: 123
WTF?
Also I must state that this exact state works perfectly for 6 other view controllers and using the exact same struct methods for saving and retrieval. Only difference is in this VC I made a class for the calculator and the others the calculator is small so its in the VC declared and called as a function with no parameters...
Anyway here is my FULL code for the VC, unaltered, if you care to help I would be much obliged and will push forward the favour in the future when I am knowledgable:
// Copyright © 2020 Roberto B. All rights reserved.
//
import UIKit
class CutCalculatorViewController: UIViewController {
// Length cut user inputs
#IBOutlet weak var overallLength: UITextField!
#IBOutlet weak var unitLength: UITextField!
#IBOutlet weak var headJointSize: UITextField!
#IBOutlet weak var metricImperialLengthSelector: UISegmentedControl!
// Length cut labels
#IBOutlet weak var buttonSelectorLabel: UILabel!
#IBOutlet weak var courseOneCut: UILabel!
#IBOutlet weak var courseOneCutLabel: UILabel!
#IBOutlet weak var courseTwoCut: UILabel!
#IBOutlet weak var courseTwoCutLabel: UILabel!
// Height cut user inputs
#IBOutlet weak var overallHeight: UITextField!
#IBOutlet weak var unitHeight: UITextField!
#IBOutlet weak var bedJointSize: UITextField!
#IBOutlet weak var metricImperialHeightSelector: UISegmentedControl!
// Height cut label
#IBOutlet weak var heightCutResult: UILabel!
override func viewDidLoad() {
if StateManager.retrieveCutCalcValue(key: StateManager.overallLengthKey) != nil && StateManager.retrieveCutCalcValue(key: StateManager.unitLengthKey) != nil && StateManager.retrieveCutCalcValue(key: StateManager.headJointSizeKey) != nil && StateManager.retrieveCutCalcValue(key: StateManager.unitLengthSelector) != nil && StateManager.retrieveCutCalcValue(key: StateManager.unitLengthSelector) as? Int == 0 {
let savedOverallLength = StateManager.retrieveCutCalcValue(key: StateManager.overallLengthKey) as? Double
let savedUnitLength = StateManager.retrieveCutCalcValue(key: StateManager.unitLengthKey) as? Double
let savedHeadJoint = StateManager.retrieveCutCalcValue(key: StateManager.headJointSizeKey) as? Double
metricImperialLengthSelector.selectedSegmentIndex = 0
print(savedOverallLength!)
print(savedUnitLength!)
print(savedHeadJoint!)
print(metricImperialLengthSelector.selectedSegmentIndex)
overallLength.text = String(savedOverallLength!)
unitLength.text = String(savedUnitLength!)
headJointSize.text = String(savedHeadJoint!)
let result = cutCalc.metricRunningBondCalc(overallLength: savedOverallLength!, unitLength: savedUnitLength!, headJointSize: savedHeadJoint!)
courseOneCut.text = String("\(result.0) mm")
courseTwoCut.text = "N/A"
courseTwoCut.textColor = UIColor.clear
courseTwoCutLabel.textColor = UIColor.clear
buttonSelectorLabel.text = "Stack bond cut result"
courseOneCutLabel.text = "Cut result:"
} else if StateManager.retrieveCutCalcValue(key: StateManager.overallLengthKey) != nil && StateManager.retrieveCutCalcValue(key: StateManager.unitLengthKey) != nil && StateManager.retrieveCutCalcValue(key: StateManager.headJointSizeKey) != nil && StateManager.retrieveCutCalcValue(key: StateManager.unitLengthSelector) != nil && StateManager.retrieveCutCalcValue(key: StateManager.unitLengthSelector) as? Int == 1 {
let savedOverallLength = StateManager.retrieveCutCalcValue(key: StateManager.overallLengthKey)
let savedUnitLength = StateManager.retrieveCutCalcValue(key: StateManager.unitLengthKey)
let savedHeadJoint = StateManager.retrieveCutCalcValue(key: StateManager.headJointSizeKey)
metricImperialLengthSelector.selectedSegmentIndex = 1
print(savedOverallLength!)
print(savedUnitLength!)
print(savedHeadJoint!)
print(metricImperialLengthSelector.selectedSegmentIndex)
overallLength.text = savedOverallLength as? String
unitLength.text = savedUnitLength as? String
headJointSize.text = savedHeadJoint as? String
let result = cutCalc.imperialRunningBondCalc(overallLength: savedOverallLength as! Double, unitLength: savedUnitLength as! Double, headJointSize: savedHeadJoint as! Double)
courseOneCut.text = String("\(result.0) inches")
courseTwoCut.text = "N/A"
courseTwoCut.textColor = UIColor.clear
courseTwoCutLabel.textColor = UIColor.clear
buttonSelectorLabel.text = "Stack bond cut result"
courseOneCutLabel.text = "Cut result:"
}
super.viewDidLoad()
}
// Initialize cut calculator structure
let cutCalc = CutCalculator()
// Initialize metric imperial segments
var unitLengthMeasurement: Bool = true
var unitHeightMeasurement: Bool = true
// Length cut buttons
// Stack bond- DONE
#IBAction func stackBondLength(_ sender: Any) {
// Resigns keyboard once button pressed
self.view.endEditing(true)
// Activate methods for cut calculation metric/imperial & label changes
if unitLengthMeasurement == true {
guard let overallLength = overallLength.text, !overallLength.isEmpty else {
let alert = UIAlertController(title: "Empty field!", message: "All text fields must contain data.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
guard let unitLength = unitLength.text, !unitLength.isEmpty else {
let alert = UIAlertController(title: "Empty field!", message: "All text fields must contain data.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
guard let headJoint = headJointSize.text, !headJoint.isEmpty else {
let alert = UIAlertController(title: "Empty field!", message: "All text fields must contain data.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale.current
guard let overallLengthValue = numberFormatter.number(from: overallLength)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
guard let unitLengthValue = numberFormatter.number(from: unitLength)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
guard let headJointValue = numberFormatter.number(from: headJoint)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
let unitSelectorValue = metricImperialLengthSelector.selectedSegmentIndex
StateManager.saveLengthCutCalcState(overallLength: overallLengthValue, unitLength: unitLengthValue, headJointSize: headJointValue, measurementLengthSelector: unitSelectorValue)
let result = cutCalc.metricRunningBondCalc(overallLength: overallLengthValue, unitLength: unitLengthValue, headJointSize: headJointValue)
courseOneCut.text = "\(result.0) mm"
courseTwoCut.text = "N/A"
courseTwoCut.textColor = UIColor.clear
courseTwoCutLabel.textColor = UIColor.clear
buttonSelectorLabel.text = "Stack bond cut result"
courseOneCutLabel.text = "Cut result:"
// Popup alert
if result.0 < 36.0 {
// First instantiate the UIAlertController
let alert = UIAlertController(title: "Small cut!", message: "Depending on conditions you may be able to open or squeeze head joints to eliminate this small cut.", preferredStyle: .alert)
// Add action buttons to it and attach handler functions if you want to
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
// Show the alert by presenting it
self.present(alert, animated: true)
}
} else {
guard let overallLength = overallLength.text, !overallLength.isEmpty else {
let alert = UIAlertController(title: "Empty field!", message: "All text fields must contain data.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
guard let unitLength = unitLength.text, !unitLength.isEmpty else {
let alert = UIAlertController(title: "Empty field!", message: "All text fields must contain data.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
guard let headJoint = headJointSize.text, !headJoint.isEmpty else {
let alert = UIAlertController(title: "Empty field!", message: "All text fields must contain data.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale.current
guard let overallLengthValue = numberFormatter.number(from: overallLength)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
guard let unitLengthValue = numberFormatter.number(from: unitLength)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
guard let headJointValue = numberFormatter.number(from: headJoint)?.doubleValue else {
// TODO: alert the user that the text they entered can't be recognized as a numeric value
return
}
let unitSelectorValue = metricImperialLengthSelector.selectedSegmentIndex
StateManager.saveLengthCutCalcState(overallLength: overallLengthValue, unitLength: unitLengthValue, headJointSize: headJointValue, measurementLengthSelector: unitSelectorValue)
let result = cutCalc.imperialRunningBondCalc(overallLength: overallLengthValue, unitLength: unitLengthValue, headJointSize: headJointValue)
courseOneCut.text = "\(result.0) inches"
courseTwoCut.text = "N/A"
courseTwoCut.textColor = UIColor.clear
courseTwoCutLabel.textColor = UIColor.clear
buttonSelectorLabel.text = "Stack bond cut result"
courseOneCutLabel.text = "Cut result:"
// Popup alert
if result.2 < 2.5 {
// First instantiate the UIAlertController
let alert = UIAlertController(title: "Small cut!", message: "Depending on conditions you may be able to open or squeeze head joints to eliminate this small cut.", preferredStyle: .alert)
// Add action buttons to it and attach handler functions if you want to
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
// Show the alert by presenting it
self.present(alert, animated: true)
}
}
}
// Running bond- DONE
#IBAction func runningBondLength(_ sender: Any) {
// Resigns keyboard once button pressed
self.view.endEditing(true)
// Activate methods for cut calculation metric/imperial & label changes
if unitLengthMeasurement == true {
let result = cutCalc.metricRunningBondCalc(overallLength: Double(overallLength.text!) ?? 1.0, unitLength: Double(unitLength.text!) ?? 1.0, headJointSize: Double(headJointSize.text!) ?? 1.0)
courseOneCut.text = String("\(result.0) mm")
courseTwoCut.text = String("\(result.1) mm")
buttonSelectorLabel.text = "Running bond cut results"
courseTwoCut.textColor = #colorLiteral(red: 0.7333333333, green: 0.8823529412, blue: 0.9803921569, alpha: 1)
courseTwoCutLabel.textColor = #colorLiteral(red: 0.7333333333, green: 0.8823529412, blue: 0.9803921569, alpha: 1)
courseOneCutLabel.text = "Course 1 cut:"
courseTwoCutLabel.text = "Course 2 cut:"
// Popup alert
if result.0 < 36 || result.1 < 36 {
// First instantiate the UIAlertController
let alert = UIAlertController(title: "Small cut!", message: "Depending on conditions you may be able to open or squeeze head joints to eliminate this small cut.", preferredStyle: .alert)
// Add action buttons to it and attach handler functions if you want to
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
// Show the alert by presenting it
self.present(alert, animated: true)
}
} else {
let result = cutCalc.imperialRunningBondCalc(overallLength: Double(overallLength.text!) ?? 1.0, unitLength: Double(unitLength.text!) ?? 1.0, headJointSize: Double(headJointSize.text!) ?? 1.0)
courseOneCut.text = String("\(result.0) inches")
courseTwoCut.text = String("\(result.1) inches")
buttonSelectorLabel.text = "Running bond cut results"
courseTwoCut.textColor = #colorLiteral(red: 0.7333333333, green: 0.8823529412, blue: 0.9803921569, alpha: 1)
courseTwoCutLabel.textColor = #colorLiteral(red: 0.7333333333, green: 0.8823529412, blue: 0.9803921569, alpha: 1)
courseOneCutLabel.text = "Course 1 cut:"
courseTwoCutLabel.text = "Course 2 cut:"
// Popup alert
if result.2 < 2.5 || result.3 < 2.5 {
// First instantiate the UIAlertController
let alert = UIAlertController(title: "Small cut!", message: "Depending on conditions you may be able to open or squeeze head joints to eliminate this small cut.", preferredStyle: .alert)
// Add action buttons to it and attach handler functions if you want to
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
// Show the alert by presenting it
self.present(alert, animated: true)
}
}
}
// Height cut button- DONE
#IBAction func heightCut(_ sender: Any) {
// Resigns keyboard once button pressed
self.view.endEditing(true)
// Activate methods for cut calculation metric/imperial & label changes
if unitHeightMeasurement == true {
let result = cutCalc.metricHeightCutCalc(overallHeight: Double(overallHeight.text!) ?? 1.0, unitHeight: Double(unitHeight.text!) ?? 1.0, beadJointSize: Double(bedJointSize.text!) ?? 1.0)
heightCutResult.text = String("\(result) mm")
// Popup alert
if result < 30.5 {
// First instantiate the UIAlertController
let alert = UIAlertController(title: "Small cut!", message: "Depending on conditions you may be able to gain or squeeze to eliminate this small cut.", preferredStyle: .alert)
// Add action buttons to it and attach handler functions if you want to
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
// Show the alert by presenting it
self.present(alert, animated: true)
}
} else {
let result = cutCalc.imperialHeightCutCalc(overallHeight: Double(overallHeight.text!) ?? 1.0, unitHeight: Double(unitHeight.text!) ?? 1.0, beadJointSize: Double(bedJointSize.text!) ?? 1.0)
heightCutResult.text = String("\(result) inches")
// Popup alert
if result.1 < 2.5 {
// First instantiate the UIAlertController
let alert = UIAlertController(title: "Small cut!", message: "Depending on conditions you may be able to gain or squeeze to eliminate this small cut.", preferredStyle: .alert)
// Add action buttons to it and attach handler functions if you want to
alert.addAction(UIAlertAction(title: "Got it!", style: .cancel, handler: nil))
// Show the alert by presenting it
self.present(alert, animated: true)
}
}
}
// Length cut calculator metric imperial selector- DONE
#IBAction func lengthUnitSelector(_ sender: UISegmentedControl) {
if (metricImperialLengthSelector.selectedSegmentIndex == 0) {
unitLengthMeasurement = true
} else {
unitLengthMeasurement = false
}
}
// Height cut calculator metric imperial selector- DONE
#IBAction func heightUnitSelector(_ sender: UISegmentedControl) {
if (metricImperialHeightSelector.selectedSegmentIndex == 0) {
unitHeightMeasurement = true
} else {
unitHeightMeasurement = false
}
}
// Acts to dismiss number keyboard when user taps outside
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
overallLength.resignFirstResponder()
unitLength.resignFirstResponder()
headJointSize.resignFirstResponder()
overallHeight.resignFirstResponder()
unitHeight.resignFirstResponder()
bedJointSize.resignFirstResponder()
}
}
Here is my saving class:
// Cut calculator saved states
static var overallLengthKey = "OverallLength"
static var unitLengthKey = "UnitLength"
static var headJointSizeKey = "HeadJointSize"
static var unitLengthSelector = "LengthUnitSelector"
static var overallHeightKey = "OverallHeight"
static var unitHeightKey = "UnitHeight"
static var bedJointSizeKey = "BedJointSize"
static var unitHeightSelector = "HeightUnitSelector"
// Saving user data
static func saveLengthCutCalcState(overallLength: Double, unitLength: Double, headJointSize: Double, measurementLengthSelector: Int) {
let defaults = UserDefaults.standard
defaults.set(overallLength, forKey: overallLengthKey)
defaults.set(unitLength, forKey: unitLengthKey)
defaults.set(headJointSize, forKey: headJointSizeKey)
defaults.set(measurementLengthSelector, forKey: unitLengthSelector)
defaults.synchronize()
}
// Saving user data
static func saveHeightCutCalcState(overallHeight: Double, unitHeight: Double, bedJointSize: Double, measurementHeightSelector: Int) {
let defaults = UserDefaults.standard
defaults.set(overallHeight, forKey: overallHeightKey)
defaults.set(unitHeight, forKey: unitHeightKey)
defaults.set(bedJointSize, forKey: bedJointSizeKey)
defaults.set(measurementHeightSelector, forKey: unitHeightSelector)
}
// Retrieve user data
static func retrieveCutCalcValue(key: String) -> Any? {
let defaults = UserDefaults.standard
defaults.synchronize()
return defaults.value(forKey: key)
}
// Clear state
static func clearLengthCutCalcState() {
let defaults = UserDefaults.standard
// Clear the saved state data in user defaults
defaults.removeObject(forKey: overallLengthKey)
defaults.removeObject(forKey: unitLengthKey)
defaults.removeObject(forKey: headJointSizeKey)
defaults.removeObject(forKey: unitLengthSelector)
}
static func clearHeightCutCalcState() {
let defaults = UserDefaults.standard
// Clear the saved state data in user defaults
defaults.removeObject(forKey: overallHeightKey)
defaults.removeObject(forKey: unitHeightKey)
defaults.removeObject(forKey: bedJointSizeKey)
defaults.removeObject(forKey: unitHeightSelector)
}
// DONE- Even gauge calcualtor saved states -DONE

Creating Firestore document with text field data

So im making a sign up page on xcode with firebase and it currently work well but I would like to get more information from the user such as the first name and the last name . Basically I want my code to automatically create a document named with the email of the user that just sign up in the "Users" collection on firestore and after create the field "FirstName" and "LastName" with the text in those textfield! You can see my code below . Thanks for your help in advance. And I also provide a screenshot ( I did it manually to explain what I want it to do )
#IBOutlet weak var FirstName: UITextField!
#IBOutlet weak var LastName: UITextField!
#IBOutlet weak var Email: UITextField!
#IBOutlet weak var Password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func SignUpButton(_ sender: Any) {
if Email.text?.isEmpty == true {
print("No text in email field")
return
}
if Password.text?.isEmpty == true {
print("No text in password field")
return
}
if FirstName.text?.isEmpty == true {
print("No text in first name field")
return
}
if LastName.text?.isEmpty == true {
print("No text in Last name field")
return
}
SignUp()
}
#IBAction func LoginButton(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "SignInPage")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
func SignUp() {
Auth.auth().createUser(withEmail: Email.text!, password: Password.text!) { (authResult, error) in
guard let user = authResult?.user, error == nil else {
print("Error \(error!.localizedDescription)")
return
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "Manager0")
vc.modalPresentationStyle = .overFullScreen
self.present(vc, animated: true)
}
}
You cannot get a user's first and last name as Firebase only honours the full name only via displayName.
Moreover, with email and password login, you only get the email - as Firebase would have no way to get the user's Name. (Suppose a user has Yahoo Email ID - how would it get the name of the user?)

NSUserDefault issue

I am creating a divelog app. When someone select a new dive, they will get an alert with two options: "Blank template" or "Use last dive as template".
The issue is with the later, last dive as a template.
Each dive I record the data to the NSUserDefaults. So, when someone wants last dive as a template, I just call each of the defaults and populate the tableview.
However, I am getting a nil error on the first field, the diveSiteTextfield.text, this baffles me:
Here is where the data gets saved to NSUserDefaults
As you can see, I added a print command to test to see if the diveSite field is nil, but it displays the correct data. The diveSiteTextfield.text is not nil.
import UIKit
import Parse
var diveUUID = NSUUID().UUIDString
class DiveDetails1VC: UIViewController, UITextFieldDelegate, UITableViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource
{
let userDefault = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var ratingControl: DiveSiteRating!
#IBOutlet weak var diveSiteTextfield: UITextField!
#IBOutlet weak var diveDurationTextfield: UITextField!
...
func toDive2NewParse () {
let diveObject = PFObject(className: "divelog")
diveObject["diveNumber"] = diveNumberInt
diveObject["diveSite"] = diveSiteTextfield.text
diveObject["diveDuration"] = diveDurationTextfield.text
diveObject["maxDepth"] = diveMaxDepthTextfield.text
diveObject["diveDate"] = diveDateTextfield.text
diveObject["diveTime"] = diveTimeTextfield.text
diveObject["bodyOfWater"] = diveBodyOfWaterTextfield.text
diveObject["diveBuddy"] = diveBuddyTextfield.text
diveObject["diveCity"] = diveCityTextfield.text
diveObject["maxDepthSymbol"] = diveMaxDepthSymbolTextField.text
diveObject["diveCountry"] = diveCountryTextField.text
diveObject["uuid"] = "\(diveUUID)"
// diveObject["diveRating"] = ratingControl
diveObject["entryFrom"] = "Divelog"
if PFUser.currentUser()!.username == nil {
diveObject["username"] = "Not Signed in"
} else {
diveObject["username"] = PFUser.currentUser()!.username
}
diveObject.saveEventually()
userDefault.setObject(diveNumberInt, forKey: "diveNumber")
userDefault.setObject(diveSiteTextfield.text, forKey: "diveSite")
userDefault.setObject(diveDurationTextfield.text!, forKey: "diveDuration")
userDefault.setObject(diveMaxDepthTextfield.text, forKey: "diveDepth")
userDefault.setObject(diveMaxDepthSymbolTextField.text, forKey: "symbol_maxDepth")
userDefault.setObject(diveDateTextfield.text, forKey: "entryDate")
userDefault.setObject(diveTimeTextfield.text, forKey: "entryTime")
userDefault.setObject(diveCityTextfield.text, forKey: "diveCity")
userDefault.setObject(diveCountryTextField.text, forKey: "diveCountry")
userDefault.setObject(diveBodyOfWaterTextfield.text, forKey: "bodyOfWater")
userDefault.setObject(diveBuddyTextfield.text, forKey: "diveBuddy")
userDefault.setObject(diveUUID, forKey: "uuid")
userDefault.synchronize()
print("DiveSite is ...\(userDefault.objectForKey("diveSite"))")
}
Here is the UIAlertAction that retrieves the "last dive":
#IBAction func addNewDive (sender: AnyObject) {
let alertController = UIAlertController(title: "New Dive", message: "Choose a template", preferredStyle:UIAlertControllerStyle.ActionSheet)
let buttonOne = UIAlertAction(title: "Blank template", style: .Default, handler: { (action) -> Void in
self.performSegueWithIdentifier("segueNewDive1", sender: self)
print("Button One Pressed")
})
let buttonTwo = UIAlertAction(title: "Use last dive as template", style: .Default, handler: { (action) -> Void in
self.VC1.diveSiteTextfield.text = self.userDefault.stringForKey("diveSite")
self.VC1.diveDurationTextfield.text = self.userDefault.stringForKey("diveDuration")
self.VC1.diveMaxDepthTextfield.text = self.userDefault.stringForKey("diveDepth")
...
self.DNo.textView.text = self.userDefault.objectForKey("diveNotes") as? String;
self.userDefault.synchronize()
self.performSegueWithIdentifier("segueNewDive1", sender: self)
any help on understanding why the nil will be greatly appreciated.
Updated with Vladian's comment

How to use custom UIAlertview in multiple ViewControllers?

I am looking for a way to call a custom alert view from multiple view controllers. So far I have made several different attempts without success.
I created an alert view with an interface builder that works fine on one view controller but not the other.
I then tried creating the alert view programmatically thinking it may have something to do with the outlets not being connected on the other view controller. This one also worked on one view controller and not the other.
I made a separate swift file and made a public function and the same result. With this last method, I am able to successfully re-use a regular UIAlertController on multiple view controllers but that is not exactly what I am looking for.
With the first two methods, I do not get any compiling errors. The app runs fine and then crashes when I call the alert from another view controller.
Thanks in advance for any input!
EDIT:
This example works when I put it in another swift file.
public func showSimpleAlert(title: String, message: String?, presentingController: UIViewController) {
if IS_OS_8_OR_LATER() {
let controller = UIAlertController(title: title, message: message, preferredStyle: .Alert)
controller.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: { (action) -> Void in
}))
presentingController.presentViewController(controller, animated: true, completion: nil)
} else {
let alert = UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
}
This is the one I want to work on.
public func showAlert(oMsg: String, oTitle:String) {
alertView.backgroundColor = UIColor.whiteColor()
alertView.layer.cornerRadius = 25
alertTitleLabel.text = oTitle as String
alertTitleLabel.font = UIFont(name: "Open-Sans-Bold", size: 20)
alertTitleLabel.textColor = UIColor.blackColor()
alertTitleLabel.textAlignment = .Center
alertTitleLabel.numberOfLines = 1
alertTitleLabel.frame = CGRectMake(25, 60, 264, 112)
alertLabel.text = oMsg as String
alertLabel.font = UIFont(name: "Open-Sans", size: 20)
alertLabel.textColor = UIColor.blackColor()
alertLabel.textAlignment = .Center
alertLabel.numberOfLines = 4
alertLabel.frame = CGRectMake(25, 130, 264, 112)
okButton.setTitle("OK", forState: .Normal)
okButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
okButton.frame = CGRectMake(60, 230, 197, 75)
okButton.addTarget(UIViewController.self, action:#selector(LoginViewController.buttonAction(_:)), forControlEvents: .TouchUpInside)
}
I will give the answer for a simple custom alertview which is basically a modified uiviewcontroller. you can use a uiviewcontroller as a uialertviewcontroller as follow.
Simple AlertView::
The AlertVC:
import UIKit
class ErrorAlert: UIViewController {
var titlenote:String = ""
var message:String = ""
#IBOutlet weak var cancelBtn: UIButton!
#IBOutlet weak var messageHolder: UILabel!
#IBOutlet weak var imageHolder: UIImageView!
#IBOutlet weak var titleHolder: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.messageHolder.text = self.message
self.titleHolder.text = self.titlenote
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func dismiss(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
This viewcontroller can be reuse in any vc and any number of times.
Useage Example::
let alertController = self.storyboard?.instantiateViewController(withIdentifier: "erroralert") as! ErrorAlert
alertController.titlenote = "Invalid login"
alertController.message = "Invalid facebook account."
alertController.providesPresentationContextTransitionStyle = true
alertController.definesPresentationContext = true
alertController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
alertController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(alertController, animated: true, completion: nil)
I have made the background of the alertviewvc semitransparent by setting the alpha value.
Actual Display ::
You can make more complex alertview by this method but for reusability you have apply some logic as the button actions will be different for different viewcontroller. Example -- Sometime you can use the alertview for logout alert or sometime for submitting a form .So in both cases the action will be different so for reusability you have to write extra logic.
Another alertView::
I hope my answer will help you.:)

Retrieving data by NSUserDefaults Swift 2.0

Currently, my code for NSUserDefaults work fine. The only issue I have now is how can I send the data store to another page? I can get it now save the data, but how do I retrieve that same data via NSUserDefaults to a secondary page?
import UIKit
class MainTableViewController: UITableViewController, UITextFieldDelegate {
let userDefaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var name: UITextField!
#IBAction func btnSave() {
if name.text == "" {
let alert = UIAlertController(title: "Data", message: "Missing Name.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
else {
userDefaults.setObject(name.text, forKey:"name")
userDefaults.synchronize()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
name.text = userDefaults.stringForKey("name")
}
}
You can retrieve your data from your second page with same code.
Create your NSUserDefaults in second page like this ;
First of all add these codes to your FirstPage in IBAction func. Don't use the if else statement for test.
UserDefaults.standard.set("yourValue", forKey: "yourKey")
When you press the button it should be save your data with your key.
In the secondPage you can retrieve your data in your viewDidLoad method like this;
override func viewDidLoad() {
super.viewDidLoad()
print(UserDefaults.standard.value(forKey: "yourKey") ?? "defaultValue in case key is not found")
print(value)
}
It should be print your value to your console (bottom window in your xcode) when the secondPage is open. If you still can't retrieve your value please share to me a your storyboard picture. I want to see your connections.