Xcode Swift中封装的UITextField如何实现输入下拉建议功能
实现方案
核心逻辑是不改动原有SwiftUI布局结构,将下拉建议视图作为独立悬浮层挂载到当前应用的KeyWindow上,通过实时计算输入框在全局坐标系的位置定位下拉框,完全复用现有输入框封装逻辑,改造成本极低。
具体实现步骤
- 第一步:扩展现有
FocusableTextField的配置参数,新增建议列表、选中建议回调的入参,给所有新增参数设置默认空值,保证所有已复用的场景完全不需要修改调用代码 - 第二步:在Coordinator中持有下拉建议列表的UITableView实例,同时新增输入框全局位置监听、输入状态监听逻辑:
- 当输入框开始编辑、文本内容变化时,实时计算输入框相对于KeyWindow的frame,将下拉列表定位在输入框正下方,显示在window最上层
- 当输入框结束编辑、选中某条建议、点击空白区域时,隐藏下拉列表
- 第三步:适配下拉列表UI,保持和输入框宽度一致,复用输入框的圆角、边框色等样式参数,避免和现有UI风格冲突
- 第四步:处理边缘场景:监听屏幕旋转、父视图布局变化事件,实时更新下拉列表位置;如果下拉列表超出屏幕底部,自动调整最大高度或者改为向上弹出
关键代码修改示例
首先修改FocusableTextField的定义,新增可选配置项,保持对旧代码的兼容:
struct FocusableTextField: UIViewRepresentable { // 原有参数完全保留,新增以下可选参数 var suggestions: [String] = [] var onSelectSuggestion: ((String) -> Void)? = nil // 原有Coordinator、makeCoordinator、makeUIView等基础逻辑保留 }
然后在Coordinator中补充下拉列表相关逻辑:
class Coordinator: NSObject, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource { @Binding var text: String var didBecomeFirstResponder = false // 下拉建议列表实例 private let suggestionTableView = UITableView(frame: .zero, style: .plain) private var suggestions: [String] = [] var onSelectSuggestion: ((String) -> Void)? weak var hostWindow: UIWindow? var borderColor: UIColor = UIColor(red: 0xEE/255, green: 0xA9/255, blue: 0x24/255, alpha: 1) init(text: Binding<String>) { _text = text super.init() configSuggestionTableView() } private func configSuggestionTableView() { suggestionTableView.delegate = self suggestionTableView.dataSource = self suggestionTableView.layer.cornerRadius = 14 suggestionTableView.layer.borderWidth = 2 suggestionTableView.clipsToBounds = true suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "SuggestionCell") } // 定位并显示下拉列表 private func showSuggestionList(for textField: UITextField) { guard !suggestions.isEmpty, let window = textField.window else { hideSuggestionList() return } hostWindow = window suggestionTableView.layer.borderColor = borderColor.cgColor // 计算输入框在全局window中的位置 let textFieldGlobalFrame = textField.convert(textField.bounds, to: window) let maxTableHeight: CGFloat = 200 let tableHeight = min(CGFloat(suggestions.count) * 44, maxTableHeight) // 下拉框定位在输入框正下方,留4px间距 suggestionTableView.frame = CGRect( x: textFieldGlobalFrame.origin.x, y: textFieldGlobalFrame.maxY + 4, width: textFieldGlobalFrame.width, height: tableHeight ) if suggestionTableView.superview == nil { window.addSubview(suggestionTableView) } suggestionTableView.reloadData() suggestionTableView.isHidden = false } private func hideSuggestionList() { suggestionTableView.isHidden = true } // MARK: 补充UITextFieldDelegate逻辑 func textFieldDidBeginEditing(_ textField: UITextField) { showSuggestionList(for: textField) } func textFieldDidEndEditing(_ textField: UITextField) { hideSuggestionList() } func textFieldDidChangeSelection(_ textField: UITextField) { text = textField.text ?? "" showSuggestionList(for: textField) } // MARK: UITableView数据源与代理 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { suggestions.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "SuggestionCell", for: indexPath) cell.textLabel?.text = suggestions[indexPath.row] return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectedValue = suggestions[indexPath.row] text = selectedValue onSelectSuggestion?(selectedValue) hideSuggestionList() tableView.deselectRow(at: indexPath, animated: true) } }
最后在makeUIView和updateUIView方法中补充参数传递逻辑:
func makeUIView(context: Context) -> UITextField { // 原有TextFieldPad初始化、样式配置逻辑完全保留 let textField = TextFieldPad(frame: .zero) // ... 原有样式配置代码 ... textField.delegate = context.coordinator // 传递配置给coordinator context.coordinator.onSelectSuggestion = onSelectSuggestion context.coordinator.borderColor = borderColor return textField } func updateUIView(_ uiView: UITextField, context: Context) { uiView.text = text // 原有firstResponder处理逻辑保留 if isFirstResponder && !context.coordinator.didBecomeFirstResponder { uiView.becomeFirstResponder() context.coordinator.didBecomeFirstResponder = true } // 更新建议数据,编辑状态下实时刷新下拉框 context.coordinator.suggestions = suggestions if uiView.isFirstResponder { context.coordinator.showSuggestionList(for: uiView) } }
补充说明
- 下拉列表直接挂载在window层级,不会挤占SwiftUI布局流中原有视图的位置,完全不会影响现有页面的布局结构
- 新增参数均设置了默认值,现有所有调用
FocusableTextField的代码不需要做任何修改,没有改造成本 - 可根据业务需要自行扩展下拉列表的样式、选中效果、模糊匹配逻辑,适配自身设计规范
- 如果需要实现点击空白区域收起下拉的效果,可给window添加点击手势,判断点击位置不在输入框和下拉列表范围内时触发隐藏即可
内容的提问来源于stack exchange,提问作者Tornike Shavishvili
相关产品推荐
相关产品推荐

