UI学习:UISearchController基础了解和应用

文章目录

UISearchController

UISearchController 是苹果提供的一个控制器,专门用来管理搜索功能。它帮你做了三件事:

  • 显示一个搜索框(searchBar
  • 监听用户输入
  • 通知你去更新搜索结果

协议

首先, 需要遵守协议, 用于在搜索栏中输入后更新搜索结果

objc 复制代码
<UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating>
  • UISearchResultsUpdating是用于当UISearchController中的搜索文本发生变化时,通知搜索结果控制器更新结果的协议。
  • 只有一个方法:updateSearchResultsForSearchController:。
  • 通常在里面获取searchBar.text,然后根据文本过滤数据源,刷新tableView。
  • 配合UISearchController的searchResultsUpdater属性使用。
  • 适用于动态过滤本地数据

这里做一展示

可以看到, 当用户在搜索框中输入、删除或修改文字时,系统会自动调用该协议中唯一的方法:- (void)updateSearchResultsForSearchController:(UISearchController *)searchController 方法

在这个方法中, 可以根据搜索框中的文本(UISearchController.searchBar.text) 来刷新UITableView 或者 UICollectionView

声明属性

objc 复制代码
// ViewController.m

@interface ViewController ()

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UISearchController *searchController;

@property (nonatomic, strong) NSArray<NSString *> *allData;       // 原始数据,永远不动
@property (nonatomic, strong) NSArray<NSString *> *filteredData;  // 显示用的数据

@end

我们定义声明一个 UITableView 来实时显示我们的搜索结果, 并声明两个NSArray 来作为 UITableView的数据源, allData 用来存储全部的数据, filteredData 这用来存储根据搜索结果筛选的数据

objc 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"搜索";
    // Do any additional setup after loading the view.
    
    // ① 准备数据
    self.allData = @[@"Apple", @"Banana", @"Cherry", @"Date", @"Fig", @"Grape"];
    self.filteredData = self.allData;
    
    [self setupTableView];
    [self setupSearchController];
    
    if (@available(iOS 26.0, *)) {
            self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementStacked;
            self.navigationItem.searchBarPlacementAllowsToolbarIntegration = NO; // 设置不允许将UISearchController放在工具栏中,保证UISearchController在上方的导航栏中
        }
}

在iOS26之后, UISearchController 默认是添加到toolbar中的,也就是显示在屏幕的下方, 如果要想让 UISearchController 显示在导航栏中, 就需要添加以下代码, 用来规定UISearchController 的位置

objc 复制代码
if (@available(iOS 26.0, *)) {
 self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementStacked;
 // 设置不允许将UISearchController放在工具栏中,保证UISearchController在上方的导航栏中    
 self.navigationItem.searchBarPlacementAllowsToolbarIntegration = NO; 
} 

创建 UISearchController

objc 复制代码
 self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];

参数 nitWithSearchResultsController 表示显示结果的页面,传 nil 表示在当前界面显示搜索结果

注意,如果要跳转界面进行结果展示, 需要要将searchResultsUpdater 代理对象设置为要跳转的视图控制器,否则无法观察到搜索结果

objc 复制代码
    MyViewController* vc = [[MyViewController alloc] init];
    UISearchController *searchController = [[UISearchController alloc] initWithSearchResultsController: vc];
    
    searchController.searchResultsUpdater = vc;               // 设置结果更新代理

这里展示跳转界面展示搜索结果

设置UISearchController 的相关属性

objc 复制代码
searchController.obscuresBackgroundDuringPresentation = NO; // 搜索时是否模糊背景(默认YES)
    searchController.hidesNavigationBarDuringPresentation = NO; // 搜索时是否隐藏导航栏(默认YES)
    searchController.searchBar.placeholder = @"搜索";            // 占位文字
//    searchController.searchBar.delegate = self;                 // 可选:监听搜索栏事件
    searchController.searchBar.returnKeyType = UIReturnKeySearch;

// 设置键盘样式
    self.searchController.searchBar.keyboardType = UIKeyboardTypeDefault;

然后设置tableView的相关属性, 用来显示搜索结果

objc 复制代码
- (void) setupTableView {
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    self.tableView.dataSource = self;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:self.tableView];
}

这里我们设置的数组filteredData是根据搜索结果实时更新的, 所以我们实现协议方法返回cell的个数就返回数组filteredData.count

另外在cell的注册和设置时, 我们就显示当前的搜索结果, 如果没有输入显示所有结果

objc 复制代码
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.filteredData.count; // 始终用 filteredData
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"
                                                            forIndexPath:indexPath];
    cell.textLabel.text = self.filteredData[indexPath.row];
    return cell;
}

另外, 还有一个更重要的部分,就是让数组 filteredData 根据搜索框中的内容实时更新, 这里就需要用到上面提到的 UISearchController 的协议方法, - (void)updateSearchResultsForSearchController:(UISearchController *)searchController,在搜索框中删除或输入内容,就筛选结果对 filteredData 进行更新

代码如下:

objc 复制代码
// 用户输入时自动调用
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    NSLog(@"搜索框结果更新");
    NSString *text = searchController.searchBar.text;
    if (text.length == 0) {
        // 没有输入,显示全部
        self.filteredData = self.allData;
    } else {
        // 有输入,过滤数据
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", text];
        self.filteredData = [self.allData filteredArrayUsingPredicate:predicate];
    }
    // 刷新列表
    [self.tableView reloadData];
}

在上面的方法中, 用到了NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", text];, 这里使用了谓词, 由于笔者没有学习过谓词, 这里也不过多讲解, 就解释一下这段代码的意义

这行代码创建了一个 NSPredicate(谓词)对象,用于测试一个字符串是否包含另一个子串 (不区分大小写和变音符号)。它通常与 NSArrayfilteredArrayUsingPredicate: 方法配合使用,实现数组的模糊搜索过滤。

用这行代码就实现了根据搜索框中的输入对结果进行筛选

搜索栏的事件监听

UISearchBarDelegate 协议包含多个可选方法,用于监听 UISearchBar 的各种交互事件。

所以如果需要监听搜索栏的事件, 就需要遵守UISearchBarDelegate 协议, 还需要需要设置代理

objc 复制代码
searchController.searchBar.delegate = self;

开始编辑时调用

objc 复制代码
// 已经开始编辑(键盘已弹出)
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    NSLog(@"开始编辑");
}

是否允许编辑

如果返回 NO, 则不会有键盘弹出

objc 复制代码
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
    return YES;
}

是否允许结束编辑

objc 复制代码
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar {
    return YES;
}

如果返回 NO, 可以阻止收起键盘

结束编辑时调用

objc 复制代码
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
    NSLog(@"已经结束编辑");
}

搜索框中内容发生变化时调用

objc 复制代码
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    NSLog(@"遵守协议, 搜索框结果更新");
}

点击搜索按钮时调用

objc 复制代码
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    NSLog(@"点击了搜索按钮");
}

完整代码如下:

objc 复制代码
//
//  ViewController.m
//  UISearchController
//
//  Created by lose_sea on 2026/5/30.
//

#import "ViewController.h"
#import "MyViewController.h"


@interface ViewController () <UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, UITableViewDelegate>
// ViewController.m

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UISearchController *searchController;

@property (nonatomic, strong) NSArray<NSString *> *allData;       // 原始数据,永远不动
@property (nonatomic, strong) NSArray<NSString *> *filteredData;  // 显示用的数据

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"搜索";
    // Do any additional setup after loading the view.
    
    [self setData];
    [self setupTableView];
    [self setupSearchController];
    
    if (@available(iOS 26.0, *)) {
        self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementStacked;
    // 设置不允许将UISearchController放在工具栏中,保证UISearchController在上方的导航栏中
        self.navigationItem.searchBarPlacementAllowsToolbarIntegration = NO;
    }
}

- (void) setData {
    // ① 准备数据
    self.allData = @[@"Apple", @"Banana", @"Cherry", @"Date", @"Fig", @"Grape"];
    self.filteredData = self.allData;
}

- (void) setupSearchController {
    MyViewController* vc = [[MyViewController alloc] init];
    UISearchController *searchController = [[UISearchController alloc] initWithSearchResultsController: vc];

    searchController.searchResultsUpdater = vc;               // 设置结果更新代理
    searchController.obscuresBackgroundDuringPresentation = YES; // 搜索时是否模糊背景(默认YES)
    searchController.hidesNavigationBarDuringPresentation = YES; // 搜索时是否隐藏导航栏(默认YES)
    searchController.searchBar.placeholder = @"搜索";            // 占位文字
    searchController.searchBar.returnKeyType = UIReturnKeySearch;

    self.searchController = searchController;
    
    searchController.searchBar.delegate = self;                 // 可选:监听搜索栏事件
    
    // 将 searchBar 添加到导航栏
    self.navigationItem.searchController = searchController;
     
    // 设置键盘样式
    self.searchController.searchBar.keyboardType = UIKeyboardTypeDefault;
    
    // 可选:滚动时保持 searchBar 在顶部
    self.navigationItem.hidesSearchBarWhenScrolling = NO;
    
    UIBarButtonItem* item1 = [[UIBarButtonItem alloc] initWithTitle: @"back" style: UIBarButtonItemStylePlain target: self action: @selector(pressBtn)];
    self.navigationItem.rightBarButtonItem = item1;
}

- (void) pressBtn {
    NSLog(@"点击啦按钮");
}

- (void) setupTableView {
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:self.tableView];
}

// 用户输入时自动调用
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    NSLog(@"搜索框结果更新");
    
    NSString *text = searchController.searchBar.text;
    
    if (text.length == 0) {
        // 没有输入,显示全部
        self.filteredData = self.allData;
    } else {
        // 有输入,过滤数据
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", text];
        self.filteredData = [self.allData filteredArrayUsingPredicate:predicate];
    }
    // 刷新列表
    [self.tableView reloadData];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.filteredData.count; // 始终用 filteredData
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"
                                                            forIndexPath:indexPath];
    cell.textLabel.text = self.filteredData[indexPath.row];
    return cell;
}

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath: indexPath animated: YES];
}

#pragma mark - 搜索栏事件监听

// 询问是否允许开始编辑(返回 NO 可阻止键盘弹出)
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
    return YES;
}

// 已经开始编辑(键盘已弹出)
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    NSLog(@"开始编辑");
}

// 询问是否允许结束编辑(返回 NO 可阻止收起键盘)
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar {
    return YES;
}

// 已经结束编辑(键盘已收起)
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
    NSLog(@"已经结束编辑");
}

// 搜索框内的文字每次改变时调用(实时输入)
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    NSLog(@"遵守协议, 搜索框结果更新");
}

// 点击键盘上的"搜索"按钮(returnKeyType 为 UIReturnKeySearch)
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    NSLog(@"点击了搜索按钮");
}


// 点击"书签"按钮(需要设置 showsBookmarkButton = YES)
- (void)searchBarBookmarkButtonClicked:(UISearchBar *)searchBar {
    NSLog(@"点击"书签"按钮");
}

// 点击"结果列表"按钮(需要设置 showsResultsListButton = YES,已废弃)
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar {
    NSLog(@"点击了结果列表");
}

@end
相关推荐
心中有国也有家1 小时前
GE图引擎深度解析——CANN的计算图优化与执行引擎
人工智能·pytorch·python·学习·numpy
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_39:(Flexbox 弹性盒子核心机制)
前端·css·ui·html·tensorflow
GHL2842710903 小时前
换脸工作流学习
学习·ai
_李小白3 小时前
【android opencv学习笔记】Day 28: 滤波算法之中值滤波器
android·opencv·学习
飞翔中文网4 小时前
Java学习笔记之抽象类与接口(设计思想)
java·笔记·学习
土星碎冰机5 小时前
xxljob学习(大白话版本)
学习·运维开发
代码的小搬运工6 小时前
ZARA仿写
ios
吃好睡好便好6 小时前
说说免疫力的维护
学习·生活
凉、介6 小时前
深入理解 ARMv8-A|处理器模式与寄存器
笔记·学习·嵌入式·arm