I've just started learning Swift UI, and I've been using the Hacking with Swift series to learn. For a challenge, I was told to make a conversion app that utilized pickers to select values, and takes a single input value. I have a computed property that converts the input. The problem is, the input value never makes it into the var, and I get a weird debug message. I tried to print out the input value inside the var and nothing prints out. I've tried this technique in the past and it's worked fine, so I don't know what gives. Can anyone help me out? I'm attaching my code, and the error message I get (not during compiling).
Error message: "2020-07-27 18:36:37.893045-0400 weConvert[4885:168065] Can't find keyplane that supports type 8 for keyboard iPhone-PortraitChoco-DecimalPad; using 25793PortraitChocoiPhone-Simple-Pad_Default"
Code Block // // ContentView.swift // weConvert // // Created by Aaron Goldgewert on 7/26/20. // Copyright © 2020 Aaronthetechguy. All rights reserved. // import SwiftUI struct ContentView: View { @State private var units = ["Farenheit", "Celcius", "Kelvin"] @State private var unitFrom = 0 @State private var unitTo = 0 @State private var input = "" @State var convertedValue: Double = 0 var conversion: Double{ let doubleInput = Double(input) ?? 0 print(input) print(doubleInput) let stringUnitFrom = units[unitFrom] if stringUnitFrom == "Farenheit"{ let stringUnitTo = units[unitTo] print(stringUnitTo) if stringUnitTo == "Celcius" { convertedValue = doubleInput * 1.8 + 32 } } return convertedValue } var body: some View { NavigationView{ Form{ Section(header: Text("Input:")){ Picker("Convert from:", selection: $unitFrom){ ForEach(0..<units.count){ Text("\(self.units[$0])") } } Picker("Convert to:", selection: $unitTo) { ForEach(0 ..< units.count) { Text("\(self.units[$0])") } } TextField("Input Value:", text: $input) .keyboardType(.decimalPad) Section{ Text("Converted value: \(convertedValue)") } } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Error message: "2020-07-27 18:36:37.893045-0400 weConvert[4885:168065] Can't find keyplane that supports type 8 for keyboard iPhone-PortraitChoco-DecimalPad; using 25793PortraitChocoiPhone-Simple-Pad_Default"
BabyJ is correct that you don't want to declare a State variable for your computed property. Only create state when you need to store a source of truth in your view. In this case, you want to compute a new value whenever certain other sources of truth change. There's no need to define storage for that separately.
I would also say that your units state variable would be better represented as an enumeration:
You make the enum Identifiable, which makes it easier to use in a ForEach, by including the conformance in the declaration and providing the id computed property. You make an enum CaseIterable, which lets you use the allCases property, by simply declaring the conformance. With this change, your "Convert from" picker now looks like this:
The "Convert to" picker looks similar, except it uses a binding to unitTo. These two properties, by the way, are now of type Unit, making it easier to understand what they do when you read the code:
Your convertedValue also gets a little more compact, easier to read, and better type-checked because you can switch on the case names:
For more information about state, see Managing User Interface State.
I would also say that your units state variable would be better represented as an enumeration:
Code Block enum Unit: String, Identifiable, CaseIterable { case farenheit = "Farenheit" case celcius = "Celcius" case kelvin = "Kelvin" var id: Unit { self } }
You make the enum Identifiable, which makes it easier to use in a ForEach, by including the conformance in the declaration and providing the id computed property. You make an enum CaseIterable, which lets you use the allCases property, by simply declaring the conformance. With this change, your "Convert from" picker now looks like this:
Code Block Picker("Convert from:", selection: $unitFrom) { ForEach(Unit.allCases) { unit in Text(unit.rawValue) } }
The "Convert to" picker looks similar, except it uses a binding to unitTo. These two properties, by the way, are now of type Unit, making it easier to understand what they do when you read the code:
Code Block @State private var unitFrom: Unit = .farenheit @State private var unitTo: Unit = .celcius
Your convertedValue also gets a little more compact, easier to read, and better type-checked because you can switch on the case names:
Code Block var convertedValue: Double { guard let doubleInput = Double(input) else { return 0 } switch unitFrom { case .farenheit: switch unitTo { case .farenheit: return doubleInput case .celcius: return (doubleInput - 32) / 1.8 case .kelvin: return ((doubleInput - 32) / 1.8) + 273.15 } case .celcius: switch unitTo { case .farenheit: return (doubleInput * 1.8) + 32 case .celcius: return doubleInput case .kelvin: return doubleInput + 273.15 } case .kelvin: switch unitTo { case .farenheit: return (doubleInput * 1.8) + 32 + 273.15 case .celcius: return doubleInput - 273.15 case .kelvin: return doubleInput } } }
For more information about state, see Managing User Interface State.