I have just started learning SwiftUI and got stuck somewhere!
I am trying to change segment styled picker datasource when changing value of another segment. But somehow it is not working as expected! Or else I might have coded something wrong. Can anyone figure it out please?
Here is my piece of code:
import SwiftUI
struct ContentView: View {
@State var selectedType = 0
@State var inputUnit = 0
@State var outputUnit = 1
let arrTypes = ["Temperature", "Length"]
var arrData: [String] {
switch self.selectedType {
case 0:
return ["Celsius", "Fahrenheit", "Kelvin"] //Temperature
case 1:
return ["meters", "kilometers", "feet", "yards", "miles"] //Length
default:
return ["Celsius", "Fahrenheit", "Kelvin"]
}
}
var body: some View {
NavigationView{
Form
{
Section(header: Text("Choose type"))
{
Picker("Convert", selection: $selectedType) {
ForEach(0 ..< 2, id: \.self)
{ i in
Text(self.arrTypes[i])
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("From"))
{
Picker("", selection: $inputUnit) {
ForEach(0 ..< arrData.count, id: \.self)
{
Text(self.arrData[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("To"))
{
Picker("", selection: $outputUnit) {
ForEach(0 ..< arrData.count, id: \.self)
{
Text(self.arrData[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
}
When I change segment from Length
back to Temperature
it merges the array somehow. I tried to debug and print the arrData
count in log, then it prints correct result but not updating the UI!
First segment selected by default:
Change segment:
Change segment back to first:
Any help or suggestion would greatly be appreciated.
Nick Polychronakis solved it in this fork: https://github.com/nickpolychronakis/100DaysOfSwiftUI/tree/master/UnitCoverter
The solution is to add .id(:identifier:) to your picker so it is unique.
Observable var:
@State var unit = 0
Main picker:
Picker("Length", selection: $unit) {
ForEach(0 ..< inputUnitTypes.count) {
Text("\(self.inputUnitTypes[$0].description)")
}
}
.pickerStyle(SegmentedPickerStyle())
One of secondary pickers which content is determined by the unit variable.
Picker("Length", selection: $inputUnit) {
ForEach(0 ..< selected.count) {
Text("\(self.selected[$0].description)")
}
}
.id(unit)
I'm not sure why SwiftUI behaves like this, seems like a bug to me (Correct me if I'm wrong). All I can suggest is to add separate pickers for temperature and length and hide those based on the current selected type. For code re-usability I've added the picker to another file.
MyCustomPicker
struct MyCustomPicker: View {
var pickerData: [String]
@Binding var binding: Int
var body: some View {
Picker("Convert", selection: $binding) {
ForEach(0 ..< pickerData.count, id: \.self)
{ i in
Text(self.pickerData[i])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
ContentView
struct ContentView: View {
@State var selectedType = 0
@State var inputTempUnit = 0
@State var outputTempUnit = 1
@State var inputLenUnit = 0
@State var outputLenUnit = 1
let arrTypes = ["Temperature", "Length"]
let tempData = ["Celsius", "Fahrenheit", "Kelvin"]
let lenData = ["meters", "kilometers", "feet", "yards", "miles"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Choose type")) {
MyCustomPicker(pickerData: arrTypes, binding: $selectedType)
}
Section(header: Text("From")) {
if selectedType == 0 {
MyCustomPicker(pickerData: tempData, binding: $inputTempUnit)
} else {
MyCustomPicker(pickerData: lenData, binding: $inputLenUnit)
}
}
Section(header: Text("To")) {
if selectedType == 0 {
MyCustomPicker(pickerData: tempData, binding: $outputTempUnit)
} else {
MyCustomPicker(pickerData: lenData, binding: $outputLenUnit)
}
}
}
}
}
}
Note: You have to use different state variables to keep track the temperature and length selection.
Combining the two earlier answers:
ContentView
...
var units: [String] {
symbols[unitType]
}
...
Section(header: Text("Unit Type")) {
UnitPicker(units: unitTypes, unit: $unitType)
}
Section(header: Text("From Unit")) {
UnitPicker(units: units, unit: $inputUnit)
.id(unitType)
}
Section(header: Text("To Unit")) {
UnitPicker(units: units, unit: $outputUnit)
.id(unitType)
}
...
UnitPicker
struct UnitPicker: View {
var units: [String]
@Binding var unit: Int
var body: some View {
Picker("", selection: $unit) {
ForEach(units.indices, id: \.self) { index in
Text(self.units[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.font(.largeTitle)
}
}
See https://github.com/hugofalkman/UnitConverter.git
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With