Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add click event to whole view in SwiftUI

I'm developing SwiftUI test app and I added my custom DropDown menu here. Dropdown works fine but I want to dismiss dropdown menu when user click dropdown menu outside area.

Here's my dropdown menu.

import SwiftUI

var dropdownCornerRadius:CGFloat = 3.0
struct DropdownOption: Hashable {
    public static func == (lhs: DropdownOption, rhs: DropdownOption) -> Bool {
        return lhs.key == rhs.key
    }

    var key: String
    var val: String
}

struct DropdownOptionElement: View {
    var dropdownWidth:CGFloat = 150
    var val: String
    var key: String
    @Binding var selectedKey: String
    @Binding var shouldShowDropdown: Bool
    @Binding var displayText: String

    var body: some View {
        Button(action: {
            self.shouldShowDropdown = false
            self.displayText = self.val
            self.selectedKey = self.key
        }) {
            VStack {
                Text(self.val)
                Divider()
            }

        }.frame(width: dropdownWidth, height: 30)
            .padding(.top, 15).background(Color.gray)

    }
}

struct Dropdown: View {
    var dropdownWidth:CGFloat = 150
    var options: [DropdownOption]
    @Binding var selectedKey: String
    @Binding var shouldShowDropdown: Bool
    @Binding var displayText: String
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            ForEach(self.options, id: \.self) { option in
                DropdownOptionElement(dropdownWidth: self.dropdownWidth, val: option.val, key: option.key, selectedKey: self.$selectedKey, shouldShowDropdown: self.$shouldShowDropdown, displayText: self.$displayText)
            }
        }

        .background(Color.white)
        .cornerRadius(dropdownCornerRadius)
        .overlay(
            RoundedRectangle(cornerRadius: dropdownCornerRadius)
                .stroke(Color.primary, lineWidth: 1)
        )
    }
}

struct DropdownButton: View {
    var dropdownWidth:CGFloat = 300
    @State var shouldShowDropdown = false
    @State var displayText: String
    @Binding var selectedKey: String
    var options: [DropdownOption]

    let buttonHeight: CGFloat = 30
    var body: some View {
        Button(action: {
            self.shouldShowDropdown.toggle()
        }) {
            HStack {
                Text(displayText)
                Spacer()
                Image(systemName: self.shouldShowDropdown ? "chevron.up" : "chevron.down")
            }
        }
        .padding(.horizontal)
        .cornerRadius(dropdownCornerRadius)
        .frame(width: self.dropdownWidth, height: self.buttonHeight)
        .overlay(
            RoundedRectangle(cornerRadius: dropdownCornerRadius)
                .stroke(Color.primary, lineWidth: 1)
        )
        .overlay(
            VStack {
                if self.shouldShowDropdown {
                    Spacer(minLength: buttonHeight)
                    Dropdown(dropdownWidth: dropdownWidth, options: self.options, selectedKey: self.$selectedKey, shouldShowDropdown: $shouldShowDropdown, displayText: $displayText)
                }
            }, alignment: .topLeading
            )
        .background(
            RoundedRectangle(cornerRadius: dropdownCornerRadius).fill(Color.white)
        )
    }
}



struct DropdownButton_Previews: PreviewProvider {
    static let options = [
        DropdownOption(key: "week", val: "This week"), DropdownOption(key: "month", val: "This month"), DropdownOption(key: "year", val: "This year")
    ]

    static var previews: some View {
        Group {
            VStack(alignment: .leading) {
                DropdownButton(displayText: "This month", selectedKey: .constant("Test"), options: options)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green)
            .foregroundColor(Color.primary)

            VStack(alignment: .leading) {

                DropdownButton(shouldShowDropdown: true, displayText: "This month", selectedKey: .constant("Test"), options: options)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green)
            .foregroundColor(Color.primary)
        }
    }
}

I think I can achieve this by adding click event to whole body view and set dropdown show State flag variable to false. But I'm not sure how to add click event to whole view. Can anyone please help me about this issue? Thanks.

like image 237
Svetoslav Atanasov Avatar asked Mar 16 '20 13:03

Svetoslav Atanasov


1 Answers

You can try like the following in your window ContentView

struct ContentView: View {
    var body: some View {
        GeometryReader { gp in     // << consumes all safe space
           // all content here
        }
        .onTapGesture {
           // change state closing any dropdown here
        }
     }
//     .edgesIgnoringSafeArea(.all) // uncomment if needed entire screen
}
like image 196
Asperi Avatar answered Nov 13 '22 20:11

Asperi