0

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 Answers0