SwiftUI中如何实现带可选下拉选项的TextField?
SwiftUI 实现带联想下拉的 TextField(起点/终点选择)
核心思路
不用依赖Picker,直接通过TextField + 动态过滤的下拉列表实现需求:绑定输入文本,根据输入实时过滤地点数据源,点击选项后更新输入框内容并记录选中值,后续业务逻辑可直接使用该选中值。
完整实现代码
方式1:基础实现(适合快速上手)
import SwiftUI struct LocationPickerView: View { // 模拟地点数据源(可替换为网络接口返回数据) let allLocations = [ "London, UK", "London, Ohio, USA", "London, Kentucky, USA", "New York, USA", "Paris, France" ] // 起点状态:输入文本、选中值、下拉显示开关 @State private var startInput = "" @State private var selectedStartLocation: String? @State private var showStartDropdown = false // 终点状态(逻辑和起点完全一致) @State private var endInput = "" @State private var selectedEndLocation: String? @State private var showEndDropdown = false // 根据输入文本过滤地点(忽略大小写) private func filteredLocations(for input: String) -> [String] { guard !input.isEmpty else { return [] } return allLocations.filter { location in location.lowercased().contains(input.lowercased()) } } var body: some View { VStack(spacing: 20) { // 起点选择区 VStack(alignment: .leading) { Text("起点") .font(.headline) TextField("输入地点", text: $startInput) .padding() .border(Color.gray) .onTapGesture { showStartDropdown = true } .onChange(of: startInput) { _ in // 有匹配结果才显示下拉 showStartDropdown = !filteredLocations(for: startInput).isEmpty } // 起点下拉列表 if showStartDropdown { List(filteredLocations(for: startInput), id: \.self) { location in Button(action: { startInput = location selectedStartLocation = location showStartDropdown = false }) { Text(location) } } // 限制下拉高度(最多5行) .frame(height: min(CGFloat(filteredLocations(for: startInput).count * 44), 220)) .border(Color.gray) } } // 终点选择区 VStack(alignment: .leading) { Text("终点") .font(.headline) TextField("输入地点", text: $endInput) .padding() .border(Color.gray) .onTapGesture { showEndDropdown = true } .onChange(of: endInput) { _ in showEndDropdown = !filteredLocations(for: endInput).isEmpty } if showEndDropdown { List(filteredLocations(for: endInput), id: \.self) { location in Button(action: { endInput = location selectedEndLocation = location showEndDropdown = false }) { Text(location) } } .frame(height: min(CGFloat(filteredLocations(for: endInput).count * 44), 220)) .border(Color.gray) } } // 演示使用选中值 Button(action: { guard let start = selectedStartLocation, let end = selectedEndLocation else { print("请选择起点和终点") return } // 这里可加入你的业务逻辑,比如发起导航请求 print("确认路线:\(start) -> \(end)") }) { Text("确认路线") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } } .padding() } } struct LocationPickerView_Previews: PreviewProvider { static var previews: some View { LocationPickerView() } }
方式2:封装自定义组件(适合复用)
如果需要多次使用该功能,可把地点选择逻辑封装成独立组件,减少代码重复:
// 自定义地点选择TextField组件 struct LocationTextField: View { let title: String let allLocations: [String] @Binding var inputText: String @Binding var selectedLocation: String? @State private var showDropdown = false private func filteredLocations() -> [String] { guard !inputText.isEmpty else { return [] } return allLocations.filter { $0.lowercased().contains(inputText.lowercased()) } } var body: some View { VStack(alignment: .leading) { Text(title) .font(.headline) TextField("输入地点", text: $inputText) .padding() .border(Color.gray) .onTapGesture { showDropdown = true } .onChange(of: inputText) { _ in showDropdown = !filteredLocations().isEmpty } if showDropdown { List(filteredLocations(), id: \.self) { location in Button(action: { inputText = location selectedLocation = location showDropdown = false }) { Text(location) } } .frame(height: min(CGFloat(filteredLocations().count * 44), 220)) .border(Color.gray) } } } } // 主视图中使用自定义组件 struct LocationPickerView: View { let allLocations = [ "London, UK", "London, Ohio, USA", "London, Kentucky, USA", "New York, USA", "Paris, France" ] @State private var startInput = "" @State private var selectedStartLocation: String? @State private var endInput = "" @State private var selectedEndLocation: String? var body: some View { VStack(spacing: 20) { LocationTextField( title: "起点", allLocations: allLocations, inputText: $startInput, selectedLocation: $selectedStartLocation ) LocationTextField( title: "终点", allLocations: allLocations, inputText: $endInput, selectedLocation: $selectedEndLocation ) Button(action: { guard let start = selectedStartLocation, let end = selectedEndLocation else { print("请选择起点和终点") return } print("确认路线:\(start) -> \(end)") }) { Text("确认路线") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } } .padding() } }
关键说明
- 选中值的使用:
selectedStartLocation和selectedEndLocation就是你后续业务代码可直接调用的选中结果,使用前建议做非空判断。 - 动态数据源适配:如果需要从网络获取联想地点(比如调用地图API),只需把
allLocations换成动态数组,在onChange输入文本时发起网络请求更新数据源即可。 - 交互优化:下拉列表高度限制为最多5行,避免列表过长遮挡页面,可根据需求调整高度值。
内容的提问来源于stack exchange,提问作者Tymur Batulin
相关产品推荐
相关产品推荐

