Read and make calculations with textfield input SwiftUI - ios

I want to make calculations with inputs from 2 different Textfields and put the output in a Text. See code:
#State var input1: String = ""
#State var input2: String = ""
var calculation : Double {
let calculationProduct = Double(input1) * Double(input2)
return calculationProduct
}
var body: some View {
VStack{
TextField("", text: $input1)
TextField("", text: $input1)
Text("\(calculation)")
}
The problem is the code won't compile, i get different compile errors, for example: "Binary operator '*' cannot be applied to two 'Double?' operands".
What goes wrong?

Double(input1) returns String? because it's not guaranteed to work. e.g. Double("1abc")
We can use guard let or if let or even a nil coalescing operator ?? to handle this. But for the following example we will gracefully handle it using guard let.
struct ContentView: View {
#State var input1: String = ""
#State var input2: String = ""
var calculation : Double {
guard let m = Double(input1), let n = Double(input2) else { return 0 }
return m * n
}
var body: some View {
VStack {
TextField("", text: $input1)
TextField("", text: $input2)
Text("\(calculation)")
}
}
}
EDIT
As per your comments, there are multiple ways to show "Error" on invalid inputs, or the answer upto 2 decimal point.
For this example, lets change the result to a computed String property both these these cases, as such:
struct ContentView: View {
#State var input1: String = ""
#State var input2: String = ""
var calculation: String {
//check if both fields have text else no need for message
guard input1.isEmpty == false, input2.isEmpty == false else { return "" }
//check if both are numbers else we need to print "Error"
guard let m = Double(input1), let n = Double(input2) else { return "Error" }
let product = m * n
return String(format: "%.2f", product)
}
var body: some View {
VStack {
TextField("Enter First Number", text: $input1)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("Enter Second Number", text: $input2)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text(calculation)
}
}
}
PS: If you want to ensure only numbers can be typed then you should think about applying the .keyboardType(.decimalPad) modifier on the TextFields.

#Johankornet, you have probably figured this out by now, but the below code will work for decimal entries:
import SwiftUI
struct ContentView: View {
#State var textFieldEntry: Double = 1.0
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
var body: some View {
VStack {
TextField("title", value: $textFieldEntry, formatter: formatter).padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
To see it working, i made an entire app with this main file:
import SwiftUI
#main
struct testTextFieldWithNumericFormattingApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
formatter.decimalstyle = .decimal restricts the TextField input to decimals only. Type in something thats not a decimal, like "abc", hit return and the input is ignored. This input field can accept scientific notation numbers as input, but the TextField will display as a decimal so very small scientific notation inputs, such as 3e-7 will be displayed as zero.
#JohanKornet, you have probably found other more elegant solutions for this problem by now, but if not maybe this will help:)

Related

How to change picker based on text field input

I'm currently trying to change the data the picker will display based on the value in the series text field. I'm not getting the picker to show up, I'm not getting any errors but I'm getting this warning "Non-constant range: not an integer range" for both the ForEach lines below.
struct ConveyorTracks: View {
#State private var series = ""
#State private var selectedMaterial = 0
#State private var selectedWidth = 0
#State private var positRack = false
let materials8500 = ["HP", "LF", "Steel"]
let widths8500 = ["3.25", "4", "6"]
let materials882 = ["HP", "LF", "PS", "PSX"]
let widths882 = ["3.25", "4.5", "6","7.5", "10", "12"]
var materials: [String] {
if series == "8500" {
return materials8500
} else if series == "882" {
return materials882
} else {
return []
}
}
var widths: [String] {
if series == "8500" {
return widths8500
} else if series == "882" {
return widths882
} else {
return []
}
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Text("Series:")
TextField("Enter series", text: $series)
}.padding()
HStack {
Text("Material:")
Picker("Materials", selection: $selectedMaterial) {
ForEach(materials.indices) { index in
Text(self.materials[index])
}
}.pickerStyle(SegmentedPickerStyle())
}.padding()
HStack {
Text("Width:")
Picker("Widths", selection: $selectedWidth) {
ForEach(widths.indices) { index in
Text(self.widths[index])
}
}.pickerStyle(SegmentedPickerStyle())
}.padding()
HStack {
Text("Positive Rack:")
Toggle("", isOn: $positRack)
}.padding()
}
}
}
struct ConveyorTrack_Previews: PreviewProvider {
static var previews: some View {
ConveyorTracks()
}
}
I would like the pickers to change based on which value is input in the series text field, for both materials and width.
Perhaps pickers isn't the best choice, I am open to any suggestions.
Thanks!
ForEach(materials.indices)
Needs to be
ForEach(materials.indices, id: \.self)
Because you are not using a compile-time constant in ForEach.
In general for fixed selections like this your code can be much simpler if you make everything enums, and make the enums Identifiable. This simplified example only shows one set of materials but you could return an array of applicable materials depending on the selected series (which could also be an enum?)
enum Material: Identifiable, CaseIterable {
case hp, lf, steel
var id: Material { self }
var title: String {
... return an appropriate title
}
}
#State var material: Material
...
Picker("Material", selection: $material) {
ForEach(Material.allCases) {
Text($0.title)
}
}

SwiftUI Storing field entry as a double or integer

I'm trying to store a user's text field entry as a number so it's easier to work with in formulas. I thought I was being slick by storing it as a string, but now using the input in mathematical formulas is becoming a real pain in the neck. It should be noted that these field entries are being stored in CoreData currently as a string entity.
Here's an MRE of one of my fields:
import SwiftUI
struct EntryMRE: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var showingResults: Int? = 1
#FocusState private var isTextFieldFocused: Bool
#State var isDone = false
#State var isSaving = false //used to periodically save data
#State var saveInterval: Int = 5 //after how many seconds the data is automatically saved
//DataPoints Chemistry
#State var potassium = ""
var body: some View {
List {
Section(header: Text("🧪 Chemistry")) {
Group {
HStack {
Text("K")
+ Text("+")
.font(.system(size: 15.0))
.baselineOffset(4.0)
Spacer()
TextField("mEq/L", text: $potassium)
.focused($isTextFieldFocused)
.foregroundColor(Color(UIColor.systemBlue))
.modifier(TextFieldClearButton(text: $potassium))
.multilineTextAlignment(.trailing)
.keyboardType(.decimalPad)
if let numberValue = Double(potassium) { // Cast String to Double
if (3.5...5.5) ~= numberValue {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(UIColor.systemGreen))
}
else {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(Color(UIColor.systemRed))
}
}
}
}
}
}
}
}
While not specifically using the $potassium value, here is what I'm currently having to do for formulas:
import SwiftUI
struct ResultView: View {
#Binding var isDone: Bool
var EV: EntryView
let decimalPlaces: Int = 2
var body: some View {
List {
Section(header: Text("Formula")) {
HStack {
Text("Corrected CO2")
Spacer()
Text("\(1.5 * (Double(EV.hCo3) ?? 0) + 8, specifier: "%.\(decimalPlaces)f")")
}
If you want your #State var potassium to be of type Double you can use a different initializer for your TextField.
#State var potassium = 0.0
TextField("mEq/L", value: $potassium, format: .number)
Of course you would need to change your CoreData model to to be of type Double but I think this should have been a Double in the first place.

SwiftUI - Getting TextField to array in ForEach

The goal is to have a TextField that takes characters as they're being entered and display them below the TextField in a 'pile'. By pile I mean, each new character is displayed on top of the last one. Piling the letters up is easy enough - I use ForEach to loop over an array inside a ZStack. To get the characters in the array, I used a custom binding. It works, but the problem is that the characters get repeated each time a new character is typed. For example, if the user types CAT, first the C will appear, then CA on top of the C, then CAT on top of the CA and the C. In other words, there are six characters stacked up when I only wanted three.
struct ContentView: View {
#State private var letter = ""
#State private var letterArray = [String]()
var body: some View {
let binding = Binding<String>(
get: { self.letter },
set: { self.letter = $0
self.letterArray.append(self.letter)
})
return VStack {
TextField("Type letters and numbers", text: binding)
ZStack {
ForEach(letterArray, id: \.self) { letter in
Text(letter)
}
}
}
}
}
UPDATE:
I was making the problem a little more difficult that it was. By using an ObservableObject I was able to separate the logic from the view AND simplify the code. First I created a view model. Now, each time the user types something into a TextField, it's it's caught by didSet and converted into an array of characters. Note, I had to use map to convert from a character array to a string array because ForEach doesn't work with characters.
class ViewModel: ObservableObject {
#Published var letterArray = [String]()
#Published var letter = "" {
didSet {
letterArray = Array(letter).map { String($0) }
}
}
}
In ContentView, I only need #ObservedObject var vm = ViewModel()Then I refer to the variables using vm.letter or vm.letterArray
Uptown where I get your problem may the below code help you
I modified your code as below;
struct ContentView: View {
#State private var letter = ""
#State private var letterCounter = 0
#State private var letterArray = [String]()
var body: some View {
let binding = Binding<String>(
get: { self.letter },
set: { self.letter = $0
if self.letter.count > self.letterCounter {
if let lastLetter = self.letter.last{
self.letterArray.append(String(lastLetter))
}
}else{
_ = self.letterArray.removeLast()
}
self.letterCounter = self.letter.count
})
return VStack {
TextField("Type letters and numbers", text: binding)
VStack {
ForEach(letterArray, id: \.self) { letter in
Text(letter)
}
}
}
}
}

SwiftUI - Add values from two textfields using var or let

How to add values of two textfields in SwiftUI?
I have this code:
import SwiftUI
struct ContentView: View {
#State private var value1 = ""
#State private var value2 = ""
private var sumValues = (Int(value1) ?? 0) + (Int(value2) ?? 0)
var body: some View {
VStack {
TextField("type value 1 here", text: $value1)
.keyboardType(.numberPad)
TextField("type value 2 here", text: $value2)
.keyboardType(.numberPad)
Text("sum: \(sumValues)")
// I need to have a var or let, so I cannot use something like this:
//Text("sum: \((Int(value1) ?? 0) + (Int(value2) ?? 0))")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I am getting this error on line with private var sumValues...:
Cannot use instance member 'value1' within property initializer;
property initializers run before 'self' is available
Cannot use instance member 'value2' within property initializer;
property initializers run before 'self' is available
Use a computed-property.
private var sumValues: Int { (Int(value1) ?? 0) + (Int(value2) ?? 0) }
The possible approach is to move all this logic (and might be all other) into view model, like below - so keep engine separated of view and let standard observed dynamic property take care of view updates:
Here is simple demo. Tested with Xcode 12 / iOS 14
class CalcViewModel: ObservableObject {
#Published var value1 = "" {
didSet { update() }
}
#Published var value2 = "" {
didSet { update() }
}
#Published var sum: Int = 0
private func update() {
self.sum = (Int(value1) ?? 0) + (Int(value2) ?? 0)
}
}
struct ContentView: View {
#ObservedObject var vm = CalcViewModel()
var body: some View {
VStack {
TextField("type value 1 here", text: $vm.value1)
.keyboardType(.numberPad)
TextField("type value 2 here", text: $vm.value2)
.keyboardType(.numberPad)
Text("sum: \(vm.sum)")
}
}
}
I think you can change sumValues to a computed property:
private var sumValues: Int {
get {
(Int(value1) ?? 0) + (Int(value2) ?? 0)
}
}

How do I make a method for distance conversion?

I'm trying to create a distance conversion app to learn Swift. I'm planning to create a method to convert all values. How would I start doing that?
If there are other ways I can convert my values, tips would be appreciated. Thanks.
struct ContentView: View {
#State private var inputNumber = ""
#State private var inputUnit = 0
#State private var outputUnit = 1
#State private var convertedValue = ""
let inputUnits = ["Meters", "Kilometers", "Feet", "Yard", "Miles"]
let outputUnits = ["Meters", "Kilometers", "Feet", "Yard", "Miles"]
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter measurement", text: $inputNumber)
.keyboardType(.numberPad)
Picker("Your unit", selection: $inputUnit) {
ForEach(0 ..< inputUnits.count) {
Text("\(self.inputUnits[$0])")
}
}
}
Section(header: Text("What unit would you like to convert to?")) {
Picker("Convert to...", selection: $outputUnit) {
ForEach(0 ..< outputUnits.count) {
Text("\(self.outputUnits[$0])")
}
}
}
Section {
Button(action: {
self.calculateConversion()
}) { Text("Convert") }
Text(convertedValue)
}
}
.navigationBarTitle("How Far?")
}
}
func calculateConvertedValue() {
convertedValue = "New converted value."
}
}
I'd just make an Dictonary [String:Double] with all Units.
And just take 1 meter as the base value and for every other unit the corresponding value which converts to 1 meter.
And then in your method just divide the old value by the double value from the old unit in the dictionary and multiplied by the double value of the unit you want it in.

Resources