UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Saving list item selection on main view change

Forums > SwiftUI

In my MacOS app MainView a define a NavigationView for my Students and Scores CoreData items as follows:

struct MainView: View {

var dataTypes = ["Students", "Repertoire"]
var dataIcons = ["person.2.fill", "music.note.list"]

  @State private var selectedData = 0

  var body: some View {

    NavigationView {
      List(0..<2, selection: $selectedData) { selection in
        Label(dataTypes[selection], systemImage: dataIcons[selection])
      }

      switch selectedData {
      case 0: StudentsView()
      case 1: WorksView()
      default: StudentsView()
      }

My StudentsView, for example, is as follows:

struct StudentsView: View {
   var body: some View {
      FilteredStudentsView()

And its correspondant FilteredStudentsView:

struct FilteredStudentsView: View {
@FetchRequest var studentsFetchRequest: FetchedResults<Student>

   var body: some View {
      List(studentsFetchRequest, id: \.id) { student in
          NavigationLink(destination: StudentDetailView(student: student)) {
            StudentCellView(student: student)
          }

As you can see, a typical navigationView-List outline.

I'd like to keep selected item on StudentsView when I change to Works on MainView, and back again (and viceversa). Now doing so I find an empty detailed view until I select –again– one item from the list.

I've tried with @Bindings and so, but haven´t found the correct way to make it work.

Any suggestions? Thanks you all in advance.

2      

Hi,

You should be able to do something like i did below, hopefully it works for you.

struct FilteredStudentsView: View {
    @AppStorage("lastSelection") var lastSelection: Int = 0
    @State var students = [Student(), Student(), Student(), Student(), Student()]
    @State var selection: UUID?

    var body: some View {
        List(students, id: \.id, selection: $selection) { student in
            NavigationLink(destination: StudentDetailView(student: student)) {
                VStack {
                    Text("\(student.id)")
                    Text(student.name)
                }

            }
            onChange(of: selection) { newValue in
                lastSelection = students.firstIndex(where: {$0.id == newValue}) ?? students.count
            }
            .onAppear {
                if lastSelection != students.count {
                    selection = students[lastSelection].id
                }
            }
        }
    }
}

2      

Thank you! Two comments about this.

Solution suggested above by @Hectorcrdna really doesn't. It throws no error at all, but doesn't save/reveal last selected student after changing to another view.

I tryed a very similar solution as suggested above by @Hectorcrdna, but with uuidString property os Students and .first property of Students array. This works! and both saves last selected item, and shows it when coming back to the view:

struct FilteredStudentsView: View {

@AppStorage("selectedStudentId") var selectedStudentId = ""
@State private var selectedStudent: Student?

  var body: some View {

  List(students, id: \.self, selection: $selectedStudent) { student in
          NavigationLink(destination: StudentDetailView(student: student)) {
            StudentCellView(student: student)
          }

          .onChange(of: selectedStudent, perform: { selection in
            selectedStudentId = selection?.id?.uuidString ?? ""
          })

          .onAppear{
            if studentsFetchRequest.first != nil {
              selectedStudent = students.first { $0.id?.uuidString == selectedStudentId } // <-- Throws an error
            }
          }

BUT now line marked throws an error on console, on run time, and app doesn't get displayed:

Ignoring exception: Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.

I supposse it's due to "forcing" the "instalation on the view" of that "constraint". That is: selecting the Student which holds that ID. I know the sentence "Does the constraint reference something from outside the subtree of the view?" is the clue to solve this issue, but I can't figure out exactly how.

Any further suggestions? Thanks again!

2      

Where is students declared? I can't see it in this view and perhaps to this the error message is referring to.

2      

Students is not really an array. It's:

@FetchRequest var studentsFetchRequest: FetchedResults<Student>

Declared on this same view (I've modified references to clarify). This is due to the necesitty of initialiting my request each time with filterings:

init(query: String, marked: Bool, orderedBy: String) {
    let queryPredicate = NSPredicate(format: "(name CONTAINS[cd] %@) || (relatedInstrument.name CONTAINS[cd] %@) || (relatedTutor.name CONTAINS[cd] %@)", query, query, query)
    let markedPredicate = NSPredicate(format: "marked = %@", marked as NSNumber)
    let compoundPredicate = NSCompoundPredicate(type: .and, subpredicates: [queryPredicate, markedPredicate])

    let sortDescriptor: NSSortDescriptor
    switch orderedBy {
    case "student":
      sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    case "instrument":
      sortDescriptor = NSSortDescriptor(key: "relatedInstrument.name", ascending: true)
    case "course":
      sortDescriptor = NSSortDescriptor(key: "relatedCourse.level", ascending: true)
    case "tutor":
      sortDescriptor = NSSortDescriptor(key: "relatedTutor.name", ascending: true)
    default:
      sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    }

    switch marked {
    case true:
      if query != "" {
        _studentsFetchRequest = FetchRequest<Student>(sortDescriptors: [sortDescriptor], predicate: compoundPredicate)
      } else {
        _studentsFetchRequest = FetchRequest<Student>(sortDescriptors: [sortDescriptor], predicate: markedPredicate)
      }
    case false:
      if query != "" {
        _studentsFetchRequest = FetchRequest<Student>(sortDescriptors:[sortDescriptor], predicate: queryPredicate)
      } else {
        _studentsFetchRequest = FetchRequest<Student>(sortDescriptors: [sortDescriptor])
      }
    }
  }

Could it interfere with this? In fact, previously selected student could not be been displayed at all due to filterings change... mmm...

2      

Solved! :-)

Just transforming fetchRequest to an array solved the problem:

selectedStudent = Array(studentsFetchRequest).first { $0.id?.uuidString == selectedStudentId }

Now everything working ok!

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.