You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
    }
}

关键说明

  1. 选中值的使用:selectedStartLocation和selectedEndLocation就是你后续业务代码可直接调用的选中结果,使用前建议做非空判断。
  2. 动态数据源适配:如果需要从网络获取联想地点(比如调用地图API),只需把allLocations换成动态数组,在onChange输入文本时发起网络请求更新数据源即可。
  3. 交互优化:下拉列表高度限制为最多5行,避免列表过长遮挡页面,可根据需求调整高度值。

内容的提问来源于stack exchange,提问作者Tymur Batulin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 04:22:09