Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How make work a Picker with an ObservedObject in SwiftUI?

Tags:

swift

swiftui

I'm trying to get a list of Datacenters from a Rest API and show them in a Picker, so the user can choose one. When I do it with a static list it works fine. However, retrieving the Datacenters dinamically seems not to work fine.

I'm using Xcode 11 (GM)

This is the Datacenter Object

struct Datacenter:Codable, Hashable, Identifiable{ 
    let id: String
    var location: String
}

This is the ObservedObject (it has the property datacenters that is an array of Datacenter objects)

@ObservedObject var datacenters_controller : DatacentersController
@State private var selectedDatacenter = 0

This was my first attempt:

Picker(selection: $selectedDatacenter, label: Text("Datacenter")) {
   ForEach(0 ..< datacenters_controller.datacenters.count) {
     Text(self.datacenters_controller.datacenters[$0].location)
   }
}

Swift complained with the following error:

ForEach<Range<Int>, Int, Text> count (4) != its initial count (0). `ForEach(_:content:)` should only be used for *constant* data. Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!

Then I switched to:

Picker(selection: $selectedDatacenter, label: Text("Datacenter")) {
   ForEach(datacenters_controller.datacenters) { datacenter in
      Text(datacenter.location)
   }
}

It "works" (no error), but the result is not the expected because although I can select a datacenter, it is not "stored", not shown in the Picker as selected.

Actual result

Picker without selection

Expected result

Picker with a datacenter selected

Any idea? What I'm doing wrong?

like image 290
jarnaez Avatar asked Oct 21 '25 19:10

jarnaez


2 Answers

Here's a working example. The key is that selectedDatacenter needs to be the same type as Datacenter.id (in this case, String).

struct ContentView: View {
    @ObservedObject var datacenters_controller = DatacentersController()
    @State private var selectedDatacenter = ""

    var body: some View {
        NavigationView {
            Form {
                Picker(selection: $selectedDatacenter, label: Text("Datacenter")) {
                    ForEach(datacenters_controller.datacenters) { datacenter in
                        Text(datacenter.location)
                    }
                }

                // Just here for demonstration
                Text("selectedDatacenter (id): \(selectedDatacenter.isEmpty ? "Nothing yet" : selectedDatacenter)")
            }
        }
    }
}

Here's the supporting code

struct Datacenter:Codable, Hashable, Identifiable{
    let id: String
    var location: String
}

class DatacentersController: ObservableObject {
    @Published var datacenters: [Datacenter] = []

    init() {
        datacenters = [
            Datacenter(id: "ABQ", location: "Albuquerque"),
            Datacenter(id: "BOS", location: "Boston"),
            Datacenter(id: "COS", location: "Colorado Springs")
        ]
    }
}
like image 120
John M. Avatar answered Oct 24 '25 10:10

John M.


I think you are missing tag on your picker:

Picker(selection: $selectedDatacenter, label: Text("Datacenter")) {
   ForEach(datacenters_controller.datacenters) {
     Text($0.location).tag($0)
   }
}

Apple docs on tag:

Sets the tag of the view, used for selecting from a list of View options.

like image 45
LuLuGaGa Avatar answered Oct 24 '25 10:10

LuLuGaGa



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!