0

Initial position: making two pickers filled from database where the second depends on the first. I followed this Picker Values from a previous picker - CoreData/SwiftUI example and it works pretty good. Only one problem: the second picker doesn't show the selected value.

    @State var courtSelected = 0
    @State var judgeSelected = 0

HStack{
    Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
        ForEach(0..<courts.count){ court in
            Text("\(courts[court].name ?? "Unknown")")
        }
    }
}
HStack {
    Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
        ForEach(Array(courts[courtSelected].courtsJudges! as! Set<Judges>), id: \.self) { judge in
            Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
        }
    }
}

Only differences:

  1. the modification from NSSet to array
  2. I had to change @Binding var judgeSelected:Int to @State, because otherwise I have to hand over the judge selected as Parameter beginning from App-Struct.

Printing the $judgeSelected inside the label demonstrates, that this var is never changed.

mihema
  • 158
  • 1
  • 10

4 Answers4

0

Your selection and presentation are differ by type, so add tag:

Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
    ForEach(0..<courts.count){ court in
        Text("\(courts[court].name ?? "Unknown")").tag(court)  // << here !!
    }
}

Second picker presents objects, so selection should also be an object, like

@State var judgeSelected: Judges? = nil

Next shows similar case so should be helpful https://stackoverflow.com/a/68815871/12299030

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Hi Asperi, thanks for your help. Unfortunally there‘s no change in behaviour in judge‘s picker, where nothing is displayed. – mihema Jun 06 '22 at 05:43
  • Obviously for second picker you have to do the same – Asperi Jun 06 '22 at 05:45
  • I did ;-), but no changes. – mihema Jun 06 '22 at 05:47
  • I think, the problem might be anywhere else (perhaps sitting in front of the computer ;-) ). When I turn the standard picker into radiobuttons, nothing can be selected at all. All judges are displayed as radiobutton with labels but are light grey. – mihema Jun 06 '22 at 06:33
0

try something like this:

    HStack{
        Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
            ForEach(0..<courts.count){ court in
                Text("\(courts[court].name ?? "Unknown")")
            }
        }
    }
    HStack {
        Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
            // -- here
            ForEach(Array(Set(courts[courtSelected].courtsJudges!)), id: \.self) { judge in  
                Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
            }
        }.id(UUID())   // <-- here
    }
}

Note also the second ForEach with Set. PS, do not use forced unwrap, ie. no ! in your code.

EDIT-1: to avoid the error with arrayLiteral, try this:

    HStack {
        Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
            if let theJudges = courts[courtSelected].courtsJudges {
                ForEach(Array(Set(theJudges)), id: \.self) { judge in
                    Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
                }
            }
        }.id(UUID())
    }

EDIT-2:

here is my test code that allows the second picker to depend on the first picker. I used both the id marker, and tag that must match the selection type.

Since you don't show your struct code for court and judge, I created some example structs for those. You will have to adjust the code to cater for your structs.

Used the id of the Judge struct in the second picker for the tag. However, there are other ways to have a Int tag, for example using array indices, such as:

struct Judge: Identifiable, Hashable {
    var id: Int
    var gender: String?
    var name: String?
    var title: String?
}

struct Court: Identifiable, Hashable {
    var id: Int
    var name: String?
    var courtsJudges: [Judge]?
}

struct ContentView: View {
    @State var courtSelected = 0
    @State var judgeSelected = 0
    
    @State var courts: [Court] = [
        Court(id: 0, name: "one",
              courtsJudges: [
                Judge(id: 0, gender: "Male", name: "name1", title: "title1"),
                Judge(id: 1, gender: "Male", name: "name2", title: "title2"),
                Judge(id: 2, gender: "Male", name: "name3", title: "title3")
              ]),
        Court(id: 1, name: "two",
              courtsJudges: [
                Judge(id: 3, gender: "Female", name: "name7", title: "title7"),
                Judge(id: 4, gender: "Female", name: "name8", title: "title8"),
                Judge(id: 5, gender: "Female", name: "name9", title: "title9")
              ])
    ]

    var body: some View {
        VStack (spacing: 77) {
            HStack{
                Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
                    ForEach(0..<courts.count) { court in
                        Text("\(courts[court].name ?? "Unknown")").tag(court)
                    }
                }
            }
            HStack {
                Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
                    if let theJudges = courts[courtSelected].courtsJudges {
                        ForEach(Array(Set(theJudges))) { judge in
                            Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
                                .tag(judge.id)
                        }
                    }
                }.id(UUID())
            }
        }.padding()
    }
}

Alternatively:

 HStack {
     Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
         if let theJudges = courts[courtSelected].courtsJudges, let arr = Array(Set(theJudges)) {
             ForEach(arr.indices, id: \.self) { index in
                 Text("\(arr[index].gender ?? "") \(arr[index].title ?? "") \(arr[index].name ?? "")")
                     .tag(index)
             }
         }
     }.id(UUID())
 }
0

First: thanks for all the great help so far.

Bringing it all together til now.

The following code creates a picker, but the selection is not shown. Changing to a radioGroup, you can't select anything.

Here's my edited code

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Courts.name, ascending: true)],
        animation: .default
    )
    private var courts: FetchedResults<Courts>
    
    @State var courtSelected = 0
    @State var judgeSelected = 0
    
    var body: some View {
        HStack{
            Picker(selection: $courtSelected, label: Text("Gericht")){
                ForEach(0..<courts.count){ court in
                    Text("\(courts[court].name ?? "Unknown")").tag(court)
                }
            }
        }
        HStack {
            Picker(selection: $judgeSelected, label: Text("Richter \(judgeSelected)")){
                ForEach(Array(courts[courtSelected].courtsJudges as! Set<Judges>), id: \.self) { judge in
                    Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")").tag(judge)
                }
            }.id(UUID())
        }
    }
}

Code above leads to no selection code above as radioButtons

The next code still leads to an error

HStack {
    Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
        if let theJudges = courts[courtSelected].courtsJudges {
            ForEach(Array(Set(theJudges)), id: \.self) { judge in
                Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
            }
        }
    }.id(UUID())
}

Code showing error

I think, the problem has to be anywhere else, because already at the start the picker doesn't show anything and the radios are grey.

Is it, because picker 2 depends on picker 1 and changes when a value in picker 1 is selected?

It's all for macOS on xcode 13.4.1

mihema
  • 158
  • 1
  • 10
  • The issue is about the type. I linked a question that will give you more detail/answer ion your question. An Int can’t be a Judge. Use the selection of the first pocket as the id for the second picker. It will get recreated when there is a change – lorem ipsum Jun 06 '22 at 10:07
0

I created a complete new project with core data, added two entities (Courts and Judges) with two attributes each (id and name).

Then I only made this view:

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Courts.name, ascending: true)],
        animation: .default
    )
    private var courts: FetchedResults<Courts>
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Judges.name, ascending: true)],
        animation: .default
    )
    private var judges: FetchedResults<Judges>
    
    @State var courtSelected = 0
    @State var judgeSelected = 0
    
    var body: some View {
        if(courts.count > 0) {
            HStack{
                Picker(selection: $courtSelected, label: Text("Gericht")){
                    ForEach(0..<courts.count){ court in
                        Text("\(courts[court].name ?? "Unknown")").tag(court)
                    }
                }
            }
            HStack {
                Picker(selection: $judgeSelected, label: Text("Richter \(judgeSelected)")){
                    ForEach(Array(courts[courtSelected].courtsJudges as! Set<Judges>), id: \.self) { judge in
                        Text("\(judge.name ?? "")").tag(judge)
                    }
                }.id(UUID())
            }
            HStack {
                Button("neue Gerichte") {
                    addItem()
                    addJudges()
                }
            }
        }
    }
    
    private func addItem() {
        for id in 0..<3 {
            let newItem = Courts(context: viewContext)
            newItem.id = UUID()
            newItem.name = "Gericht Nr. \(id)"
            
            
            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
        
    }
    
    
    private func addJudges() {
        for cd in 0..<courts.count {
            for jd  in 0..<3 {
                let newJudge = Judges(context: viewContext)
                newJudge.id = UUID()
                newJudge.name = "Richter \(jd) am Gericht \(courts[cd].name)"
                newJudge.judgesCourts = courts[cd]
                
                try? viewContext.save()
            }
        }
    }
    
}

The result: both pickers are shown, first ist ok, second shows the right judges, but they are not "selectable"

mihema
  • 158
  • 1
  • 10
  • the tag you use in the second picker, `.tag(judge)` is not correct. It must be of the same type as `judgeSelected`. The tag must match the selection type, in this case `Int` not `judge`. That is why they are not "selectable". Change either the `tag` or the `judgeSelected` so that they have the same type. – workingdog support Ukraine Jun 06 '22 at 12:07