iPadOS 17.1 SwiftUI DatePicker with onTapGesture unexpected behaviour

444 views Asked by At

We have met a problem with iPadOS 17.1.

We have a normal DatePicker to choose the date, but in the meantime, we added an onTapGesture to it and did something.

With the previous version 17.0, when we click the DatePicker, the calendar view is poping up and let's us choose the date. But when we updated to 17.1, the calendar is not shown with a normal click, only a long press gesture will trigger it.

Here is the sample code:

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        DatePicker("test", selection: .constant(Date()), displayedComponents: .date)
            .onTapGesture {
                print("on tap")
            }
    }
}

The expectation is the calendar should be shown when the picker is tapped.

3

There are 3 answers

2
workingdog support Ukraine On

You could try a different approach to do something after the date has been selected, using .onChange(..), instead of relying on the other tap guesture.

For example:

struct ContentView: View {
    @State var selection = Date()
    
    var body: some View {
        DatePicker("test", selection: $selection, displayedComponents: .date)
            .onChange(of: selection) {
                print("---> do your thing here")
            }
    }
}
1
soundflix On

I think that DatePicker and onTapGesture are fighting to be first, which gives an undefined behaviour that could be different at any time. It is probably better to use .simultaneousGesture(_:) in this situation.

For example:

struct ContentView: View {
    let newGesture = TapGesture().onEnded {
        // Do your work here
        print("on simultaneous tap \(Int.random(in: 1...99))")
    }
    var body: some View {
        DatePicker("test", selection: .constant(Date()), displayedComponents: .date)
            .simultaneousGesture(newGesture)
    }
}
0
Manabu Nakazawa On

I encountered the same issue. As a dirty workaround, you can programatically call sendActions(for: .touchUpInside) of UIButton in UIDatePicker that is internally used in DatePicker, in onTapGesture().

You can use whatever to get the UIButton, but if you use SwiftUI Introspect, the code would look like this:

import UIKit
import SwiftUI
@_spi(Advanced) import SwiftUIIntrospect

struct DatePickerIssueWorkaround: View {
    @Weak private var datePicker: UIDatePicker?

    var body: some View {
        VStack{
            DatePicker("test", selection: .constant(Date()), displayedComponents: .date)
                .onTapGesture {
                    let button = datePicker?.findViews(of: UIButton.self).first
                    button?.sendActions(for: .touchUpInside)
                }
                .introspect(.datePicker, on: .iOS(.v15...)) { datePicker in
                    self.datePicker = datePicker
                }
        }
        .onTapGesture {
            print("on tap")
        }
    }
}

extension UIView {
    func findViews<TargetView: UIView>(of type: TargetView.Type) -> [TargetView] {
        var foundViews = [TargetView]()
        for subview in subviews {
            if let subviewOfType = subview as? TargetView {
                foundViews.append(subviewOfType)
            }
            foundViews.append(contentsOf: subview.findViews(of: TargetView.self))
        }
        return foundViews
    }
}