swiftui: navigationsplitview crashes in landscape on ipad (ios 16.2)

Hi,

i am working on an app for iphone and ipad and wanted to use NavigationSplitView. When running on an iPad in landscape orientation i am running into a crash on iOS 16.2 that i can't really understand. I followed the developer documentation and various tutorials. I am probably missing something.

In this Example you can reproduce the crash on iPad in landscaope mode like this: First select "George" and then click on his book "Big Book" (last in the list for him). Then select "Luca". Click on his first book entry ("Boring book"). At this point this crashes the whole app. I get an

Thread 1: EXC_BREAKPOINT (code=1, subcode=0x105c9fe94)

(of course no breakpoint has been set).

Any help would be greatly appreciated!!

RR

This is the code (you can paste it directly into a new project):



//
//  ContentView.swift
//  test
//


import SwiftUI

struct Member:Hashable, Identifiable {
var id: Int
var name: String
    
}

struct Book:Hashable, Identifiable {
var id: Int
var memberId: Int
var name: String
    
}

struct ContentView: View {
    
    @State private var memberID: Member.ID? // Single selection.
    @State private var bookID: Book.ID? // Single selection.
    @State private var columnVisibility = NavigationSplitViewVisibility.all
    
    
    
    var members = [
        Member(id:1, name:"George"),
        Member(id:2, name:"John"),
        Member(id:3, name:"Luca"),
    ]
    
    
    var books
    = [
        Book(id:1,memberId:1, name:"A good Book"),
        Book(id:2, memberId:1, name:"Another great Book"),
        Book(id:3, memberId: 1, name:"Very good Book"),
        Book(id:4,memberId:1, name:"Lame Book"),
        Book(id:5, memberId:1, name:"Big Book"),
        Book(id:6, memberId: 2, name:"Small Book"),
        Book(id:7,memberId:2, name:"Green Book"),
        Book(id:8, memberId:2, name:"Yellow Book"),
        Book(id:9, memberId: 3, name:"Boring Book"),
        Book(id:10, memberId: 3, name:"Best Book"),
    ]
    
    
    
    var body: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            List(members, selection: $memberID) { member in
                HStack{
                    Text("ID: \(member.id)")
                    Text("Name: \(member.name)")
                }
                
            } //List
            
        } content: {
            
            if memberID == nil {
                Text("select")
                
            } else {
                List(books.filter({ $0.memberId == memberID }), selection: $bookID) { book in
                    
                    
                    Text(book.name)
                    
                } //List
                
            } //else
            
            
        } detail: {
            
            Text("Details")
        }// detail
        .onChange(of: memberID){ _ in
            bookID = nil
        }
        
    } // body
    
    
    
    
}
 

Apparently, problem comes from here:

                List(books.filter({ $0.memberId == memberID }), selection: $bookID) { book in

The id in the List are not updated and somehow you get out of range (app crashes if you first select "Lame Book" for George.

I met similar issue here: https://developer.apple.com/forums/thread/724393

You should probably make books and members Observable.

See example here: https://stackoverflow.com/questions/68465201/why-is-my-swiftui-list-selection-not-highlighting-when-selected

Unfortunately, I could not yet make it work…

I may have finally found a way to make it work, replacing List selection by onTapGesture with a ForEach.

No more crash. But remaining issue: highlight the selected book. I mimic partially

struct Member:Hashable, Identifiable {
    var id: Int
    var name: String
}

struct Book:Hashable, Identifiable {
    var id: Int
    var memberId: Int
    var name: String
}

class MemberList: ObservableObject {
    @Published var members = [
        Member(id:1, name:"George"),
        Member(id:2, name:"John"),
        Member(id:3, name:"Luca")
    ]
}

class BookStore: ObservableObject {
    @Published var books: [Book] = [
        Book(id:1,memberId:1, name:"A good Book"),
        Book(id:2, memberId:1, name:"Another great Book"),
        Book(id:3, memberId: 1, name:"Very good Book"),
        Book(id:4,memberId:1, name:"Lame Book"),
        Book(id:5, memberId:1, name:"Big Book"),
        Book(id:6, memberId: 2, name:"Small Book"),
        Book(id:7,memberId:2, name:"Green Book"),
        Book(id:8, memberId:2, name:"Yellow Book"),
        Book(id:9, memberId: 3, name:"Boring Book"),
        Book(id:10, memberId: 3, name:"Best Book")
    ]
    
    @Published var filteredBooks: [Book] = [    // Will change
        ]
}

struct ContentView: View {
    
    @State private var memberID: Member? // Member.ID? // Single selection.
    @State private var bookID: Book? // Book.ID? // Single selection.
    @State private var columnVisibility = NavigationSplitViewVisibility.all
    @State private var selectedBookID = 0

    @StateObject var bookStore : BookStore = BookStore()
    @StateObject var memberList : MemberList = MemberList()  

    
    var body: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            List(memberList.members, id: \.self, selection: $memberID) { member in
                HStack {
                    Text("ID: \(member.id)")
                    Text("Name: \(member.name)")
                }
            } //List
            
        } content: {
            
            if memberID == nil {
                Text("select")
                
            } else {
                List {
                    ForEach(bookStore.filteredBooks, id: \.self) { book in // ForEach
                        if book.id == selectedBookID {
                            Text("\(book.name)")
                                .background(.blue)
                        } else {
                            Text(book.name)
                                .background(.clear)
                                .onTapGesture {
                                    bookID = book
                                    selectedBookID = book.id
                                }
                        }
                    }
                }
//                List(bookStore.filteredBooks, id: \.self, selection: $bookID) { book in // Selection may crash
//                    HStack {
//                        Text("ID: \(book.id)")
//                        Text(book.name)
//                    }
//                }
                
            }
            
        } detail: {
            Text("Details\n\((bookID?.name ?? ""))")
        }
        .onChange(of: memberID) { _ in
            bookID = nil
            bookStore.filteredBooks = bookStore.books.filter({ $0.memberId == memberID?.id })
        }

    } // body
    
}

And here is a way to get the correct cell background

                List {
                    ForEach(bookStore.filteredBooks, id: \.self) { book in // ForEach
                        if book.id == selectedBookID {
                            RoundedRectangle(cornerRadius: 8)
                                .fill(Color.blue)
                                .overlay(alignment: .leading) {
                                    Text(book.name)
                                        .foregroundColor(.white)
                                }

                        } else {
                            Text(book.name)
                                .foregroundColor(.black)
                                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
                                .background(.clear)
                                .onTapGesture {
                                    bookID = book
                                    selectedBookID = book.id
                                }
                        }
                    }
                }

To see Details when in Portrait:

        } detail: {
            Text("Details\n\((bookID?.name ?? ""))")
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
        }
swiftui: navigationsplitview crashes in landscape on ipad (ios 16.2)
 
 
Q