如何在 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())
我试图在 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())