0

I'm trying to implement a segmented picker with an additional behavior where tapping the selected option again deselects it. However, this code doesn't seem to function as intended.

Picker("",selection: $selectedOption) {
    ForEach(["A","B","C","D","E"], id:\.self) { option in
        Text(option)
            .onTapGesture {
                if selectedOption == option {
                    selectedOption = ""
                }
            }
    }
}
.pickerStyle(.segmented)
1
  • Assuming this is iOS, you need to subclass UISegmentedControl (see here) and write a UIViewRepresentable wrapping it, or build your own picker from scratch. Commented Nov 19, 2024 at 13:06

1 Answer 1

0

This kind of behavior can be achieved by superimposing the selected option with an overlay and attaching the tap gesture to the overlay. When tapped, the selection can be de-selected.

  • One way to get the positioning right is to use .matchedGeometryEffect.
  • It doesn't work to use the text labels themselves as the source for .matchedGeometryEffect, but it does work to use invisible placeholders in the background.
  • Use the ids of the picker options as the ids for matching.
  • Apply a .contentShape to the clear overlay, to make it receptive to tap gestures.
struct ContentView: View {
    let options = ["A", "B", "C", "D", "E"]
    @State private var selectedOption = ""
    @Namespace private var ns

    var body: some View {
        Picker("", selection: $selectedOption) {
            ForEach(options, id:\.self) { option in
                Text(option)
            }
        }
        .pickerStyle(.segmented)
        .background {

            // A row of placeholders
            HStack(spacing: 0) {
                ForEach(options, id: \.self) { option in
                    Color.clear
                        .matchedGeometryEffect(id: option, in: ns, isSource: true)
                }
            }
        }
        .overlay {
            if selectedOption != "" {
                Color.clear
                    .contentShape(Rectangle())
                    .matchedGeometryEffect(id: selectedOption, in: ns, isSource: false)
                    .onTapGesture {
                        selectedOption = ""
                    }
            }
        }
    }
}

Animation

Sign up to request clarification or add additional context in comments.

1 Comment

The code needed a small fix: .contentShape must come before .matchedGeometryEffect, otherwise the overlay prevents a different option from being selected. Answer updated. Ps. Thanks for accepting it!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.