Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a button (or any other element) show SwiftUI's DatePicker popup on tap?

Tags:

ios

swift

swiftui

I'm trying to achieve the simplest possible use case, but I can't figure it out. I have a picture of calendar. All I want is to show DatePicker popup when tapping the picture. I tried to put it inside ZStack, but by doing it I can't hide default data textfields:

ZStack {
    Image("icon-calendar")
    .zIndex(1)
    DatePicker("", selection: $date)
    .zIndex(2)
}

How to make this simple layout natively without ridiculous workarounds?

like image 729
Mike Raccoon Avatar asked Jan 19 '21 18:01

Mike Raccoon


2 Answers

I'm having this problem too. I couldn't sleep for a few days thinking about the solution. I have googled hundred times and finally, I found a way to achieve this. It's 1:50 AM in my timezone, I can sleep happily now. Credit goes to chase's answer here

Demo here: https://media.giphy.com/media/2ILs7PZbdriaTsxU0s/giphy.gif

The code that does the magic

struct ContentView: View {
    @State var date = Date()
    
    var body: some View {
        ZStack {
            DatePicker("label", selection: $date, displayedComponents: [.date])
                .datePickerStyle(CompactDatePickerStyle())
                .labelsHidden()
            Image(systemName: "calendar")
                .resizable()
                .frame(width: 32, height: 32, alignment: .center)
                .userInteractionDisabled()
        }
    }
}

struct NoHitTesting: ViewModifier {
    func body(content: Content) -> some View {
        SwiftUIWrapper { content }.allowsHitTesting(false)
    }
}

extension View {
    func userInteractionDisabled() -> some View {
        self.modifier(NoHitTesting())
    }
}

struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
    let content: () -> T
    func makeUIViewController(context: Context) -> UIHostingController<T> {
        UIHostingController(rootView: content())
    }
    func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}
like image 50
Hieu Dinh Avatar answered Nov 19 '22 03:11

Hieu Dinh


Tried using Hieu's solution in a navigation bar item but it was breaking. Modified it by directly using SwiftUIWrapper and allowsHitTesting on the component I want to display and it works like a charm.

Also works on List and Form

struct StealthDatePicker: View {
    @State private var date = Date()
    var body: some View {
        ZStack {
            DatePicker("", selection: $date, in: ...Date(), displayedComponents: .date)
                .datePickerStyle(.compact)
                .labelsHidden()
            SwiftUIWrapper {
                Image(systemName: "calendar")
                .resizable()
                .frame(width: 32, height: 32, alignment: .topLeading)
            }.allowsHitTesting(false)
        }
    }
}

struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
    let content: () -> T
    func makeUIViewController(context: Context) -> UIHostingController<T> {
        UIHostingController(rootView: content())
    }
    func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}
like image 3
Sharath Sriram Avatar answered Nov 19 '22 02:11

Sharath Sriram