I am building a custom SegmentedPicker in SwiftUI where the selector adjusts its size to fit the frame of each picker item. I did it already using PreferenceKeys as inspired by this post (Inspecting the View Tree) for uniformly sized items like shown below:
I think I can simplify my implementation considerably and avoid using PreferencyKeys altogether by using a .matchedGeometryEffect(). My idea was to present a selector behind each item only when that item has been selected and sync the transition using the .matchedGeometryEffect(). Almost everything is working except for an issue where the selector will be in front of the previously selected item. I tried explicitly setting the zIndex, but it does not seem to affect the result:
The code:
struct MatchedGeometryPicker: View {
@Namespace private var animation
@Binding var selection: Int
let items: [String]
var body: some View {
HStack {
ForEach(items.indices) { index in
ZStack {
if isSelected(index) {
Color.gray.clipShape(Capsule())
.matchedGeometryEffect(id: "selector", in: animation)
.animation(.easeInOut)
.zIndex(0)
}
itemView(for: index)
.padding(7)
.zIndex(1)
}
.fixedSize()
}
}
.padding(7)
}
func itemView(for index: Int) -> some View {
Text(items[index])
.frame(minWidth: 0, maxWidth: .infinity)
.foregroundColor(isSelected(index) ? .black : .gray)
.font(.caption)
.onTapGesture { selection = index }
}
func isSelected(_ index: Int) -> Bool { selection == index }
}
And in ContentView:
struct ContentView: View {
@State private var selection = 0
let pickerItems = [ "Item 1", "Long item 2", "Item 3", "Item 4", "Long item 5"]
var body: some View {
MatchedGeometryPicker(selection: $selection, items: pickerItems)
.background(Color.gray.opacity(0.10).clipShape(Capsule()))
.padding(.horizontal, 5)
}
}
Any ideas how to fix this?


