自定义UISearchBar和UISearchDisplayController

来源:互联网 发布:计算机c语言二级考试 编辑:程序博客网 时间:2024/05/18 02:35

自定义UISearchBar和UISearchDisplayController

  • 自定义UISearchBar和UISearchDisplayController
    • 起因
    • 步骤
    • 效果图
    • 主要实现代码
      • 实现SearchTextField组件及代理
        • layoutSubviews代码说明
      • 实现CustomSearchBar相关组件
        • CustomSearchBar的主要组成
        • layoutSubviews代码说明
      • 实现CustomSearchDisplayController相关组件
        • CustomSearchDisplayController类组成
        • setActiveanimate主要代码说明
    • 结语

起因

近期由于公司项目升级,UI变化较大,之前项目中的搜索一直是使用的系统自带组件UISearchBar和UISearchDisplayController,然而根据UI设计图,该系统组件无法满足需求,故需要自定义该组件以实现类似微信的搜索功能,在百度无果的情况下,终于决定自己动手完全从头自定义,特写此文以提供思路给有同样需求的同行。

步骤

  1. 定义SearchTextField相关组件和代理
  2. 定义SearchBar相关组件和代理
  3. 定义SearchDisplayController相关组件和代理

效果图

按照惯例先上一张效果图,如下所示,由于重点在于搜索控件,所以tableView中的数据并非真实数据。
搜索框

主要实现代码

实现SearchTextField组件及代理

由于我需要的效果是搜索框图片位于左边和中央的位置,而且还需要一层边框,所以CustomSearchTextField主要组成有:CustomSearchTextFieldBackgroundView(背景层),UISearchTextField(输入框),UIImageView(搜索提示按钮),UILabel(占位文本标签)

@interface CustomSearchTextField ()@property(nonatomic, strong) UIButton* _searchTextClearButton;@property(nonatomic, strong) UIView* _searchTextFieldBackgroudView;@property(nonatomic, strong) UILabel* _searchTextFieldPlaceHolderLabel;@property(nonatomic, strong) UIImageView* _searchTextFieldIcon;@property(nonatomic, strong) UITextField* _searchTextField;@end

省略了各个UI控件的初始化代码,主要的布局代码在layoutSubviews中,代码如下所示:

[UIView animateWithDuration:0.25f animations:^{    [__searchTextFieldBackgroudView setFrame:self.bounds];    if(__searchTextField.isFirstResponder) {        [__searchTextFieldIcon setFrame:CGRectMake(10, __searchTextFieldIcon.frame.origin.y, __searchTextFieldIcon.frame.size.width, __searchTextFieldIcon.frame.size.height)];        [__searchTextField setFrame:CGRectMake(__searchTextFieldIcon.frame.origin.x + __searchTextFieldIcon.frame.size.width + 5, __searchTextField.frame.origin.y, __searchTextFieldBackgroudView.bounds.size.width - __searchTextFieldIcon.frame.origin.x - __searchTextFieldIcon.frame.size.width - 15, __searchTextField.frame.size.height)];        [__searchTextFieldPlaceHolderLabel setFrame:__searchTextField.bounds];        }else {            [__searchTextFieldIcon setCenter:CGPointMake(__searchTextFieldBackgroudView.bounds.size.width / 2 - 10, __searchTextFieldBackgroudView.bounds.size.height / 2)];            [__searchTextField setFrame:__searchTextFieldBackgroudView.bounds];            [__searchTextFieldPlaceHolderLabel setFrame:CGRectMake(__searchTextFieldBackgroudView.bounds.size.width / 2, __searchTextField.frame.origin.y, __searchTextFieldBackgroudView.bounds.size.width / 2 - 5, __searchTextField.bounds.size.height)];    }}];

layoutSubviews代码说明

该段代码主要是做了两件事

  1. 如果该UITextField是firstResponder : 调整各个控件的frame,以动画形式使得搜索图标位于左侧,UITextField调整至合适大小,UILabel也调整到文本框左侧最开始处

  2. 如果该UITextField不是firstResponder : 调整各个控件的frame,以动画形式使得UITextField的frame充满整个背景层,搜索图标和UILabel调整至整个CustomSearchTextField的正中央

    SearchTextField的代码到此就结束了,主要就是做了对UITextField的再封装,及对图标和占位文本的处理等等。

实现CustomSearchBar相关组件

参考UISearchBar头文件,该类主要实现了一个基本的SearchBar,包括对UITextField的基本状态的处理,留出外部调用的代理接口。

CustomSearchBar的主要组成

CustomSearchTextField主要组成是由一个背景层,一个搜索框,上下两条分隔线,取消按钮等组成,这些控件都放在分类里面,外部不可见,如下代码所示:

@interface CustomSearchBar () <CustomSearchTextFieldDelegate>@property(nonatomic, strong) CMSearchTextField* searchTextField;@property(nonatomic, strong) UIView* searchBarBackgroudView;@property(nonatomic, strong) UIImageView* searchBarTopSeperatorLine;@property(nonatomic, strong) UIImageView* searchBarBottomSeperatorLine;@property(nonatomic, strong) UIButton* cancelSearchButton;@property(nonatomic, assign) BOOL shouldShowCancelButton;@end

而由于本人水平所限,不知道系统的UISearchBar是如何做到看上去似乎与UISearchDisplayController毫无关联,故我在CustomSearchBar中保留了一个CustomSearchDisplayController属性,使得在输入框状态变化时能调用CustomSearchDisplayController中的相关代理方法,故CustomSearchBar类如下代码所示:

@interface CustomSearchBar : UIView@property(nonatomic, copy) NSString* placeholder;@property(nonatomic, copy) NSString* text;@property(nonatomic, weak) CMSearchDisplayController* searchDisplayController;@property(nonatomic, assign) id<CMSearchBarDelegate>  delegate;@end

其中最主要的frame调整依然是在layoutSubViews方法中,相关调整代码如下:

-(void)layoutSubviews {    [UIView animateWithDuration:0.25f animations:^{        [_searchBarBackgroudView setFrame:self.bounds];        [_searchBarTopSeperatorLine setFrame:CGRectMake(0, 0, _searchBarBackgroudView.bounds.size.width, kSingleLine)];        [_searchBarBottomSeperatorLine setFrame:CGRectMake(0, _searchBarBackgroudView.bounds.size.height - kSingleLine, _searchBarBackgroudView.bounds.size.width, kSingleLine)];        if(_shouldShowCancelButton) {            [_searchTextField setFrame:CGRectMake(20, 25,  _searchBarBackgroudView.bounds.size.width - 60, _searchBarBackgroudView.bounds.size.height - 30)];        }else {            [_cancelSearchButton setHidden:YES];            [_searchTextField setFrame:CGRectMake(20, 5, _searchBarBackgroudView.bounds.size.width - 40, _searchBarBackgroudView.bounds.size.height - 10)];        }    } completion:^(BOOL finished) {        if(finished) {            dispatch_async(dispatch_get_main_queue(), ^{                if(_shouldShowCancelButton) {                    [_cancelSearchButton setHidden:NO];                    [_cancelSearchButton setFrame:CGRectMake(_searchBarBackgroudView.bounds.size.width - 5 - _cancelSearchButton.bounds.size.width, _cancelSearchButton.frame.origin.y, _cancelSearchButton.bounds.size.width, _cancelSearchButton.bounds.size.height)];                    [_cancelSearchButton setCenter:CGPointMake(_cancelSearchButton.center.x, _searchTextField.center.y)];                }            });        }    }];    return [super layoutSubviews];}

layoutSubviews代码说明

变量__shouldShowCancelButton,是用于指示是否显示取消按钮的BOOL变量,该按钮只有在激活状态下才会显示,故此变量可用于指示搜索框是否处于激活状态下(所谓激活状态就是指UITextField是否成为firstResponder),如果是处于激活状态下,调整整个CustomSearchBackgroundView子控件的frame下移20个单位,即状态栏的高度,如果处于未激活状态,则调整至CustomSearchBar的正中央。

实现CustomSearchDisplayController相关组件

CustomSearchDisplayController类组成

参考UISearchDisplayController类中的方法,CustomSearchDisplayController类情况如下所示:

@interface CMSearchDisplayController : NSObject-(instancetype)initWithSearchBar:(CMSearchBar*)searchBar contentsController:(UIViewController*)viewController;@property(nonatomic, strong, readonly) CMSearchBar* searchBar;@property(nonatomic, strong, readonly) UIViewController* searchContentsController;@property(nonatomic, strong, readonly) UITableView* searchResultsTableView;@property(nonatomic, assign) id<UITableViewDataSource> searchResultsDataSource;@property(nonatomic, assign) id<UITableViewDelegate> searchResultsDelegate;@property(nonatomic, assign) id<CMSearchDisplayControllerDelegate> delegate;@property (nonatomic, assign) BOOL isActive;-(void)setActive:(BOOL)bActive;-(void)setActive:(BOOL)bActive animated:(BOOL)animated;@end

其中主要包括一个searchBar,一个tableView,一个contentsController实例,还有用于tableview的dataSource和delegate的两个代理及用于外部实现的SearchDisplayControllerDelegate,其中最主要的代码便要数setActive:animated中的处理,如下所示:

-(void)setActive:(BOOL)bActive animated:(BOOL)animated {    _isActive = bActive;    CGFloat animateDuring = animated ? 0.25f : 0.0f;        if(_searchContentsController.navigationController == nil)        return ;    if(bActive) {        [_searchContentsController.navigationController.navigationBar setFrame:CGRectMake(0, -44, _searchContentsController.navigationController.navigationBar.bounds.size.width, _searchContentsController.navigationController.navigationBar.bounds.size.height)];        UIImageView* dimmView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 44, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];        [dimmView setUserInteractionEnabled:YES];        [dimmView setImage:[[self createImageWithColor:[UIColor colorWithRed:247.0f / 255.0f green:247.0f / 255.0f blue:247.0f / 255.0f alpha:0.9f] size:dimmView.bounds.size] applyLightEffect]];        [[UIApplication sharedApplication].keyWindow addSubview:dimmView];        _searchBarPreviousSuperView = _searchBar.superview;        _searchBarPreviousSuperRect = _searchBar.frame;        [_searchBar removeFromSuperview];        [dimmView addSubview:_searchBar];        [_searchBar setFrame:CGRectMake(0, 0, _searchBar.bounds.size.width, _searchBar.bounds.size.height + 20)];        [UIView animateWithDuration:animateDuring animations:^{            [_searchContentsController.view setFrame:CGRectMake(0, -44, _searchContentsController.view.bounds.size.width, _searchContentsController.view.bounds.size.height + 44)];            [dimmView setFrame:CGRectMake(0, 0, dimmView.bounds.size.width, dimmView.bounds.size.height)];        } completion:^(BOOL finished) {            if(finished) {                dispatch_async(dispatch_get_main_queue(), ^{                    [_searchResultsTableView setFrame:CGRectMake(0, _searchBar.bounds.size.height, dimmView.bounds.size.width, dimmView.bounds.size.height - _searchBar.bounds.size.height)];                    [dimmView addSubview:_searchResultsTableView];                    if([self.delegate respondsToSelector:@selector(searchDisplayControllerDidBeginSearch:)])                        [self.delegate searchDisplayControllerDidBeginSearch:self];                });            }        }];    }else {        [_searchResultsTableView removeFromSuperview];        [UIView animateWithDuration:0.25f animations:^{            [_searchContentsController.navigationController.navigationBar setFrame:CGRectMake(0, 20, _searchContentsController.navigationController.navigationBar.bounds.size.width, _searchContentsController.navigationController.navigationBar.bounds.size.height)];            [_searchBar.superview setFrame:CGRectMake(0, 64, _searchBar.superview.bounds.size.width, _searchBar.superview.bounds.size.height)];            [_searchContentsController.view setFrame:CGRectMake(0, 0, _searchContentsController.view.bounds.size.width, _searchContentsController.view.bounds.size.height - 44)];            [_searchBar setFrame:CGRectMake(0, 0, _searchBar.bounds.size.width, _searchBar.bounds.size.height - 20)];        }completion:^(BOOL finished) {            if(finished) {               dispatch_async(dispatch_get_main_queue(), ^{                    [_searchBar.superview removeFromSuperview];                    [_searchBarPreviousSuperView addSubview:_searchBar];                    if([self.delegate respondsToSelector:@selector(searchDisplayControllerDidEndSearch:)])                        [self.delegate searchDisplayControllerDidEndSearch:self];                });            }        }];    }    return ;}

setActive:animate:主要代码说明

contentsController指的是内容展示viewController,一般传递的是持有者本身,在此方法中主要做了以下几件事:

  1. 如果contentsController没有导航栏,则不需要调整任何控件

  2. 如果contentsController有导航栏,且处于激活状态
    使用动画方式使得该UINavigationBar实例往上移动44像素个单位,然后创建遮罩层,为方便起见,我直接将他加在当前的keyWindow当中,记录CustomSearchBar的superView及在superView中的frame,再将它从父视图中移除,并加入到遮罩层dimmView当中,调整坐标为0,0,拉长20个高度以覆盖整个状态栏,显示取消按钮。

  3. 如果contentsController有导航栏,且自于未激活状态
    使用动画的方式使得该UINavigationBar实例重新移动到合适位置,然后遮罩层同步下移同样的高度,调整CustomSearchBar的高度,使之减少20个像素单位,回到未激活状态的位置,动画完成之后,删除遮罩层dimmView,CustomSearchBar从其superView(dimmView)中移出,并加入到原来它的superView当中,并设置位置为在原来superView当中的位置,隐藏取消按钮。

结语

由于本人水平所限,代码写的并不十分美观,也并不是十分高效,iOS大神们肯定会有更好的解决方案,本文旨在为那些需要此功能但还没有思路的同行们提供一点意见,iOS大神请无视,最后感谢所有阅读此文的人。

0 0
原创粉丝点击