如何在 Swift UI 中创建下拉菜单?

How to create dropdown in Swift UI?

我试图在 Swift UI 中创建一个下拉菜单。大多数文章建议使用覆盖来创建相同的内容。尝试了下面的一些示例代码,但是当下拉菜单出现时,它确实出现在下一个内容下方。See Image, How to make this look like normal dropdown? I observed it happens when we add HStack, without that it comes top of the other view, which is expected, But when I try to add HStack its coming behind the UI. I want the dropdown to look like this enter image description here

 import SwiftUI

struct DropdownOption: Hashable {
    let key: String
    let value: String
    
    public static func == (lhs: DropdownOption, rhs: DropdownOption) -> Bool {
        return lhs.key == rhs.key
    }
}

struct DropdownRow: View {
    var option: DropdownOption
    var onOptionSelected: ((_ option: DropdownOption) -> Void)?
    
    var body: some View {
        Button(action: {
            if let onOptionSelected = self.onOptionSelected {
                onOptionSelected(self.option)
            }
        }) {
            HStack {
                Text(self.option.value)
                    .font(.system(size: 14))
                    .foregroundColor(Color.black)
                Spacer()
            }
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 5)
    }
}

struct Dropdown: View {
    var options: [DropdownOption]
    var onOptionSelected: ((_ option: DropdownOption) -> Void)?
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 0) {
                ForEach(self.options, id: \.self) { option in
                    DropdownRow(option: option, onOptionSelected: self.onOptionSelected)
                }
            }
        }
        .frame(minHeight: CGFloat(options.count) * 30, maxHeight: 250)
        .padding(.vertical, 5)
        .background(Color.white)
        .cornerRadius(5)
        .overlay(
            RoundedRectangle(cornerRadius: 5)
                .stroke(Color.gray, lineWidth: 1)
        )
    }
}

struct DropdownSelector: View {
    @State private var shouldShowDropdown = false
    @State private var selectedOption: DropdownOption? = nil
    var placeholder: String
    var options: [DropdownOption]
    var onOptionSelected: ((_ option: DropdownOption) -> Void)?
    private let buttonHeight: CGFloat = 45
    
    var body: some View {
        Button(action: {
            self.shouldShowDropdown.toggle()
        }) {
            HStack {
                Text(selectedOption == nil ? placeholder : selectedOption!.value)
                    .font(.system(size: 14))
                    .foregroundColor(selectedOption == nil ? Color.gray: Color.black)
                
                Spacer()
                
                Image(systemName: self.shouldShowDropdown ? "arrowtriangle.up.fill" : "arrowtriangle.down.fill")
                    .resizable()
                    .frame(width: 9, height: 5)
                    .font(Font.system(size: 9, weight: .medium))
                    .foregroundColor(Color.black)
            }
        }
        .padding(.horizontal)
        .cornerRadius(5)
        .frame(width: .infinity, height: self.buttonHeight)
        .overlay(
            RoundedRectangle(cornerRadius: 5)
                .stroke(Color.gray, lineWidth: 1)
        )
        .overlay(
            VStack {
                Image("top-image")
                                        .resizable()
                                        .aspectRatio(contentMode: .fit)
                                        .scaledToFit()
                if self.shouldShowDropdown {
                    Spacer(minLength: buttonHeight + 10)
                    Dropdown(options: self.options, onOptionSelected: { option in
                        shouldShowDropdown = false
                        selectedOption = option
                        self.onOptionSelected?(option)
                    })
                }
            }, alignment: .topLeading
        )
        .background(
            RoundedRectangle(cornerRadius: 5).fill(Color.white)
        )
    }
}

struct DropdownSelector_Previews: PreviewProvider {
    @State private static var address: String = ""
    
    static var uniqueKey: String {
        UUID().uuidString
    }
    
    static let options: [DropdownOption] = [
        DropdownOption(key: uniqueKey, value: "Sunday"),
        DropdownOption(key: uniqueKey, value: "Monday"),
        DropdownOption(key: uniqueKey, value: "Tuesday"),
        DropdownOption(key: uniqueKey, value: "Wednesday"),
        DropdownOption(key: uniqueKey, value: "Thursday"),
        DropdownOption(key: uniqueKey, value: "Friday"),
        DropdownOption(key: uniqueKey, value: "Saturday")
    ]
    
    
    static var previews: some View {
        VStack(spacing: 20) {
            
            
            HStack(){
                DropdownSelector(
                    placeholder: "Day of the week",
                    options: options,
                    onOptionSelected: { option in
                        print(option)
                    })
                    .padding(.horizontal)
                    .zIndex(1)
                
                DropdownSelector(
                    placeholder: "Day of the week",
                    options: options,
                    onOptionSelected: { option in
                        print(option)
                    })
                    .padding(.horizontal)
                    .zIndex(1)
            }
            
            
            Group {
                TextField("Full Address", text: $address)
                    .font(.system(size: 14))
                    .padding(.horizontal)
            }
            .frame(width: .infinity, height: 45)
            .overlay(
                RoundedRectangle(cornerRadius: 5)
                    .stroke(Color.gray, lineWidth: 1)
            )
            .padding(.horizontal)
        }
    }
}

您可以结合使用GroupBox和DisclosureGroup来实现您想要的,您可以根据需要修改下面的代码

import SwiftUI

struct ContentView: View {
    //MARK: - PROPERTIES
    
    let nutrients: [String] = ["Energy", "Sugar", "Fat", "Protein", "Vitamins", "Minerals"]
    
    //MARK: - BODY
    
    var body: some View{
        GroupBox(){
            DisclosureGroup("Nutritional value per 100g") {
                ForEach(0..<nutrients.count, id: \.self){ index in
                    Divider()
                        .padding(.vertical, 2)
                    
                    HStack{
                        Group{
                            Image(systemName: "info.circle")
                            Text(nutrients[index])
                        }//: GROUP
                        .foregroundColor(.gray)
                        .font(.system(.body).bold())
                        
                        Spacer(minLength: 25)
                        
                        Text("1g")
                            .multilineTextAlignment(.trailing)
                    }//: HSTACK
                }//: LOOP
            }//: DISCLOSURE
        }//: BOX
    }
}

//MARK: - PREVIEW

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

您可以使用 .toggleStyle 修饰符自定义 Toggle,首先您必须像这样创建您的样式

struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
    return HStack{
        //IMAGE
        Image(systemName: configuration.isOn ? "checkmark.square" : "square")
            .font(.system(size: 30, weight: .semibold, design: .rounded))
            .onTapGesture {
                configuration.isOn.toggle()
            }
        
        //LABEL
        configuration.label
    }//: HSTACK
}
}

然后像这样将它应用到您的 Toggle 上

Toggle("Placeholder label", isOn: .constant(true))
        .toggleStyle(CheckboxStyle())