问题描述
我已成功在应用程序的其他页面上使用用户默认设置,它的工作方式与您期望的一样。在这个特定的控制器中,我从带有参数的结构中调用了一个方法,并将其分配为常量。据我所知,由于某种原因,此配置不会保存或从用户默认值检索数据。我确定有办法,但是我不知道适当的办法。一些指导会在这里大有帮助。
因此,我试图将文本字段保存为用户默认设置,并在应用程序重新加载后调用文本字段数据以及方法,以便用户将所有旧数据恢复原样。现在什么也没有发生,如果保存任何内容,我什至无法打印以进行故障排除,因为我真的不知道如何打印保存的状态。菜鸟在这里。
到目前为止,我已经尝试将保存点移动到各个位置,在按下按钮的方法调用之前,在调用之后,还尝试将用户默认值插入struct方法中,这一切都不可行!鉴于确实可以加载,但到目前为止我还尝试了各种检索方法。
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:"
}
}
按钮:
@IBAction func calcButtonPress(_ sender: Any) {
// Resigns keyboard once button pressed
self.view.endEditing(true)
// State save attempt
StateManager.saveClearZone(area: Double(area.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:"
}
编辑(添加保存结构):
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)
}
}
解决方法
我最好的猜测是,从String到Double的转换有时会在此行上失败(假设不是您打算写saveClearZone
的{{1}},而是
saveClearCalcState
Double从字符串初始化的失败,这意味着如果从字符串到数字的转换失败,它可以返回StateManager.saveClearZone(area: Double(area.text!),volume: Double(volume.text!),height: Double(height!))
。由于尾部空格或格式不严格等多种问题,它可能会失败。
下面是一些修复方法的建议。
使用严格类型的函数参数,并避免使用nil
当前,您的save方法接受Any
,但是如果您知道仅在用户正确输入所有值后才保存数据,请改用Any
:
Double
如果将参数类型更改为static func saveClearCalcState(area: Double,volume: Double,height: Double)
,则代码将不再编译,因为像Double
这样的初始化程序会返回Double(area.text!)
,因此您必须先拆开可选内容以检查是否值有效,这是一个很好的机会让用户知道他们的输入是否无法处理。
如果您打算保存用户键入的内容以保持两次启动之间的状态,则可以选择Double?
而不是String?
。无论您选择哪种方法代替Any
,都将更清楚地声明该方法的用法。
使用Any
将字符串转换为数字
NumberFormatter
类为该任务提供了更大的灵活性。例如,您可以转换用户在其当前语言环境中键入的数字。假设您的用户所在的国家/地区使用逗号作为小数点分隔符,NumberFormatter
将允许他们使用首选的数字输入方式,而NumberFormatter
初始化程序始终需要使用句号作为分隔符小数点分隔符。
使用可选的链接而不是强制展开
您的代码包含许多强制展开(感叹号运算符)。
请记住,强行打开Double
的尝试将使您的应用程序崩溃。
在某些情况下可能会很好,但是仅由于文本字段中没有任何文本而使应用程序崩溃并不是很好。
首选optional chaining来强制展开。
优雅地处理nil的示例
nil
,
@Vadim,首先再次感谢您的详细帖子。再次学习了很多知识并应用了可选的处理方式,从现在开始,我将使用这些方法。很简洁!
关于用户默认设置的问题...仍然存在。这让我发疯。我尝试了很多不同的事情,几乎无法一一列举,但我会在这里尝试:
我有:
- 重新编写3次状态管理器结构并更改了许多不同的内容,并尝试了各种不同的数据类型处理
- 将保存状态点在代码中多次移动到许多不同的点
- 下载了cocopod“ Defaults”,最终出现了与UserDefaults完全相同的问题
- 我已经更改了视图,并使用许多不同的配置至少加载了10次方法
- 到目前为止,尝试了各种可选的nil处理
出了什么问题:
- 无论什么代码配置,我都会得到相同的怪异数据检索,因为如果不是nil,我会在重新启动时将值写到文本字段中,这样我就可以看到值(并打印它们)。一致地,第一个文本字段是正确的,但是第二个和第三个文本字段是不正确的。有时第二个和第三个将显示我输入的最后一个数据,而不显示最近的重启数据。就像它落后于一次重新启动。示例:
尝试一个: 栏位1:12345 栏位2:1234 区域3:123 重新启动-
加载: 节目- 栏位1:12345 栏位2: 栏位3: 新数据输入尝试: 栏位1:54321 区域2:5432 场3:543 重新启动-
显示- 栏位1:54321 栏位2:1234 栏位3:123
WTF?
我还必须指出,此确切状态对于6个其他视图控制器来说是完美的,并且使用完全相同的struct方法进行保存和检索。唯一的区别是在此VC中,我为计算器创建了一个类,而其他计算器则较小,因此在VC中将其声明为没有参数的函数。
无论如何,这是我完整的VC代码,如果您愿意提供帮助,我将非常乐意为您提供帮助,将来在我广为人知的情况下,将继续向您提供帮助:
// 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!",animated: true)
return
}
guard let headJoint = headJointSize.text,!headJoint.isEmpty else {
let alert = UIAlertController(title: "Empty field!",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,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!",handler: nil))
// Show the alert by presenting it
self.present(alert,animated: true)
}
} else {
guard let overallLength = overallLength.text,measurementLengthSelector: unitSelectorValue)
let result = cutCalc.imperialRunningBondCalc(overallLength: overallLengthValue,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!",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,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!",animated: true)
}
} else {
let result = cutCalc.imperialRunningBondCalc(overallLength: Double(overallLength.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,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!",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.",animated: true)
}
} else {
let result = cutCalc.imperialHeightCutCalc(overallHeight: Double(overallHeight.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!",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()
}
}
这是我的储蓄班:
// 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