1

In theory SwiftUI should give the child’s view gesture higher priority compared to parent's view gesture. And it is really so for most situations. But I encountered some situations where child's gestures stop work as expected. For example Picker of .pickerStyle(.segmented) or a Button inside a Form. How can we make those elements handle taps again?

import SwiftUI

struct ContentView: View {
    @State private var message = "Message"
    @State private var kind = Kind.item
    @State private var isToggleOn = false
    let newGesture = TapGesture().onEnded {
        print("Tap on VStack.")
    }
    
    var body: some View {
        VStack(spacing:25) {
            
            // Elements responding to tap
            
            Image(systemName: "heart.fill")
                .resizable()
                .frame(width: 75, height: 75)
                .padding()
                .foregroundColor(.red)
                .onTapGesture {
                    print("Tap on image.")
                }
            Rectangle()
                .fill(Color.blue)
            Button("Button 1") {
                print("Button 1 tapped")
            }
            Toggle("Toggle", isOn: $isToggleOn)
            
            Picker("Kind", selection: $kind) {
                ForEach(Kind.allCases) { kind in
                    Text(kind.description).tag(kind)
                }
            }
            
            // Elements below stops responding to tap
            
            Picker("Kind", selection: $kind) {
                ForEach(Kind.allCases) { kind in
                    Text(kind.description).tag(kind)
                }
            }
            .pickerStyle(.segmented)
            
            Form {
                Section {
                    Button("Button 2") {
                        print("Button 2 tapped")
                    }
                } header: {
                    Text("Header")
                }
            }
        }
        .simultaneousGesture(newGesture)
        .border(Color.purple, width: 3)
    }
}

enum Kind: String, CaseIterable, CustomStringConvertible, Codable, Identifiable {
    case item, service
    var description: String {
        switch self {
        case .item:
            return NSLocalizedString("Item", comment: "Item Kind in ItemModel")
        case .service:
            return NSLocalizedString("Service", comment: "Item Kind in ItemModel")
        }
    }
    var id: Self { self }
}
Paul B
  • 3,989
  • 33
  • 46

2 Answers2

0

This happens because some components change they behaviour according the way you are using.

For example in the case of the button inside the form. As you see in your first Button, if this component is outside of a Form {} the action of it may and should be in the action: handler. However, if you want or need it to be inside the Form, you will have to add a .onTapGesture {}.

If in your code you ignore the action handler and add a tapGesture instead, it will work

            Form {
                Section {
                    Button("Button 2") {
                        
                    }.onTapGesture {
                        print("Button 2 tapped")
                    }
                } header: {
                    Text("Header")
                }
            }

For the picker I can't tell a 100% but I think is just incompatible and the simultaneous create an interference. That might be as the Picker changing is not a tapGesture itself, but an intrinsec functionality of the component.

0

There is a way to force Button execute its action in this case. Just add a .buttonStyle(.plain) or .buttonStyle(.bordered) to it. Maybe other styles will do. This way Button gets priority to handle taps.

Paul B
  • 3,989
  • 33
  • 46