标签:
近期由于公司项目升级,UI变化较大,之前项目中的搜索一直是使用的系统自带组件UISearchBar和UISearchDisplayController,然而根据UI设计图,该系统组件无法满足需求,故需要自定义该组件以实现类似微信的搜索功能,在百度无果的情况下,终于决定自己动手完全从头自定义,特写此文以提供思路给有同样需求的同行。
按照惯例先上几张效果图,如下:
由于重点在于搜索控件,所以tableView中的数据并非真实数据。
由于我需要的效果是搜索框图片位于左边和中央的位置,而且还需要一层边框,所以CustomSearchTextField主要组成有:CustomSearchTextFieldBackgroundView(背景层),UISearchTextField(输入框),UIImageView(搜索提示按钮),UILabel(占位文本标签)
1 @interface CMSearchTextField () 2 @property(nonatomic, strong) UIButton* _searchTextClearButton;<br/> 3 @property(nonatomic, strong) UIView* _searchTextFieldBackgroudView;<br/> 4 @property(nonatomic, strong) UILabel* _searchTextFieldPlaceHolderLabel;<br/> 5 @property(nonatomic, strong) UIImageView* _searchTextFieldIcon;<br/> 6 @property(nonatomic, strong) UITextField* _searchTextField;<br/> 7 @end
省略了各个UI控件的初始化代码,layoutSubviews中的代码如下:
1 [UIView animateWithDuration:0.25f animations:^{ 2 [__searchTextFieldBackgroudView setFrame:self.bounds]; 3 if(__searchTextField.isFirstResponder) { 4 [__searchTextFieldIcon setFrame:CGRectMake(10, __searchTextFieldIcon.frame.origin.y, __searchTextFieldIcon.frame.size.width, __searchTextFieldIcon.frame.size.height)]; 5 [__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)]; 6 [__searchTextFieldPlaceHolderLabel setFrame:__searchTextField.bounds]; 7 }else { 8 [__searchTextFieldIcon setCenter:CGPointMake(__searchTextFieldBackgroudView.bounds.size.width / 2 - 10, __searchTextFieldBackgroudView.bounds.size.height / 2)]; 9 [__searchTextField setFrame:__searchTextFieldBackgroudView.bounds]; 10 [__searchTextFieldPlaceHolderLabel setFrame:CGRectMake(__searchTextFieldBackgroudView.bounds.size.width / 2, __searchTextField.frame.origin.y, __searchTextFieldBackgroudView.bounds.size.width / 2 - 5, __searchTextField.bounds.size.height)]; 11 } 12 }];
该段代码主要是做了两件事
1. 如果该UITextField是firstResponder : 调整各个控件的frame,以动画形式使得搜索图标位于左侧,UITextField调整至合适大小,UILabel也调整到文本框左侧最开始处
2. 如果该UITextField不是firstResponder : 调整各个控件的frame,以动画形式使得UITextField的frame充满整个背景层,搜索图标和UILabel调整至整个CustomSearchTextField的正中央
参考UISearchBar头文件,该类主要实现了一个基本的SearchBar,包括对UITextField的基本状态的处理,留出外部调用的代理接口。
CustomSearchTextField主要组成是由一个背景层,一个搜索框,上下两条分隔线,取消按钮等组成,如下代码所示:
1 @interface CMSearchBar () <CMSearchTextFieldDelegate><br/> 2 @property(nonatomic, strong) CMSearchTextField* searchTextField;<br/> 3 @property(nonatomic, strong) UIView* searchBarBackgroudView;<br/> 4 @property(nonatomic, strong) UIImageView* searchBarTopSeperatorLine;<br/> 5 @property(nonatomic, strong) UIImageView* searchBarBottomSeperatorLine;<br/> 6 @property(nonatomic, strong) UIButton* cancelSearchButton;<br/> 7 @property(nonatomic, assign) BOOL shouldShowCancelButton;<br/> 8 @end
而由于本人水平所限,不知道系统的UISearchBar是如何做到看上去似乎与UISearchDisplayController毫无关联,故我在CustomSearchBar中保留了一个CustomSearchDisplayController属性,使得在输入框状态变化时能调用CustomSearchDisplayController中的相关代理方法,如下代码所示:
@interface CMSearchBar : 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)]; ifshouldShowCancelButton) { [_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, <u>cancelSearchButton.bounds.size.height)]; [cancelSearchButton setCenter:CGPointMake(_cancelSearchButton.center.x, _searchTextField.center.y)]; } }); } }]; return [super layoutSubviews]; }
变量__shouldShowCancelButton,是用于指示是否显示取消按钮的BOOL变量,该按钮只有在激活状态下才会显示,故此变量可用于指示搜索框是否处于激活状态下(所谓激活状态就是指UITextField是否成为firstResponder),如果是处于激活状态下,调整整个CustomSearchBackgroundView子控件的frame下移20个单位,即状态栏的高度,如果处于未激活状态,则调整至CustomSearchBar的正中央
参考UISearchDisplayController类中的方法,CustomSearchDisplayController类情况如下所示:
1 @interface CMSearchDisplayController : NSObject 2 -(instancetype)initWithSearchBar:(CMSearchBar*)searchBar contentsController:(UIViewController*)viewController; 3 @property(nonatomic, strong, readonly) CMSearchBar* searchBar; 4 @property(nonatomic, strong, readonly) UIViewController* searchContentsController; 5 @property(nonatomic, strong, readonly) UITableView* searchResultsTableView; 6 @property(nonatomic, assign) id<UITableViewDataSource> searchResultsDataSource; 7 @property(nonatomic, assign) id<UITableViewDelegate> searchResultsDelegate; 8 @property(nonatomic, assign) id<CMSearchDisplayControllerDelegate> delegate; 9 @property (nonatomic, assign) BOOL isActive; 10 -(void)setActive:(BOOL)bActive; 11 -(void)setActive:(BOOL)bActive animated:(BOOL)animated; 12 @end
其中主要包括一个searchBar,一个tableView,一个contentsController实例,还有用于tableview的dataSource和delegate的两个代理及用于外部实现的SearchDisplayControllerDelegate,其中最主要的代码便要数setActive:animated中的处理,如下所示:
1 -(void)setActive:(BOOL)bActive animated:(BOOL)animated { 2 isActive = bActive; 3 CGFloat animateDuring = animated ? 0.25f : 0.0f; 4 if(searchContentsController.navigationController == nil) 5 return ; 6 if(bActive) { 7 [_searchContentsController.navigationController.navigationBar setFrame:CGRectMake(0, -44, _searchContentsController.navigationController.navigationBar.bounds.size.width, _searchContentsController.navigationController.navigationBar.bounds.size.height)]; 8 UIImageView* dimmView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 44, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)]; 9 [dimmView setUserInteractionEnabled:YES]; 10 [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]]; 11 [[UIApplication sharedApplication].keyWindow addSubview:dimmView]; 12 _searchBarPreviousSuperView = _searchBar.superview; 13 _searchBarPreviousSuperRect = searchBar.frame; 14 [searchBar removeFromSuperview]; 15 [dimmView addSubview:searchBar]; 16 [searchBar setFrame:CGRectMake(0, 0, _searchBar.bounds.size.width, searchBar.bounds.size.height + 20)]; 17 [UIView animateWithDuration:animateDuring animations:{ 18 [searchContentsController.view setFrame:CGRectMake(0, -44, _searchContentsController.view.bounds.size.width, searchContentsController.view.bounds.size.height + 44)]; 19 [dimmView setFrame:CGRectMake(0, 0, dimmView.bounds.size.width, dimmView.bounds.size.height)]; 20 } completion:BOOL finished { 21 if(finished) { 22 dispatch_async(dispatch_get_main_queue(), { 23 [searchResultsTableView setFrame:CGRectMake(0, _searchBar.bounds.size.height, dimmView.bounds.size.width, dimmView.bounds.size.height - searchBar.bounds.size.height)]; 24 [dimmView addSubview:searchResultsTableView]; 25 if([self.delegate respondsToSelector:@selector(searchDisplayControllerDidBeginSearch:)]) 26 [self.delegate searchDisplayControllerDidBeginSearch:self]; 27 }); 28 } 29 }]; 30 }else { 31 [searchResultsTableView removeFromSuperview]; 32 [UIView animateWithDuration:0.25f animations:{ 33 [searchContentsController.navigationController.navigationBar setFrame:CGRectMake(0, 20, _searchContentsController.navigationController.navigationBar.bounds.size.width, searchContentsController.navigationController.navigationBar.bounds.size.height)]; 34 [searchBar.superview setFrame:CGRectMake(0, 64, _searchBar.superview.bounds.size.width, searchBar.superview.bounds.size.height)]; 35 [searchContentsController.view setFrame:CGRectMake(0, 0, _searchContentsController.view.bounds.size.width, searchContentsController.view.bounds.size.height - 44)]; 36 [searchBar setFrame:CGRectMake(0, 0, _searchBar.bounds.size.width, searchBar.bounds.size.height - 20)]; 37 }completion:BOOL finished { 38 if(finished) { 39 dispatch_async(dispatch_get_main_queue(), { 40 [searchBar.superview removeFromSuperview]; 41 [searchBarPreviousSuperView addSubview:searchBar]; 42 if([self.delegate respondsToSelector:@selector(searchDisplayControllerDidEndSearch:)]) 43 [self.delegate searchDisplayControllerDidEndSearch:self]; 44 }); 45 } 46 }]; 47 } 48 return ; 49 }
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大神请无视,最后感谢所有阅读此文的人。
从头自定义SearchBar和SearchDisplayController
标签:
原文地址:http://www.cnblogs.com/wangluofan/p/5094453.html