1

in a book club app I'm trying to write, I have a NavigationSplitView which displays a list of readers in a selectable List that gives the user access to each reader's comments on a given book. At the top of the list is a button "Book" that takes the user back to the book's main page:

struct ProjectView: View
{
    @State private var readers: [Reader]
    @State private var selectedReader: Reader?
    
    init(readers: [Reader])
    {
        self.readers = readers
    }
    
    var body: some View
    {
        NavigationSplitView
        {
                List(selection: self.$selectedReader)
                {
                    Button("Book")
                    {
                        self.selectedReader = nil
                    }
                    
                    Divider()
                                        
                    ForEach (self.readers, id: \.self)
                    {
                        reader in Text(reader.name)
                    }
                }
        }
        detail:
        {
            if let selectedReader = self.selectedReader
            {
                ReaderView(reader: selectedReader)
            }
            else
            {
                BookView()
            }
        }
        #if os(macOS)
        .navigationSplitViewColumnWidth(min: 180, ideal: 200)
        #endif
        .navigationTitle(self.project.name)
    }
}

I'm trying to make it so that "Book" will select / highlight just as the reader names in the reader list do, you can see what I mean in this video:

https://youtube.com/shorts/__IA890FxG0?feature=share

Does anyone know how to accomplish this? Or is it even possible?

2
  • Note, you should not initialise self.readers = readers, it should be _readers = State(initialValue: readers), or use let readers: [Reader]. If you need to change readers then use a @Binding. Also, you should not use ForEach (self.readers, id: \.self), make the struct Reader Identifiable. How do you declare selectedReader? Note, Above the list of readers is a home button "Book" ... no, the button is at the top of the list, not above it. Commented Jul 25, 2024 at 1:04
  • @workingdogsupportUkraine Thanks for the tips. Forgot to add in the declaration for selectedReader, changed it. Commented Jul 25, 2024 at 2:06

1 Answer 1

4

You should use a ReaderOrBook?, since the selection can be either a reader, or "Book", or nothing is selected at all (nil).

enum ReaderOrBook: Hashable {
    case book
    case reader(Reader)
    
    // convenient property to convert a ReaderOrBook to a Reader 
    var asReader: Reader? {
        if case let .reader(r) = self {
            return r
        }
        return nil
    }
}

Then you just need to tag the list rows appropriately.

struct ProjectView: View {
    // this should not be a @State since you want callers to pass in an array of readers
    // if you want to mutate this in ProjectView, make it a @Binding instead
    let readers: [Reader]
    @State private var selectedReaderOrBook: ReaderOrBook?
    
    var body: some View {
        NavigationSplitView {
            List(selection: self.$selectedReaderOrBook) {
                Text("Book").tag(ReaderOrBook.book)
                
                Divider()
                
                ForEach (self.readers, id: \.self) { reader in
                    Text(reader.name)
                        .tag(ReaderOrBook.reader(reader))
                }
            }
        } detail: {
            // ...
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Wow that is such a great solution, I just implemented it and it worked perfectly. Thank you so much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.