Пользовательские значения по умолчанию не сохраняются, не извлекаются данные

#swift #nsuserdefaults

#swift #nsuserdefaults

Вопрос:

Я успешно использую пользовательские значения по умолчанию на других страницах моего приложения, оно работает так, как вы ожидаете. В этом конкретном контроллере я вызвал метод из структуры с параметрами и назначил его как константу. Насколько я могу судить, по какой-то причине эта конфигурация не будет сохранять или извлекать данные из пользовательских значений по умолчанию. Я уверен, что есть способ, но я не знаю правильного способа. Здесь очень помогли бы некоторые рекомендации.

Итак, я пытаюсь сохранить текстовые поля в пользовательских значениях по умолчанию и вызвать данные текстового поля, а также метод после перезагрузки приложения, чтобы у пользователя были все старые данные в такте. Прямо сейчас ничего не происходит, я даже не могу распечатать, чтобы устранить неполадки, если что-то будет сохранено, потому что я действительно не знаю, как распечатать сохраненное состояние. Новичок здесь.

До сих пор я пытался переместить точку сохранения в разные места, перед вызовом метода при нажатии кнопки, после вызова также пытался вставить пользовательские значения по умолчанию в метод 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) amp;amp; (savedVolume != nil) amp;amp; (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!), 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:"
    
}
  

РЕДАКТИРОВАТЬ (добавление структуры сохранения):

 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)
}
}
  

Комментарии:

1. Код вообще не связан с UserDefaults . Как мы можем знать, что это такое zoneCalc и StateManager прочее?

2. исправлено, что … zoneCalc — это просто калькулятор, который вычисляет некоторые числа, взятые из текстовых полей…

3. Внутри вызывается calcButtonPress метод StateManager.saveClearZone , но в предоставленном вами коде такого метода нет, не могли бы вы добавить его, пожалуйста?

4. исправлено, проблема была не в этом. Это была моя опечатка при публикации этим утром после долгой ночной смены. Я должен изменить свой код на post, потому что он содержит конфиденциальную интеллектуальную собственность… проще говоря: приведенный выше код работает хорошо, сохраняя состояние пользователя внутри вызова функции в контроллере представления, однако в этой конфигурации я пытаюсь сохранить после и до вызова метода struct, у меня возникают проблемы… Должен ли я сохранять весь вызов метода struct? Можно ли это сделать? … прошу прощения за головоломки, все еще привыкаю к тому, как публиковать здесь.

5. Получил его для печати сохраненных значений в viewDidLoad, и похоже, что только одно из трех печатает значение, а другие печатают ноль… Я трижды проверил, что мой код исправен. Что происходит? помощь приветствуется 🙂

Ответ №1:

Мое лучшее предположение заключается в том, что преобразование из String в Double иногда завершается ошибкой в этой строке (при условии, что вместо saveClearZone вы хотели написать saveClearCalcState ):

 StateManager.saveClearZone(area: Double(area.text!), volume: Double(volume.text!), height: Double(height!))
  

Инициализатор Double из строки является отказоустойчивым, что означает, что он может вернуться nil , если преобразование из строки в число завершается неудачей. Это может привести к сбою из-за широкого круга проблем, таких как конечный пробел или нестрогий формат.

Ниже приведены несколько рекомендаций, как это исправить.

Используйте строго типизированные аргументы функции и избегайте Any

В настоящее время ваш метод сохранения принимается Any , но если вы знаете, что хотите сохранить свои данные только тогда, когда пользователь правильно ввел все значения, используйте Double вместо этого:

 static func saveClearCalcState(area: Double, volume: Double, height: Double)
  

Если вы измените типы аргументов на Double , ваш код больше не будет компилироваться, потому что инициализаторам нравится Double(area.text!) return Double? , и вам придется сначала развернуть опции, чтобы проверить, что значения действительны, что является хорошей возможностью сообщить пользователю, если его ввод не может быть обработан.

Если вы намерены просто сохранить все, что ввел пользователь, чтобы сохранить состояние между запусками, вы можете выбрать String? вместо Any . Независимо от того, что вы выберете вместо Any , использование метода будет намного понятнее.

Используется NumberFormatter для преобразования строк в числа

NumberFormatter Класс обеспечивает гораздо большую гибкость для этой задачи. Например, вы можете преобразовать числа, введенные пользователем, в их текущую локаль. Скажем, если ваш пользователь находится в стране, где в качестве десятичного разделителя используется запятая, NumberFormatter это позволит им использовать предпочитаемый способ ввода чисел, в то время как Double инициализатору всегда требуется символ полной остановки в качестве десятичного разделителя.

Используйте необязательную цепочку вместо принудительного развертывания

Ваш код содержит много принудительного развертывания (операторы восклицательного знака). Помните, что попытка принудительного развертывания nil приведет к сбою вашего приложения. В некоторых случаях это может быть нормально, но было бы нехорошо завершать работу приложения только потому, что в текстовом поле нет текста.

Предпочитаю необязательную цепочку принудительному развертыванию.

Пример изящной обработки с нулевым значением

 @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:"    
}
  

Комментарии:

1. спасибо за подробный пост, я определенно чему-то научился с помощью number formatter, я применил его. Я также обрабатываю опции путем объединения. У меня все еще возникают проблемы! Сверхпрочность сохранения ключей и значений каким-то образом нарушается. Итак, на данный момент я попробовал много новых вещей, таких как форматирование чисел в строки, это все, что сохраняется сейчас, сохранение двойных значений возвращает ноль для всех ключей. Также сохранение строк является дурацким, оно сохранит значение ключа 1 идеально, но 2 и 3 всегда разные, иногда вторые последние значения, которые я ввел … не имеют смысла. Я даже пытался использовать. syncronize() — нет.

2. @AppMonster, я отредактировал свой ответ, включив пример того, как я буду обрабатывать nils в такой ситуации. Если вы сделали что-то подобное и все еще испытываете проблемы, пожалуйста, приложите фактический код, который не работает, и я буду рад помочь.

3. изменил мой пост ниже со всем кодом. Все еще не работает, все еще ломаю голову над этим. Любая помощь действительно ценится!

Ответ №2:

@Vadim, во-первых, еще раз спасибо за подробный пост. Снова многому научился и применил вашу необязательную обработку, с этого момента я буду использовать эти методы. Очень лаконично!

Что касается проблем с пользовательскими значениями по умолчанию… ОНИ ВСЕ ЕЩЕ ЕСТЬ. Это сводит меня с ума. Я перепробовал так много разных вещей, что едва могу перечислить их все, но я попробую здесь:

У меня есть:

  • Переписал структуру State manager 3 раза и изменил много разных вещей и предпринял попытку обработки всех типов данных
  • Перемещал точку состояния сохранения в коде много раз и во многие разные точки
  • Загруженный cocopod «Defaults», который в конечном итоге столкнулся с точно такой же проблемой, как UserDefaults
  • Я изменил свой метод загрузки view did НЕ МЕНЕЕ 10 раз с множеством разных конфигураций
  • На данный момент пробовал все виды необязательной обработки с нулевым значением

Что происходит не так:

  • Независимо от того, какие конфигурации кода я получаю одно и то же странное извлечение данных, потому что я записываю значения в текстовые поля при перезагрузке, если не nil, я могу видеть значения (также их распечатывая). И последовательно 1-е текстовое поле является правильным, но второе и третье неверны. Иногда 2-й и 3-й отображают последние введенные данные, но не самые последние данные перезагрузки. Как будто это отстает от одной перезагрузки. Пример:

Попытка первая: Поле 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 amp;amp; StateManager.retrieveCutCalcValue(key: StateManager.unitLengthKey) != nil amp;amp; StateManager.retrieveCutCalcValue(key: StateManager.headJointSizeKey) != nil amp;amp; StateManager.retrieveCutCalcValue(key: StateManager.unitLengthSelector) != nil amp;amp; 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 amp;amp; StateManager.retrieveCutCalcValue(key: StateManager.unitLengthKey) != nil amp;amp; StateManager.retrieveCutCalcValue(key: StateManager.headJointSizeKey) != nil amp;amp; StateManager.retrieveCutCalcValue(key: StateManager.unitLengthSelector) != nil amp;amp; 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 amp; 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 amp; 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 amp; 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()
}
  

}

Вот мой класс сохранения:

 // 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