用户默认值不保存,不检索数据 使用严格类型的函数参数,并避免使用nil 使用Any将字符串转换为数字使用可选的链接而不是强制展开优雅地处理nil的示例

问题描述

我已成功在应用程序的其他页面上使用用户认设置,它的工作方式与您期望的一样。在这个特定的控制器中,我从带有参数的结构中调用一个方法,并将其分配为常量。据我所知,由于某种原因,此配置不会保存或从用户认值检索数据。我确定有办法,但是我不知道适当的办法。一些指导会在这里大有帮助。

因此,我试图将文本字段保存为用户认设置,并在应用程序重新加载后调用文本字段数据以及方法,以便用户将所有旧数据恢复原样。现在什么也没有发生,如果保存任何内容,我什至无法打印以进行故障排除,因为我真的不知道如何打印保存的状态。菜鸟在这里

到目前为止,我已经尝试将保存点移动到各个位置,在按下按钮的方法调用之前,在调用之后,还尝试将用户认值插入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