16

I build a macOS app in swiftui

i try to create a listview where the first item is preselected. i tried it with the 'selected' state of the navigationLink but it didn't work.

Im pretty much clueless and hope you guys can help me.

The code for creating this list view looks like this.

//personList
struct PersonList: View {
    var body: some View {
          NavigationView
          {
            List(personData) { person in
                NavigationLink(destination: PersonDetail(person: person))
                {
                    PersonRow(person: person)
                }
            }.frame(minWidth: 300, maxWidth: 300)
          }
    }
}

(Other views at the bottom)

This is the normal View when i open the app. appnormalstate When i click on an item its open like this. Thats the state i want as default opening state when i render this view. appwantedstate

The Code for this view looks like this:

//PersonRow

struct PersonRow: View {

    //variables definied
    var person: Person

    var body: some View {

        HStack
        {
            person.image.resizable().frame(width:50, height:50)
                .cornerRadius(25)
                .padding(5)

            VStack (alignment: .leading)
            {
                Text(person.firstName + " " + person.lastName)
                    .fontWeight(.bold)
                    .padding(5)
                Text(person.nickname)
                    .padding(5)
            }
            Spacer()
        }
    }
}


//personDetail

struct PersonDetail: View {

    var person : Person

    var body: some View {

        VStack
        {
              HStack
              {
                VStack
                {
                    CircleImage(image: person.image)
                    Text(person.firstName + " " + person.lastName)
                      .font(.title)
                    Text("Turtle Rock")
                      .font(.subheadline)
                }
                Spacer()
                Text("Subtitle")
                  .font(.subheadline)
            }
            Spacer()
        }
        .padding()
    }
}

Thanks in advance!

lvollmer
  • 1,418
  • 2
  • 13
  • 32

4 Answers4

13

working example. See how selection is initialized

import SwiftUI

struct Detail: View {
    let i: Int
    var body: some View {
        Text("\(self.i)").font(.system(size: 150)).frame(maxWidth: .infinity)
    }
}


struct ContentView: View {
    
    @State var selection: Int?
    var body: some View {
        
        HStack(alignment: .top) {
            NavigationView {
                List {
                    ForEach(0 ..< 10) { (i) in
                        
                        NavigationLink(destination: Detail(i: i), tag: i, selection: self.$selection) {
                            VStack {
                                Text("Row \(i)")
                                Divider()
                            }
                        }
                        
                    }.onAppear {
                        if self.selection != nil {
                            self.selection = 0
                        }
                    }
                }.frame(width: 100)
            }
        }.background(Color.init(NSColor.controlBackgroundColor))
    }
}

screenshot enter image description here

Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
user3441734
  • 16,722
  • 2
  • 40
  • 59
12

You can define a binding to the selected row and used a List reading this selection. You then initialise the selection to the first person in your person array.

Note that on macOS you do not use NavigationLink, instead you conditionally show the detail view with an if statement inside your NavigationView.

If person is not Identifiable you should add an id: \.self in the loop. This ressembles to:

struct PersonList: View {
  @Binding var selectedPerson: Person?

  var body: some View {
    List(persons, id: \.self, selection: $selectedPerson) { person in // persons is an array of persons
      PersonRow(person: person).tag(person)
    }
  }
}

Then in your main window:

struct ContentView: View {
  // First cell will be highlighted and selected
  @State private var selectedPerson: Person? = person[0]

  var body: some View {
    NavigationView {
      PersonList(selectedPerson: $selectedPerson)

      if selectedPerson != nil {
        PersonDetail(person: person!)
      }
    }
  }
}

Your struct person should be Hashable in order to be tagged in the list. If your type is simple enough, adding Hashable conformance should be sufficient:

struct Person: Hashable {
  var name: String
  // ...
}

There is a nice tutorial using the same principle here if you want a more complete example.

Louis Lac
  • 5,298
  • 1
  • 21
  • 36
  • 1
    Hey thank you. I tried it like this but it isnt working... is there a debug function etc. im pretty new to swiftui sorry.. this is my code now https://pastebin.com/xcSRMb1x – lvollmer Feb 09 '20 at 21:34
  • 1
    Do not use NavigationLink on macOS, I edit my answer with details on how to do this properly. – Louis Lac Feb 09 '20 at 22:15
  • thank you! but line 8 of the contentView gives me this... : "Unable to infer complex closure return type; add explicit type to disambiguate" – lvollmer Feb 09 '20 at 22:46
  • This is a cryptic error, I forgo to mention two things: `Person` should be Hashable and Identifiable. I add detail in my answer. – Louis Lac Feb 09 '20 at 22:59
  • thanks for your answer. My struct looks like this : https://pastebin.com/01ESPWKj – lvollmer Feb 09 '20 at 23:08
  • This should work at the condition that all ids are different obviously. – Louis Lac Feb 09 '20 at 23:09
  • hmm.. all ids are different but it looks like this now.. https://gyazo.com/742b6ae46d360cefd93e0376b4c9432c – lvollmer Feb 09 '20 at 23:15
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207511/discussion-between-louis-lac-and-lvollmer). – Louis Lac Feb 09 '20 at 23:17
  • 1
    The error is clear: `person` variable does not exist, the variable name is `selectedPerson`. – Louis Lac Feb 09 '20 at 23:18
  • why not use NavigationLink on macOS? – Bijoy Thangaraj Mar 03 '20 at 13:38
  • I don't know, SwiftUI is a young framework that can still evolve and solve inconsistencies. I guess that on macOS the root view and child view can be rendered on the same window side by side contrary to iOS where views are pushed on each other instead, effectively rendering a different window. – Louis Lac Mar 03 '20 at 21:24
  • I thought list selection binding is only for edit mode. – malhal Jun 25 '20 at 15:02
6

Thanks to this discussion, as a MacOS Beginner, I managed a very basic NavigationView with a list containing two NavigationLinks to choose between two views. I made it very basic to better understand. It might help other beginners. At start up it will be the first view that will be displayed. Just modify in ContentView.swift, self.selection = 0 by self.selection = 1 to start with the second view.

FirstView.swift

import SwiftUI

struct FirstView: View {

  var body: some View {
    Text("(1) Hello, I am the first view")
      .frame(maxWidth: .infinity, maxHeight: .infinity)
  }
}

struct FirstView_Previews: PreviewProvider {
  static var previews: some View {
    FirstView()
  }
}

SecondView.swift

import SwiftUI

struct SecondView: View {
  var body: some View {
    Text("(2) Hello, I am the second View")
      .frame(maxWidth: .infinity, maxHeight: .infinity)
  }
}

struct SecondView_Previews: PreviewProvider {
  static var previews: some View {
    SecondView()
  }
}

ContentView.swift

import SwiftUI

struct ContentView: View {

  @State var selection: Int?

  var body: some View {

    HStack() {

      NavigationView {
        List () {
          NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
            Text("Click Me To Display The First View")
          } // End Navigation Link

          NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
            Text("Click Me To Display The Second View")
          } // End Navigation Link

        } // End list
          .frame(minWidth: 350, maxWidth: 350)
        .onAppear {
            self.selection = 0
        }

      } // End NavigationView
        .listStyle(SidebarListStyle())
        .frame(maxWidth: .infinity, maxHeight: .infinity)

    } // End HStack
  } // End some View
} // End ContentView

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Result:

enter image description here

Wild8x
  • 459
  • 4
  • 13
3
import SwiftUI


struct User: Identifiable {
    let id: Int
    let name: String
}

struct ContentView: View {
    
    @State private var users: [User] = (1...10).map { User(id: $0, name: "user \($0)")}
    @State private var selection: User.ID?
    
    var body: some View {
        NavigationView {
            List(users) { user in
                NavigationLink(tag: user.id, selection: $selection) {
                    Text("\(user.name)'s DetailView")
                } label: {
                    Text(user.name)
                }
            }
            
            Text("Select one")
        }
        .onAppear {
            if let selection = users.first?.ID {
                self.selection = selection
            }
        }
    }
}

You can use make the default selection using onAppear (see above).

radley
  • 3,212
  • 1
  • 22
  • 18
sugimoto
  • 40
  • 1
  • 1