swiftuimatchedgeometryeffect

SwiftUI: Creating a custom segmented control using .matchedGeometry()


I am currently trying to build some kind of custom segmented control element. My code currently looks like this:

@AppStorage("selectedcountry") private var selectedCountry: Country = .france
@Namespace private var animation

HStack {
    ForEach(Country.allCases, id: \.self) { country in
        Button {
            withAnimation {
                selectedCountry = country
            }
        } label: {
            Text(country.flag)
                .padding()
        }
        .frame(maxWidth: .infinity)
        .background {
            if selectedCountry == country {
                RoundedRectangle(cornerRadius: 10)
                    .fill(Color.accentColor)
                    .matchedGeometryEffect(id: "country", in: animation)
            }
        }
    }
}

The "RoundedRectangle(cornerRadius: 10)" element is supposed to slide around like in the regular iOS segmented controls. But somehow I just don't get the matched geometry effect to work.

Any advice that you could give me?


Solution

  • Here's how to get it working:

    HStack {
        ForEach(Country.allCases, id: \.self) { country in
            Button {
                withAnimation {
                    selectedCountry = country
                }
            } label: {
                Text(country.flag)
                    .padding()
            }
            .matchedGeometryEffect(id: country, in: animation, isSource: selectedCountry == country)
            .frame(maxWidth: .infinity)
        }
    }
    .background {
        RoundedRectangle(cornerRadius: 10)
            .fill(Color.accentColor)
            .matchedGeometryEffect(id: selectedCountry, in: animation, isSource: false)
    }
    

    I couldn't understand why the marker was jumping from one position to another, instead of moving in an animated way. But it's because, the variable that gets updated is @AppStorage instead of @State. When you change it to @State, animations work fine:

    @State private var selectedCountry: Country = .france
    

    Animation

    See SwiftUI: save the state of toggle and keep the animation for some workarounds.