Transition with matchedGeometryEffect works correctly only in one direction

153 Views Asked by At

Trying to make segmented control animation with changing options colors. In one direction everything works good, but in other background is above content. I want to do it without GeometryReader.

Animation works good in one direction: https://i.stack.imgur.com/w0YXF.gif

But here how it works in another direction: https://i.stack.imgur.com/6TAbB.gif

Code of Picker:

struct CustomPicker<Option: CustomPickerOption>: View {

    // MARK: - Properties

    @Binding var selection: Option
    let options: [Option]

    @Namespace private var namespaceID
    private let buttonBackgroundID: String = "buttonOverlayID"
    private let buttonOverlayID: String = "buttonOverlayID"

    // MARK: - UI

    var body: some View {
        HStack(spacing: 0) {
            ForEach(options) { option in
                Segment(
                    title: option.title,
                    isSelected: selection == option,
                    backgroundID: buttonBackgroundID,
                    overlayID: buttonOverlayID,
                    namespaceID: namespaceID,
                    action: { selection = option }
                )
            }
        }
        .padding(4)
        .background(Color.blue)
        .clipShape(Capsule())
    }

}

Code of Segment:

private struct Segment: View {

        // MARK: - Properties

        let title: String
        let isSelected: Bool
        let backgroundID: String
        let overlayID: String
        let namespaceID: Namespace.ID
        let action: () -> Void

        @State private var isPressed: Bool = false

        // MARK: - UI

        var body: some View {
            Button(action: action) {
                titleView
                    .blendMode(.difference)
                    .overlay(
                        titleView
                            .blendMode(.hue)
                    )
                    .overlay(
                        titleView
                            .foregroundColor(.black)
                            .blendMode(.overlay)
                    )
                    .background {
                        if isSelected {
                            background
                                .matchedGeometryEffect(id: backgroundID, in: namespaceID)
                                .transition(.offset())
                        }
                    }
            }
            .buttonStyle(.customHighlighted(isPressed: $isPressed))
        }

        private var background: some View {
            Color.white
                .clipShape(Capsule())
        }

        private var titleView: some View {
            Text(title)
                .font(\.semibold)
                .foregroundColor(.white)
                .padding(.horizontal, 12)
                .padding(.vertical, 10)
        }

    }

I tried to replace .transition(.offset) and use another transitions, add .frame(maxWidth: .infinity, maxHeight: .infinity), but nothing works. Are there any other solutions of this problem? .animation(.default, value: isSelected) adds fade effect, so it's not best solution.

Update: zIndex(selection == option ? 1 : 0) helps to normalize transition. To be honest I don't understand why. If you can, please, explain this weird hack.

0

There are 0 best solutions below