Reputation: 125
Hello I’m really new to SwiftUI and especially converting uints. I’m trying to convert inches
to feet
and then use the MeasurementFormatter()
to display the value as feet
rather than a decimal. For some reason, I keep getting an error in my code when trying to assign my output value the string from the formatter
. Would love any suggestions anyone has.
Func Code:
func convertToFeet() {
let formatter = MeasurementFormatter()
var distanceInFeet = Measurement(value: Double(inputValue) ?? 0, unit: UnitLength.inches)
distanceInFeet.convert(to: UnitLength.feet)
//formatter.unitStyle = MeasurementFormatter.UnitStyle.long
formatter.string(from: distanceInFeet) // 3,626.81 miles
outputValue = formatter.description
}
All Code:
//
// ContentView.swift
// AC Converstion
//
// Created by Luke Jamison on 11/7/21.
//
import SwiftUI
import Foundation
struct ContentView: View {
@State private var inputValue = ""
@State private var outputValue = ""
@State var value: Double = 0
@State var length: Measurement = .init(value: 1, unit: UnitLength.inches)
private var massFormatter = MeasurementFormatter()
var body: some View {
NavigationView {
VStack {
Spacer()
Text("\(outputValue)").font(.title2)
Form {
Section(header: Text("Inches to Feet")) {
TextField("Enter Inches", text: $inputValue).keyboardType(.decimalPad)
Button(action: {
self.convertToFeet()
}, label: {
Label("Convert", systemImage: "car")
})
}
}.navigationTitle("Convert")
}
}
}
func convertToFeet() {
let formatter = MeasurementFormatter()
var distanceInFeet = Measurement(value: Double(inputValue) ?? 0, unit: UnitLength.inches)
distanceInFeet.convert(to: UnitLength.feet)
//formatter.unitStyle = MeasurementFormatter.UnitStyle.long
formatter.string(from: distanceInFeet) // 3,626.81 miles
outputValue = formatter.description
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Upvotes: 1
Views: 2200
Reputation: 29383
The great thing about Measurement
is that both the value
and the unit
are always together and you don't have to disconnect them.
You can eliminate the multiple sources of truth by maximizing generics and SwiftUI's features.
import SwiftUI
struct MeasurementEditorParentView: View {
@State private var measurement: Measurement = .init(value: 200, unit: UnitLength.inches)
var body: some View {
VStack {
//View non-localized Measurement
Text(measurement
.formatted(.measurement(
width: .abbreviated,
usage: .asProvided, //Show the non-localized measurement
numberFormatStyle: .number
.precision(.fractionLength(0...2))
))
)
//Measurement Editor
HStack {
MeasurementEditorView(measurement: $measurement, options: [.feet, .inches])
}.fixedSize()
}
}
}
If you pick "general" instead of asProvided
it will show units that "seem" appropriate for the locale. Such as inches for something small, feet for medium or miles for something long.
Text(measurement
.formatted(.measurement(
width: .abbreviated,
usage: .general,
numberFormatStyle: .number
.precision(.fractionLength(0...2))
))
)
You can also pick usages like person
or personHeight
to show appropriate units with context.
The MeasurementEditorView
is as follow, you can just add this code to a swift
file and use it as above.
struct MeasurementEditorView<U: Dimension>: View {
@Binding var measurement: Measurement<U>
///Options for the Picker
let options: [U]
/// two-way connection for the measurement's value
var value: Binding<Double> {
.init {
measurement.value
} set: { newValue in
measurement = .init(value: newValue, unit: measurement.unit)
}
}
/// two-way connection for the measurement's unit
var unit: Binding<U> {
.init {
measurement.unit
} set: { newValue in
measurement = Measurement(value: measurement.value, unit: newValue)
}
}
/// options + current selection if not included
var adjOptions: [U] {
if options.contains(measurement.unit) {
return options
} else {
return options + [measurement.unit]
}
}
var body: some View {
TextField("Enter value", value: value, format: .number).textFieldStyle(.roundedBorder)
Picker("Select unit", selection: unit) {
ForEach(adjOptions, id:\.symbol) { u in
Text(u.symbol).tag(u as U)
}
}
}
}
Upvotes: 0
Reputation: 114920
In general the description
property in Swift should only be used for debug purposes. Its value isn't guaranteed to be consistent over different versions of a particular class.
The correct way to get a string value from a measurement formatter is to call the string(from:)
function as you are doing. This function returns a string. You aren't doing anything with the string value that is returned which is what the compiler is warning you about.
Rather than relying on a side-effects, I would change your function to accept an input parameter and return a value.
You will also need to set the formatter's unitOptions
property to .providedUnit
to ensure you get output in feet; If you don't then you will get a locale-specific output (ie kilometres in metric locales)
struct ContentView: View {
@State private var inputValue = ""
@State private var outputValue = ""
@State var value: Double = 0
@State var length: Measurement = .init(value: 1, unit: UnitLength.inches)
private var massFormatter = MeasurementFormatter()
var body: some View {
NavigationView {
VStack {
Spacer()
Text("\(outputValue)").font(.title2)
Form {
Section(header: Text("Inches to Feet")) {
TextField("Enter Inches", text: $inputValue).keyboardType(.decimalPad)
Button(action: {
self.outputValue = self.convertToFeet(inches: self.inputValue)
}, label: {
Label("Convert", systemImage: "car")
})
}
}.navigationTitle("Convert")
}
}
}
func convertToFeet(inches: String)-> String {
let formatter = MeasurementFormatter()
var distanceInFeet = Measurement(value: Double(inches) ?? 0, unit: UnitLength.inches)
distanceInFeet.convert(to: UnitLength.feet)
formatter.unitOptions = .providedUnit
return formatter.string(from: distanceInFeet)
}
}
Upvotes: 2