How would I go around making a picker for units? - ios

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

Related

SwiftUI: detecting when #State variable has change in their #Binding

I'm trying to figure how can change the value from different View. Here is the implementation of my main view:
import SwiftUI
import Combine
struct ContentView: View {
#State private var isPresented: Bool = false
#State private var textToProces = "" {
didSet{
print("text: oldValue=\(oldValue) newValue=\(textToProces)")
}
}
var body: some View {
ZStack{
VStack{
Button("Show Alert"){
self.isPresented = true
}.background(Color.blue)
}
ItemsAlertView(isShown: $isPresented, textToProcess: $textToProces)
}
}
}
On this view I'm trying to change the textToProces variable:
struct AnotherView: View {
#Binding var isShown: Bool
#Binding var textToProcess: String
var title: String = "Add Item"
let screenSize = UIScreen.main.bounds
var body: some View {
VStack {
Button(action: {
self.textToProcess = "New text"
self.isShown = false
}, label: {
Text("dissmis")
})
Text(self.textToProcess)
}
.background(Color.red)
.offset(y: isShown ? 0 : screenSize.height)
}
}
When I change the value on this line self.textToProcess = "New text" the textToProcess in the main view never gets the notification of the change. What I can I do to get the notification of the change in the main view any of you knows?
I'll really appreciate your help
You have to use the onChange modifier to track changes to textToProces.
import SwiftUI
import Combine
struct ContentView: View {
#State private var isPresented: Bool = false
#State private var textToProces = ""
var body: some View {
ZStack{
VStack{
Button("Show Alert"){
self.isPresented = true
}.background(Color.blue)
}
ItemsAlertView(isShown: $isPresented, textToProcess: $textToProces)
}
.onChange(of: textToProces) { value in
print("text: \(value)")
}
}
}

Secondary Picker's 'ForEach' gives me Fatal Error: Index Out of Range

I get this error every time I launch a secondary picker.
The first picker works okay.
However when I switch pickers & scroll, I get the following:
Here is my entire code (written as a test of this problem):
import SwiftUI
struct ContentView: View {
#State private var selectedItem = 0
#State private var isMainPickerHidden = false
#State private var isSecondaryPickerHidden = true
var colors = ["Red", "Green", "Blue", "Tartan"]
var sizes = ["Tiny", "Small", "Medium", "Large", "Super Size"]
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
ZStack {
VStack {
Picker(selection: $selectedItem, label: Text("Please choose a color")) {
ForEach(colors.indices, id: \.self) {
Text(self.colors[$0])
}
}.hiddenConditionally(isHidden: isMainPickerHidden)
Text("You selected: \(colors[selectedItem])")
.hiddenConditionally(isHidden: isMainPickerHidden)
}
VStack {
Picker(selection: $selectedItem, label: Text("Please choose a size")) {
ForEach(sizes.indices, id: \.self) {
Text(self.sizes[$0])
}
}.hiddenConditionally(isHidden: isSecondaryPickerHidden)
Text("You selected: \(sizes[selectedItem])")
.hiddenConditionally(isHidden: isSecondaryPickerHidden)
Spacer()
Button(action: {
isSecondaryPickerHidden = !isSecondaryPickerHidden
isMainPickerHidden = !isMainPickerHidden
}) {
Text("Switch Pickers")
}.padding()
}
}
}
}
}
// =========================================================================================================
extension View {
func hiddenConditionally(isHidden: Bool) -> some View {
isHidden ? AnyView(hidden()) : AnyView(self)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
What is the correct syntax for the ForEach{} to avoid this problem?
This is because you use the same selectedItem for both pickers.
If in the second picker you select the last item (index 4) and then you switch to the first picker (max index = 3), then in this line:
Text("You selected: \(colors[selectedItem])")
you'll try accessing the index which is out of range.
To fix this you can use a separate #State variable for each picker:
struct ContentView: View {
#State private var selectedColorIndex = 0
#State private var selectedSizeIndex = 0
Picker(selection: $selectedColorIndex, label: Text("Please choose a color")) {
Picker(selection: $selectedSizeIndex, label: Text("Please choose a size")) {

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

SwiftUI Setting Initial Picker Value From CoreData

I am pulling my hair out trying to figure out how to get my picker to show the already stored CoreData value. I want it to show on the right side of the picker as if the user just selected it. I have tried adding self.updatedItemAttribute = self.editItem.attribute ?? "" prior to the picker to set the initial value but that does not build. I also tried defining it in #State (e.g. #State var updatedItemAttribute: String = self.editItem.attribute) but that does not build either. If I add a TextField prior to the picker it will set the value, but I do not want to have a TextField with the value just to get it to set. Any ideas on how I get updatedItemAttribute set to editItem.attribute just prior to the picker? Thanks.
import CoreData
import SwiftUI
struct EditItemView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
#ObservedObject var editItem: Item
#State var updatedItemName: String = ""
#State var updatedItemAttribute: String = ""
let attributes = ["Red", "Purple", "Yellow", "Gold"]
var body: some View {
NavigationView {
Form {
Section {
TextField("Name of item", text: $updatedItemName)
.onAppear {
self.updatedItemName = self.editItem.name ?? ""
}
Picker("Test attribute", selection: self.$updatedItemAttribute) {
ForEach(attributes, id: \.self) {
Text($0)
.onAppear {
self.updatedItemAttribute = self.editItem.attribute ?? ""
}
}
}
}
...
You have to this in init as shown below
struct EditItemView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
#ObservedObject var editItem: Item
#State private var updatedItemName: String
#State private var updatedItemAttribute: String
init(editItem item: Item) { // << updated here
self.editItem = item
self._updatedItemName = State<String>(initialValue: item.name ?? "")
self._updatedItemAttribute = State<String>(initialValue: item.attribute ?? "")
}
let attributes = ["Red", "Purple", "Yellow", "Gold"]
var body: some View {
NavigationView {
Form {
Section {
TextField("Name of item", text: $updatedItemName)
Picker("Test attribute", selection: self.$updatedItemAttribute) {
ForEach(attributes, id: \.self) {
Text($0)
}
}
}
}
}
}
}

Resources