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
Related
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
I've been trying to present a UIAlertController when user entered wrong password for their account, the UIAlertController is located in one separate file inside a Model group which I extend the UIViewController class to add this alert functionality to it. I also has another file inside my model group namely LogIn which I wrote all the logic behind the login process so that I can call it to my LogInVC. However, I got an error of "Attempt to present on whose view is not in the window hierarchy!" whenever the function get call inside my LogInVC. I'm trying to make my project in MVC and I know what caused this error but I just don't know how to fix it. May anyone tell me how to fix this problem?
Alert
import Foundation
import UIKit
extension UIViewController {
//MARK: - Not Enough Information Alert
func notEnoughInfo(title: String, message: 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)
}
//MARK: - Incorrect Username and Password
func wrongInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "Try again", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
LogIn
import Foundation
import Firebase
class LogIn: UIViewController{
let db = Firestore.firestore()
//MARK: - userValidation()
func userValidation(Username:String, Password:String){
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapShotDocument = querySnapshot?.documents {
for doc in snapShotDocument {
let data = doc.data()
if let username = data[C.username] as? String, let password = data[C.password] as? String {
if Username == username, Password == password {
print("Log in Successfully")
}
else {
self.wrongInfo(title: "Incorrect password", message: "Try again please")
}
}
}
}
}
}
}
}
LogInVC
import UIKit
import Firebase
class LogInVC: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var logInBtn: UIButton!
let db = Firestore.firestore()
let logIn = LogIn()
override func viewDidLoad() {
super.viewDidLoad()
//logInBtn.layer.cornerRadius = logInBtn.frame.height/5
}
#IBAction func logInBtn(_ sender: UIButton) {
if let username = emailTextField.text, let password = passwordTextField.text{
if username.isEmpty || password.isEmpty{
notEnoughInfo(title: "Not enough information", message: "Please fill in all the necessary information.")
}else{
logIn.userValidation(Username: username, Password: password) //here is where problem occured
//move to another viewcontroller
}
}
}
#IBAction func signUpBtn(_ sender: UIButton) {
let push = storyboard?.instantiateViewController(withIdentifier: C.signUpVC) as! SignUpVC
push.modalPresentationStyle = .fullScreen
present(push, animated: true, completion: nil)
}
} //ends of class
You need to first dismiss the current present alert or present controller. currently you are trying to present controller over a controller that's why it shows this error. Don't present . remove this line from self.wrongInfo(title: "Incorrect password", message: "Try again please") from LogIn.
try this and you can comment again if there is anything regarding this.
For some reasons, I can't seem to get my NSUserDefaults to work correctly under Swift 2.0. It work fine under the older version, but under Swift 2.0, it doesn't work. I know the coding has been changed for Swift 2.0, but for some reason, all the information filled in the tableview goes away once I leave that page. Any suggestions?
import UIKit
class MainTableViewController: UITableViewController, UITextFieldDelegate {
#IBOutlet weak var name: UITextField!
#IBAction func btnSave() {
let userDefaults = NSUserDefaults.standardUserDefaults()
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)
let userDefaults = NSUserDefaults.standardUserDefaults()
NSUserDefaults.standardUserDefaults().setObject(name, forKey:"name")
userDefaults.synchronize()
}
override func viewDidLoad() {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(" ", forKey: "name")
super.viewDidLoad()
}
}
Perhaps try this
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")
}
}
In your viewDidLoad you're erasing the info in name, so everytime you enter your view, it will get deleted.
override func viewDidLoad() {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(" ", forKey: "name")
super.viewDidLoad()
}
Also, since name is a UITextField and not a String, when you're saving your name, it should be done like this:
NSUserDefaults.standardUserDefaults().setObject(name.text, forKey:"name")
instead of this:
NSUserDefaults.standardUserDefaults().setObject(name, forKey:"name")
Also, inside your btnSave() method, you're only saving the name.text if it's equal to "". So if you enter any other information, it won't be saved. I'd recommend modifying it so it looks something like this (you could use guard instead of if name.text also):
#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)
return
}
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(name.text, forKey:"name")
userDefaults.synchronize()
}
P.S: Remember that NSUserDefaults is intended for user preferences. I'd recommend against saving large amounts of data into NSUserDefaults.
I'm trying to display the user camera (back camera) using the AVFoundation, but I must be doing something wrong because It's only showing a black background image.
I have checked my Privacy > Camera and there isn't any option regarding the camera with my App, and I am not able to display the .Alert action to ask the user the permission to access the camera.
Here is my code, I hope you could help me because this is very weird:
import UIKit
import AVFoundation
class CodigoBarrasViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
#IBOutlet weak var messageLabel:UILabel!
#IBOutlet weak var imagenFondo:UIImageView!
#IBOutlet weak var BackgroundView:UIView!
var string:String!
var captureSession:AVCaptureSession?
var videoPreviewLayer:AVCaptureVideoPreviewLayer?
var qrCodeFrameView:UIView?
// Added to support different barcodes
let supportedBarCodes = [AVMetadataObjectTypeQRCode, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeUPCECode, AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeAztecCode]
override func viewDidAppear(animated: Bool) {
captureSession?.startRunning()
self.qrCodeFrameView?.hidden = true
}
override func viewDidLoad() {
//captureSession?.startRunning()
super.viewDidLoad()
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video
// as the media type parameter.
let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
do {
input = try AVCaptureDeviceInput(device: captureDevice) as AVCaptureDeviceInput
}
catch let error as NSError {
print(error)
}
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
//captureSession?.addInput(input as AVCaptureInput)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
captureMetadataOutput.metadataObjectTypes = supportedBarCodes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Move the message label to the top view
view.bringSubviewToFront(imagenFondo)
view.bringSubviewToFront(messageLabel)
view.bringSubviewToFront(BackgroundView)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
qrCodeFrameView?.layer.borderColor = UIColor(hex: 0x00B7BB).CGColor
qrCodeFrameView?.layer.borderWidth = 2
view.addSubview(qrCodeFrameView!)
view.bringSubviewToFront(qrCodeFrameView!)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//self.navigationController?.hidesBarsOnSwipe = true
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRectZero
//messageLabel.text = "No QR code is detected"
return
}
else
{
// Get the metadata object.
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
// Here we use filter method to check if the type of metadataObj is supported
// Instead of hardcoding the AVMetadataObjectTypeQRCode, we check if the type
// can be found in the array of supported bar codes.
if supportedBarCodes.filter({ $0 == metadataObj.type }).count > 0 {
// If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObjectForMetadataObject(metadataObj as AVMetadataMachineReadableCodeObject) as! AVMetadataMachineReadableCodeObject
qrCodeFrameView?.frame = barCodeObject.bounds
if metadataObj.stringValue != nil {
captureSession?.stopRunning()
self.qrCodeFrameView?.hidden = false
launchApp(metadataObj.stringValue)
}
}
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "seeProduct" {
let destinationController = segue.destinationViewController as! ProductoCamViewController
let string = (sender as! String!)
let backItem = UIBarButtonItem()
backItem.title = " "
navigationItem.backBarButtonItem = backItem
destinationController.ean = string
}
}
func launchApp(decodedURL: String) {
let alertPrompt = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
//let alertPrompt = UIAlertController(title: "", message: decodedURL, preferredStyle: .ActionSheet)
let confirmAction = UIAlertAction(title: "See product", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.performSegueWithIdentifier("seeProduct", sender: decodedURL)
})
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.captureSession?.startRunning()
self.qrCodeFrameView?.hidden = true
})
//let cancelAction = UIAlertAction(title: "Cancelar", style: UIAlertActionStyle.Cancel, handler: nil)
alertPrompt.addAction(confirmAction)
alertPrompt.addAction(cancelAction)
self.presentViewController(alertPrompt, animated: true, completion: nil)
}
}
Thanks in advance,
Regards.
I would suggest taking a look at UIImagePickerControllerDelegate if you're wanting to access the camera.
Implement this and all of the permission alerts are handled for you
Ive searched a lot of threads and this is my last resort because Ive seen this question asked different ways but not exactly for swift and for Username and password saving. I want my UISwitch when turned on to save my username and password info. I've been trying all day to get this UISwitch to save my username and password using NSUserDefaults. Please help me I'm at my whits end with trying it, I've searched almost every forum to find the answer but not many people show you exactly how to implement it. Below is my code. I know its bleak, as I am a beginner, but I have my "Login Button" saving my username and password, but i don't know how to get it to only save that information when I click the UISwitch and how to save it in the "view did load method". thanks for the help in advance!! I don't know what code to include into my UISwitch Method.
Here is the first part of my login button and then my view did load method. I don't have any code for the UISwitchMethod
override func viewDidLoad() {
super.viewDidLoad()
//Save username and password info if Save UISwitch is selected
switchState.on = NSUserDefaults.standardUserDefaults().boolForKey("switchState")
NSUserDefaults.standardUserDefaults().boolForKey("keepUsername")
NSUserDefaults.standardUserDefaults().boolForKey("keepPassword")
}
#IBAction func LoginButton(sender: AnyObject) {
var username = self.usernameTextField.text
var password = self.passwordTextField.text
var user = PFUser.currentUser()
NSUserDefaults.standardUserDefaults().setObject(username, forKey: "keepUsername")
NSUserDefaults.standardUserDefaults().setObject(password, forKey: "keepPassword")
if count(username) < 4 || count(password) < 5 {
var alert: UIAlertView = UIAlertView(title: "Sorry!", message: "Username Must be greater than 4 characters and the password greater that 5 characters", delegate: self, cancelButtonTitle: "Ok")
alert.show()
}else {
self.actInd.startAnimating()
PFUser.logInWithUsernameInBackground(username, password: password, block: { (user, NSError) -> Void in
self.actInd.stopAnimating()
if ((user) != nil) {
println("Success \(user) logged in")
self.performSegueWithIdentifier("toHomeFromLogin", sender: self)
}else {
var alert: UIAlertView = UIAlertView(title: "error", message: "Please Sign up :)", delegate: self, cancelButtonTitle: "Ok")
alert.show()
}
#IBAction func switchStateChanged(sender: UISwitch) {
NSUserDefaults.standardUserDefaults().setBool(switchState.on, forKey: "switchState")
}
I will add some notes and edits I would do and I hope it can help you:
var switchState = Bool()
var userName = String()
var password = String()
override func viewDidLoad() {
super.viewDidLoad()
//Load all values
switchState = NSUserDefaults.standardUserDefaults().boolForKey("switchState")
userName = NSUserDefaults.standardUserDefaults().stringForKey("keepUsername")
password = NSUserDefaults.standardUserDefaults().stringForKey("keepPassword")
//Display values somewhere
}
#IBAction func LoginButton(sender: AnyObject) {
var enteredUser = self.usernameTextField.text
var enteredPassword = self.passwordTextField.text
var user = PFUser.currentUser()
NSUserDefaults.standardUserDefaults().setObject(enteredUser, forKey: "keepUsername")
NSUserDefaults.standardUserDefaults().setObject(enteredPassword, forKey: "keepPassword")
NSUserDefaults.standardUserDefaults().synchronize()
if count(username) < 4 || count(password) < 5 {
var alert: UIAlertView = UIAlertView(title: "Sorry!", message: "Username Must be greater than 4 characters and the password greater that 5 characters", delegate: self, cancelButtonTitle: "Ok")
alert.show()
}else {
self.actInd.startAnimating()
PFUser.logInWithUsernameInBackground(username, password: password, block: { (user, NSError) -> Void in
self.actInd.stopAnimating(
if ((user) != nil) {
println("Success \(user) logged in")
self.performSegueWithIdentifier("toHomeFromLogin", sender: self)
}else {
var alert: UIAlertView = UIAlertView(title: "error", message: "Please Sign up :)", delegate: self, cancelButtonTitle: "Ok")
alert.show()
}
#IBAction func switchStateChanged(sender: UISwitch) {
//var readValueFromSwitch = something bolean
//NSUserDefaults.standardUserDefaults().setBool(readValueFromSwitch, forKey: "switchState")
//NSUserDefaults.standardUserDefaults().synchronize()
}
I send you the hole page so you can get and idea and also there is a way you can send alert messages for the Register page, look the //store data
import UIKit
class RegisterPageViewController: UIViewController {
#IBOutlet weak var userEmailTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
#IBOutlet weak var repeatPasswordTextField: UITextField!
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.
}
#IBAction func registerButtonTapped(sender: AnyObject) {
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
let userRepeatPassword = repeatPasswordTextField.text;
// Check for empty fields
if (userEmail.isEmpty || userPassword.isEmpty || userRepeatPassword.isEmpty)
{
// Display alert message
displayMyAlertMessage("All fields are required");
return;
}
// Check if passwords match
if(userPassword != userRepeatPassword)
{
//Display an alert message
displayMyAlertMessage("Passwords do not match");
return;
}
// Store data
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userEmail");
NSUserDefaults.standardUserDefaults().setObject(userPassword, forKey: "userPassword");
NSUserDefaults.standardUserDefaults().synchronize();
// Display alert message with confirmation.
var myAlert = UIAlertController(title: "Alert", message: "Registration successful, Thank you!", preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default){action in
self.dismissViewControllerAnimated(true, completion: nil);
}
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
func displayMyAlertMessage(userMessage:String)
{
var myAlert = UIAlertController(title: "Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert , animated: true, completion: nil)
}
}