iOS UISearchBar实现

项目需求要求实现带历史记录的搜索功能,经过无数次网上查找资料和几次修改,终于实现了,菜鸟一枚,需记录下来以便以后查阅。代码中的布局使用Masonry,另外还使用了ReactiveCocoa来处理删除某条历史记录、清空历史记录等操作的消息传递。

搜索框UI自定义

A574C7A4-8948-4BC9-B033-58944A3AD32C.png

上图是要实现的效果,下面来看看具体的实现。这里我使用的是系统的UISearchBar控件,首先要考虑的是如何实现搜索框背景颜色,光标颜色的改变,以及如何将系统放大镜等图片替换成我们想要的样子。
添加UISearchBar在VC上,运行一下,我们可以看到其效果:


_searchBar = [[UISearchBar alloc] init];
_searchBar.backgroundColor = [UIColor clearColor];
_searchBar.showsCancelButton = NO;
[_searchBgView addSubview:_searchBar];

3C74223F-D9A6-4DE0-A1A0-F387D4C4FEC7.png

发现将searchBar的背景颜色设置为clearColor并不能去掉其灰色的边框,查看文档,发现其还有两个关于颜色设置的属性,其中tintColor可以用来改变输入光标的的颜色,另外文档里说可以使用-barTintColor来调整搜索框的背景颜色。

/*
 The behavior of tintColor for bars has changed on iOS 7.0. It no longer affects the bar's background
 and behaves as described for the tintColor property added to UIView.
 To tint the bar's background, please use -barTintColor.
 */
@property(null_resettable, nonatomic,strong) UIColor *tintColor;
@property(nullable, nonatomic,strong) UIColor *barTintColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;  // default is nil

于是设置searchBar.barTintColor = [UIColor whiteColor];运行效果如下图所示:

C6000359-8D0A-4315-9D81-4B8AD4861358.png

结果是出现上线两条线,并没有完全消除背景颜色,根据网友资料,在控制台通过命令打印出UISearchBar的视图层次结构如下:

<UISearchBar: 0x7fb6586b6ca0; frame = (10 30; 355 30); text = ''; tintColor = UIDeviceRGBColorSpace 1 0.5 0 1; gestureRecognizers = <NSArray: 0x7fb6586bab20>; layer = <CALayer: 0x7fb658609540>>
   | <UIView: 0x7fb6586b9e00; frame = (0 0; 355 30); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x7fb65869f110>>
   |    | <UISearchBarBackground: 0x7fb6586baff0; frame = (0 0; 355 30); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fb6586bb4c0>>
   |    | <UISearchBarTextField: 0x7fb658525ce0; frame = (0 0; 0 0); text = ''; clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x7fb658505960>>
   |    |    | <_UISearchBarSearchFieldBackgroundView: 0x7fb658419960; frame = (0 0; 0 0); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x7fb658409dc0>>

可以移除UISearchBarBacground来移除背景颜色

        for (UIView *subView in _searchBar.subviews) {
            if ([subView isKindOfClass:[UIView  class]]) {
                [[subView.subviews objectAtIndex:0] removeFromSuperview];
            }
        }

移除了背景颜色,接下来要修改输入框的背景颜色和放大镜图片,此时UIView中就剩下一个UITextField,系统默认的放大镜图片可通过设置UITextField的leftView来设置,打印UITextField的leftView,可以看出其是一个UIImageView,具体信息如下

(lldb) po textField.leftView
<UIImageView: 0x7f8df3f1b870; frame = (0 0; 13 13); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7f8df3f13d40>>

具体实现代码和实现效果如下:

        _searchBar = [[UISearchBar alloc] init];
        _searchBar.backgroundColor = [UIColor clearColor];
        _searchBar.showsCancelButton = NO;
        _searchBar.tintColor = [UIColor orangeColor];
        _searchBar.placeholder = @"搜索感兴趣的内容";
        
        for (UIView *subView in _searchBar.subviews) {
            if ([subView isKindOfClass:[UIView  class]]) {
                [[subView.subviews objectAtIndex:0] removeFromSuperview];
                if ([[subView.subviews objectAtIndex:0] isKindOfClass:[UITextField class]]) {
                    UITextField *textField = [subView.subviews objectAtIndex:0];
                    textField.backgroundColor = [UIColor colorWithRed:240/255.0 green:240/255.0 blue:240/255.0 alpha:1];
                    
                    //设置输入框边框的颜色
//                    textField.layer.borderColor = [UIColor blackColor].CGColor;
//                    textField.layer.borderWidth = 1;
                    
                    //设置输入字体颜色
//                    textField.textColor = [UIColor lightGrayColor];
                    
                    //设置默认文字颜色
                    UIColor *color = [UIColor grayColor];
                    [textField setAttributedPlaceholder:[[NSAttributedString alloc] initWithString:@"搜索感兴趣的内容"
                                                                                        attributes:@{NSForegroundColorAttributeName:color}]];
                    //修改默认的放大镜图片
                    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 13, 13)];
                    imageView.backgroundColor = [UIColor clearColor];
                    imageView.image = [UIImage imageNamed:@"gww_search_ misplaces"];
                    textField.leftView = imageView;
                }
            }
        }
        
        [_searchBgView addSubview:_searchBar];
4F12C076-C4B5-4D12-A980-6950BA32274C.png

有些需求可能会要求限制输入的次数,这里限制字数20,超出及给个弹窗提示,具体实现如下:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if ([searchText length] > 20) {
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:@"字数不能超过20" preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *alertAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
        [alertController addAction:alertAction];
        [self presentViewController:alertController animated:nil completion:nil];
        [_searchBar setText:[searchText substringToIndex:20]];
    }
}

历史记录功能实现

搜索框已经按照我们想要的样式修改完毕,接下来要实现的是历史记录功能。搜索历史的弹出使用DXPopver,这里我是写了一个tableViewController放到popper中来进行历史记录的展示。首先我们需要定义一个数组来存储搜索记录,同时要将记录存储到端上,这里定义了一个单例用来存储记录:


#import "APPUserDefaults.h"

@implementation APPUserDefaults

+(id)instance
{
    static APPUserDefaults *_instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        _instance = [[APPUserDefaults alloc] init];
    });
    return _instance;
}

-(void)setUserSearchHistory:(NSMutableArray *)array
{
    [[NSUserDefaults standardUserDefaults] setObject:array forKey:@"SearchRecord"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

-(NSMutableArray *)getUserSearchHistory
{
    return [[NSUserDefaults standardUserDefaults] objectForKey:@"SearchRecord"];
}
@end

存储好搜索记录之后,每次点击搜索框,如果之前有历史记录就要显示,如没有则不用显示

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    // when we start to write sth, the record should display to us
    if (_historyArray.count != 0) {
        _historyViewController.historyRecords = _historyArray;
        [self showTheHistoryRecords];
        [self.searchBar becomeFirstResponder];
    }
}
- (void)showTheHistoryRecords
{
    CGPoint startPoint = CGPointMake(CGRectGetWidth(self.view.frame), 64);
    [self.popover showAtPoint:startPoint
               popoverPostion:DXPopoverPositionDown
              withContentView:self.historyViewController.view
                       inView:self.view];
    self.popover.didDismissHandler = ^{
        
    };
}

- (void)hiddenTheHistoryRecords
{
    [self.popover dismiss];
}

搜索的时候要记录搜索历史,在点击空格键,在点击搜索时,没有匹配的内容出现,但是搜索历史里面会留下一行“空白”的记录,点击多下空格时,同样会出现这个问题,然而我们的搜索历史并不需要存储这个空格。

EB317CA8-DE59-48DD-8263-6B6F2CF28EF2.png

这里我们就要识别输入的字符是不是空格,如果是空格,就不对其进行存储,因为可能会出现多个空格的情况,就不能简单的使用

[_searchBar.text isEqualToString:@" "];

这里使用NSPredicate谓词来判断输入的字符是否为一个或者多个空格

NSString *regex = @"\\s*";

这里\s是正则表达式,匹配任何空白字符
*,匹配当期表达式一次或者多次

    NSString *regex = @"\\s*";
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    if (![_historyArray containsObject:_searchBar.text] && ![pred evaluateWithObject:_searchBar.text]) {
        [_historyArray insertObject:_searchBar.text atIndex:0];
    }
    

本地内容及时搜索

许多 app 都支持对本地内容的及时搜索,这里简单实现了该功能,直接上代码

    words = [words uppercaseString];  //搜索关键字,可以是中文,也可以是拼音或者缩写
    NSMutableArray *array = [NSMutableArray array]; //用来存储搜索结果
    //self.dataArray 中存储着本地信息,逐项对比,看是不是我们所要的结果,self.cachePinyinDict 中存储着本地信息的拼音转换结果 例如 影视对应 yingshi ys
    for (NSString *name in self.dataArray) {
        NSString *pinyin = [self.cachePinyinDict objectForKey:name];
        pinyin = pinyin.uppercaseString;
        if ([name rangeOfString:words].length > 0) {
            [array addObject:name];
        }else if ([pinyin rangeOfString:words].length > 0 && [pinyin rangeOfString:words].location == 0) {
            [array addObject:name];
        }
    }

拼音的转化采用了第三方 PinyinHelper 工具

- (void)cachePinyin
{
    if (!self.dataArray.count) return;
    
    for (NSString *name in self.dataArray) {
        NSString *p = [self.cachePinyinDict objectForKey:name];
        if (p == nil) {
            p = [[PinyinHelper getPinyin:name  isXingshi:NO] join:@" "];
            self.cachePinyinDict[name] = p;
        }
    }
}

具体实现结果

9FC0286E-6CEC-47EB-8678-B55E152054EC.png

具体的功能已经介绍差不多了,具体demo地址github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,262评论 4 61
  • Swift版本点击这里欢迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh阅读 25,590评论 7 249
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,761评论 25 709
  • 不是因为年纪大了,不是因为朋友都结婚了,还有的生孩子了,不是因为家人着急了,不是因为所有人觉得我应该了;我一直都期...
    砰砰砰_砰阅读 178评论 1 0
  • 今天的早课,是查兰英老师的《迩之事父,远之事君》,这个主题给我的第一印象是,好有诗意,所以在做图片的时候,特别找了...
    叶赖子阅读 390评论 3 1