How do I make a method for distance conversion? - ios

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.

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

Problem with how to implement distance conversion

I'm trying to make a basic distance conversion app, and my solution is to convert the user input to meters first before converting it to their desired unit. However, I don't know how to implement it. How would I go about that?
struct ContentView: View {
#State private var inputNumber = ""
#State private var inputUnit = 0
#State private var outputUnit = 1
let inputUnits = ["Meters", "Kilometers", "Feet", "Yard", "Miles"]
let outputUnits = ["Meters", "Kilometers", "Feet", "Yard", "Miles"]
var conversions: Double {
//conversions computed here
}
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 {
Text("\(meterToKM)")
}
}
.navigationBarTitle("How Far?")
}
}
}
You can do the conversion in a method, say calculateConvertedValue and invoke it in a button within your section and make changes to an #State property say convertedValue which then displays the final value in your Text View component. Here's an example:
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."
}
}

How would I go around making a picker for units?

I'm new at Swift and I'm looking to make a unit converter app to test my skills. Here's my attempt at making a picker:
struct ContentView: View {
#State private var inputNumber = ""
#State private var inputUnit = 2
#State private var outputUnit = ""
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(.decimalPad)
Picker("Your unit", selection: $inputUnit) {
ForEach(0 ..< inputUnits.count) {
Text("\(self.inputUnits)")
}
You need to use the parameter to the closure, which is an index
Picker(selection: $inputUnit, label: Text("Your unit")) {
ForEach(0 ..< inputUnits.count) {
Text("\(self.inputUnits[$0])") //<- $0 is the index for your array
}
}

Read and make calculations with textfield input SwiftUI

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

Resources