NavigationSplitView and Lists in Child views

I'm trying to make a three column Split View where the sidebar determines the type of content, so I was hoping to have separate views drive the Lists for each content type, but I'm getting an issue where the selections get out of sync / don't highlight. For simplicity, I tested using a sidebar/detail Split View, as follows:

struct StringItem : Identifiable, Hashable {
	let id = UUID()
	let item : String
}


struct SimpleSplitView: View {
	@State private var selString : StringItem?
	let content = [StringItem(item: "Here"), StringItem(item: "There"), StringItem(item: "Everywhere") ]

	var body: some View {
		NavigationSplitView {
			// This
			List(content, selection: $selString) { c in
				NavigationLink(value: c) {
					Text(c.item)
				}
			}
			// or this
			ContentStringX(selection: $selString)
		} detail: {
			if let s = selString {
				DetailStringX(item: s)
			} else {
				Text("Empty")
			}
		}
		
	}
}

struct ContentStringX: View {
	
	let content = [StringItem(item: "Here"), StringItem(item: "There"), StringItem(item: "Everywhere") ]
	
	@Binding var selection : StringItem?
	
	var body: some View {
		
		List(content, selection: $selection) { c in
			NavigationLink(value: c) {
				Text(c.item)
			}
		}
	}
}

struct DetailStringX: View {
	var item : StringItem
	
	var body: some View {
		VStack{
			Spacer()
			Text("Detail String " + item.item)
			Spacer()
		}
	}
}

Ignore for a moment that my sidebar has two sections, the behavior is the same whether "This" or "or this" are used alone. You can see the highlighted selection of "There" from the upper sidebar selection and the corresponding detail. If I select any of the items in the lower sidebar part (from the embedded child view ContentStringX the detail is properly passed, but the selection isn't highlighted. Is it ok to pass the binding from the parent to the child to the list?

Second curiosity, is if I had a simpler content array of just Strings and used id:.self, then the selection works from within the nested child (other problems happen with uniqueness).

Accepted Reply

Hi,

Try making your content variables be @State private var content instead.

This code updated correctly for me

struct ContentView: View {
    @State private var selString : StringItem?
    @State private var content = [StringItem(item: "Here"), StringItem(item: "There"), StringItem(item: "Everywhere") ]

    var body: some View {
        NavigationSplitView {
            // This
            List(content, selection: $selString) { c in
                NavigationLink(value: c) {
                    Text(c.item)
                }
            }
            // or this
            ContentStringX( selection: $selString)
        } detail: {
            if let s = selString {
                DetailStringX(item: s)
            } else {
                Text("Empty")
            }
        }
        
    }
}

struct ContentStringX: View {
    
    @State private var content : [StringItem] = [StringItem(item: "Here2"), StringItem(item: "There2"), StringItem(item: "Everywhere2") ]
    
    @Binding var selection : StringItem?
    
    var body: some View {
        
        List(content, selection: $selection) { c in
            NavigationLink(value: c) {
                Text(c.item)
            }
        }
    }
}

struct DetailStringX: View {
    var item : StringItem
    
    var body: some View {
        VStack{
            Spacer()
            Text("Detail String " + item.item)
            Spacer()
        }
    }
}
  • Thanks! Any thoughts as to why? I guess the content array was getting lost on each refresh?

Add a Comment

Replies

As an experiment I tried a private state variable for the selection. Selections are highlighted (but the detail is broken), but as soon as I hook up the onChange, the detail works but the selections fail

struct ContentStringX: View {
	
	let content = [StringItem(item: "Here"), StringItem(item: "There"), StringItem(item: "Everywhere") ]
	
	@Binding var selection : StringItem?
	@State private var selPriv : StringItem?
	
	var body: some View {
		
		List(content, selection: $selPriv) { c in
			NavigationLink(value: c) {
				Text(c.item)
			}
		}
		.onChange(of: selPriv) { new in
			selection = selPriv
		}
	}
}

Hi,

Try making your content variables be @State private var content instead.

This code updated correctly for me

struct ContentView: View {
    @State private var selString : StringItem?
    @State private var content = [StringItem(item: "Here"), StringItem(item: "There"), StringItem(item: "Everywhere") ]

    var body: some View {
        NavigationSplitView {
            // This
            List(content, selection: $selString) { c in
                NavigationLink(value: c) {
                    Text(c.item)
                }
            }
            // or this
            ContentStringX( selection: $selString)
        } detail: {
            if let s = selString {
                DetailStringX(item: s)
            } else {
                Text("Empty")
            }
        }
        
    }
}

struct ContentStringX: View {
    
    @State private var content : [StringItem] = [StringItem(item: "Here2"), StringItem(item: "There2"), StringItem(item: "Everywhere2") ]
    
    @Binding var selection : StringItem?
    
    var body: some View {
        
        List(content, selection: $selection) { c in
            NavigationLink(value: c) {
                Text(c.item)
            }
        }
    }
}

struct DetailStringX: View {
    var item : StringItem
    
    var body: some View {
        VStack{
            Spacer()
            Text("Detail String " + item.item)
            Spacer()
        }
    }
}
  • Thanks! Any thoughts as to why? I guess the content array was getting lost on each refresh?

Add a Comment

And additionally, in the case without selPriv, I end up getting this warning on device when the selection doesn't work:

2023-09-14 10:44:45.305377-0700 NavSplitView[20843:16029318] [Assert] Collection view focus state got out of sync. Expected <SwiftUI.ListCollectionViewCell: 0x13d050a00; baseClass = UICollectionViewListCell; frame = (116 44; 288 44); layer = <CALayer: 0x600002305540>> to be the current managed subview but found <SwiftUI.ListCollectionViewCell: 0x14d01a000; baseClass = UICollectionViewListCell; frame = (116 44; 288 44); alpha = 0; layer = <CALayer: 0x6000023fa520>>.

It doesn't happen when I use the private selection case (but the selection still doesn't highlight).