如何在SwiftUI中为整个视图添加点击事件

17
我正在开发SwiftUI测试应用程序,并在此处添加了我的自定义下拉菜单。下拉菜单可以正常工作,但我希望在用户单击下拉菜单外部区域时关闭下拉菜单。
这是我的下拉菜单。
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)
        }
    }
}

我认为可以通过给整个body视图添加点击事件并将下拉选项的显示状态设置为false来实现此目标。但是我不确定如何给整个视图添加点击事件。 有人可以帮我解决这个问题吗?谢谢。


1
你尝试过在顶层视图上使用 onTapGesture 修饰符吗? - Sam
你知道,SwiftUI 视图只包含宽度和高度,子元素会填充这些尺寸。例如,当我在整个视图中放置一个下拉菜单时,整个视图的宽度仅为下拉菜单的宽度。 - Ioan Moldovan
2个回答

19

您可以在窗口的 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
}

1
哦,是的,GeometryReader 完美地工作了,你是 SwiftUI 忍者。谢谢。顺便说一下,我认为你需要在 onTapGesture 之前添加自定义背景或颜色,否则 onTapGesture 不会被调用。如果你不想添加背景,我们可以在添加 onTapGesture 之前添加类似 .background(Color.blue.opacity(0.0001)) 的代码。 - Ioan Moldovan
2
对于任何地方,轻拍手势仅适用于非透明背景,因此这在这里不是主题,但是是的,你是正确的。 - Asperi

3

在使用.onTapGesture之前,可以通过将.contentShape(Rectangle())添加到HStack/VStack中来实现。例如:

var body: some View  {
    VStack(alignment: .leading, spacing: 8) {
        HStack {
            VStack(alignment:.leading, spacing: 8) {
                CustomText(text: model?.id ?? "Today", fontSize: 12, fontColor: Color("Black50"))
                CustomText(text: model?.title ?? "This Day..", fontSize: 14)
                    .lineLimit(2)
                    .padding(.trailing, 8)
            }
            Spacer()
            Image("user_dummy")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 60)
                .cornerRadius(8)
            
        }
        CustomText(text: model?.document ?? "", fontSize: 12, fontColor: Color("Black50"))
            .lineLimit(4)
    }
    .contentShape(Rectangle())
    .onTapGesture {
        debugPrint("Whole view as touch")
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接