rustproofFish
rustproofFish

Reputation: 999

SwiftUI tap gesture recogniser only called once when it effects a state change

I'm building a custom view (a multi-wheel picker) for use in a Form. In order to keep the Form compact, I only want to display the picker when the associated row has been tapped. This should be simple but I'm getting some unexpected behaviour.

If the closure associated with the tap gesture recogniser doesn't influence the state of the view (e.g. by executing a simple print statement) all taps are registered as one would expect. When I try and update the state of the view by toggling a boolean value in the closure, the tap gesture is only executed once (the picker is shown). Further taps do not effect further state changes. Adding borders to all of the views in the hierarchy hasn't shown any shift in the position or dimensions of the gesture recogniser.

This should be a very simple thing to implement but I've wasted a good hour trying to debug my code. I will undoubtedly kick myself when I find out what I've done wrong but, in the meantime, assistance would be gratefuly received. The code is a bit messy as it's proof of concept at the moment.

//
//  MultiWheelPickerView.swift
//  AthenaVS
//
//  Created by Adrian Ward on 02/02/2020.
//  Copyright © 2020 AwardVetSciences. All rights reserved.
//

import SwiftUI

struct MultiWheelPickerView: View {
    @State private var isWheelVisible = false
    @State var selection = ["", ""]
    private var prompt: String {
        let currentValue = selection.reduce("", +)
        return currentValue.count == 0 ? "Not set" : currentValue
    }
    private var title: String = "title".uppercased()

    var body: some View {
        VStack(alignment: .center) {
            VStack(alignment: .leading) {
                HStack {
                    Text("\(self.title)")
                        .font(.caption)
                        .bold()
                    Spacer()
                    Text("\(prompt)")
                        .foregroundColor(Color.gray)
                }
                    .contentShape(Rectangle()) // ensures entire HStack is tapable
                    .onTapGesture {
                        self.isWheelVisible = !self.isWheelVisible
                        print("Tapped")
                }

                if isWheelVisible {
                    GeometryReader { geometry in
                        HStack {
                            ZStack {
                                Text("One")
                                Picker(selection: self.$selection[0], label: Text("one")) {
                                    ForEach(0..<10) { row in
                                        Text("\(row)")
                                            .tag(String(row))
                                    }
                                }
                                    .labelsHidden()  // label is misplaced otherwise
                                    .pickerStyle(WheelPickerStyle())
                                    .fixedSize(horizontal: true, vertical: true)
                                    .frame(width: geometry.size.width / 2,
                                           alignment: .trailing)
                                    .clipped() // to enusre touch zone matches the text position
                            }

                            ZStack {
                                Text("Two")
                                Picker(selection: self.$selection[1], label: Text("two")) {
                                    ForEach(10..<100) { row in
                                        Text("\(row)")
                                            .tag(String(row))
                                    }
                                }
                                    .labelsHidden() // label is misplaced otherwise
                                    .pickerStyle(WheelPickerStyle())
                                    .fixedSize(horizontal: true, vertical: true)
                                    .frame(width: geometry.size.width / 2, alignment: .trailing)
                                    .clipped() // to enusre touch zone matches the text position
                            }

                        }
                    }
                    .frame(height:100)
                        .clipped() // ensures that wheel isn't superimposed on next row
                }
            }

        }
    }
}

struct MultiPickerView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            Form {
                MultiWheelPickerView().previewDisplayName("As Form child")
            }
            MultiWheelPickerView().previewDisplayName("As View child")
        }
    }
}

Upvotes: 1

Views: 1884

Answers (1)

Lieksu
Lieksu

Reputation: 71

Your GeometryReader is intercepting taps, when pickers are visible.

You cant test it by adding .onTapGesture { print("Wheel tapped!") } to it.

Try adding .contentShape(Rectangle()) modifier before setting GeometryReader's frame height to 100. It solves the problem for me.

Upvotes: 1

Related Questions