Swift 4:使用NSPredicate过滤自定义对象数组实现UITextField自动补全崩溃问题
哈哈,做UITextField自动补全时栽在NSPredicate上太常见了!我帮你梳理下最可能导致崩溃的几个原因,对应着排查应该就能解决:
常见崩溃原因及修复方案
1. 自定义对象的属性键路径写错了
这绝对是Top1的坑!比如你的自定义对象叫CustomItem,属性是name,但Predicate里写成了Name或者username,系统找不到对应的键路径直接就崩了,而且这种错误编译器还不会提前提示你。
修复方法:
- 仔仔细细核对自定义对象的属性名和Predicate里的键路径,大小写完全敏感!
- 强烈建议用Swift的
#keyPath宏来写键路径,编译器会帮你检查拼写错误,写错了直接编译不通过,从根源避免问题。
示例代码:
// 先定义你的自定义对象(记得继承NSObject,否则NSPredicate的键路径可能失效) class CustomItem: NSObject { let name: String init(name: String) { self.name = name super.init() } } // 正确的Predicate写法 let searchText = textField.text ?? "" let predicate = NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(CustomItem.name), searchText) let filteredItems = originalItems.filtered(using: predicate)
小提示:
CONTAINS[cd]里的c表示忽略大小写,d表示忽略变音符号,用户体验会好很多~
2. 输入文本为空或nil时没处理
如果用户还没输入内容,textField.text可能是nil,直接把nil传到Predicate的格式字符串里,会导致参数数量不匹配,瞬间崩溃。
修复方法:
- 先对输入文本做空值兜底,转成空字符串;如果是空字符串,直接返回全部数据或者隐藏自动补全的TableView就行。
示例代码:
func textFieldDidChange(_ textField: UITextField) { // 先处理空输入,去掉前后空格 let searchText = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if searchText.isEmpty { // 空输入时要么显示全部数据,要么隐藏TableView filteredItems = originalItems autocompleteTableView.isHidden = true return } // 正常执行过滤逻辑 let predicate = NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(CustomItem.name), searchText) filteredItems = originalItems.filtered(using: predicate) autocompleteTableView.reloadData() autocompleteTableView.isHidden = false }
3. Predicate格式字符串语法错误
比如格式字符串里的占位符和后面的参数数量不匹配,或者把CONTAINS拼成了CONTAIN,这种语法错误也会直接引发崩溃。
修复方法:
- 检查格式字符串的
%K、%@数量和后面的参数数量完全一致; - 如果要做前缀匹配(比如只匹配输入内容开头的结果),可以把
CONTAINS[cd]换成BEGINSWITH[cd]。
4. 非主线程操作UI或数据源
如果你的过滤操作是在后台线程跑的,然后直接刷新TableView,或者数据源数组在后台被修改,就会因为UI操作不在主线程而崩溃。
修复方法:
- 确保过滤数据和刷新TableView的操作都在主线程执行,用
DispatchQueue.main.async包起来:
func textFieldDidChange(_ textField: UITextField) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } let searchText = textField.text ?? "" let predicate = NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(CustomItem.name), searchText) self.filteredItems = self.originalItems.filtered(using: predicate) self.autocompleteTableView.reloadData() } }
5. 自定义对象不支持KVC
如果你的自定义对象是Swift结构体,或者属性是私有的,NSPredicate用%K访问键路径时会崩溃,因为结构体不支持KVC,私有属性也无法被KVC访问。
修复方法:
- 如果是结构体,直接改用Swift原生的
filter方法来过滤,更简单还不会有KVC的问题:
// 结构体版本的自定义对象 struct CustomItem { let name: String } // 用Swift filter代替NSPredicate let filteredItems = originalItems.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
额外调试小技巧
如果还是找不到问题,就在崩溃前加两行日志,看看输入文本和生成的Predicate内容,能快速定位问题:
let searchText = textField.text ?? "" let predicate = NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(CustomItem.name), searchText) print("当前搜索文本:\(searchText)") print("生成的Predicate:\(predicate)") let filteredItems = originalItems.filtered(using: predicate)
内容的提问来源于stack exchange,提问作者Ne AS
相关产品推荐
相关产品推荐

