美文网首页
京东搜索框的实现(深度定制)

京东搜索框的实现(深度定制)

作者: 简_爱SimpleLove | 来源:发表于2017-06-06 17:12 被阅读227次

要实现这个搜索框,我们需要先看一下效果,然后再进行拆分,分步实现,这样就没有你想想中的那么难了。

从这个图


图一

点击搜索框到


图二 这个图的时候
  1. 搜索框被拉伸了,而且有动画的效果
  2. 最右边的扫一扫隐藏,最左边的消息按钮,先是缩小,再然后放大,并且文字改为“取消”
  3. 在第二个图的时候,滑动下面历史记录键盘的时候,键盘隐藏
  4. 再点击取消按钮回到第一个页面的时候,取消按钮缩小,并且放大成为一个有图片的消息按钮,并且搜索框缩短,扫一扫按钮显示

分析:

  1. 第一个图的搜索框并不是一个textField,而是一个label加上的一个图片,然后再给整个加上了一个手势,当点击的时候进行界面切换(实际上第一个搜索框也没有起到任何的搜索作用,当一点击的时候就跳到了第二图。这告诉我们好多实际上我们看起来是一个东西的事物,其实是两个不同的事物)
  2. 这两个图都并没有用到导航栏,而是将导航栏隐藏了,如果是在导航栏上面进行操作,会有很多不便
  3. 封装了继承自UIViewController的searchController和继承自UITextField的searchBar

下面说下需要注意的几点:

注意1

封装button,一种是没有图片的,一种是有图片和文字的,图片在上,文字在下,这也相当于是特别定制的button

#import <UIKit/UIKit.h>

@interface EOCButton : UIButton
@property(nonatomic, assign)BOOL noImage;
@end
#import "EOCButton.h"

@implementation EOCButton

- (void)layoutSubviews {
    [super layoutSubviews];
    // 当没有设置noImage这个属性的时候,默认noImage为nil,即有图片
    if (!_noImage) {
        self.imageView.frame = CGRectMake(6.f, 0.f, self.eoc_width-12.f, self.eoc_width-12.f);
        self.titleLabel.frame = CGRectMake(0.f, self.eoc_height-12.f, self.eoc_width, 12.f);
        self.titleLabel.font = [UIFont systemFontOfSize:9.f];
    } else {
        NSLog(@"no image");
        self.titleLabel.frame = CGRectMake(0.f, 0.f, self.eoc_width, self.eoc_height);
        self.titleLabel.font = [UIFont systemFontOfSize:14.f];
    }
    self.titleLabel.textAlignment = NSTextAlignmentCenter;
    //纵横适配,使一直保持居中
    self.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
    self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
//    [self setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
}
@end
注意2

封装上面这一块,相当于导航栏,导航栏的高度

#import <UIKit/UIKit.h>
#import "EOCButton.h"

@interface EOCNavigationView : UIView
typedef void (^btnBlock)();
@property(nonatomic, strong)UIImageView *searchView;
@property(nonatomic, strong)EOCButton *messageBtn;
@property(nonatomic, strong)EOCButton *scanBtn;
@property(nonatomic, strong)UIButton *audioBtn;
@property(nonatomic, strong)btnBlock scanActionBlock;
@property(nonatomic, strong)btnBlock audioActionBlock;
@property(nonatomic, strong)btnBlock searchActionBlock;
@property(nonatomic, strong)btnBlock messageActionBlock;
@end
#import "EOCNavigationView.h"

@implementation EOCNavigationView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor clearColor];
        [self setNavigationView];
    }
    return self;
}

- (void)setNavigationView
{
    
    /*
     这里searchView、button的frame是根据Debug view Hierarchy 查出取消按钮和搜索框的间距,以及搜索框高度来计算出来的
     */
    
    //创建searchBar 把它添加到UINavigationBar的titleView里
    _searchView = [[UIImageView alloc] initWithFrame:CGRectMake(50.f, 8.f, SCREENSIZE.width-103.f, 28.f)];
    _searchView.userInteractionEnabled = YES;
    UIImage *searchBg = [UIImage imageNamed:@"searchBar_white"];
    searchBg = [searchBg stretchableImageWithLeftCapWidth:30.f topCapHeight:0.f]; //左边30不拉伸
    _searchView.image = searchBg;
    
    //创建placeHolder
    UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(30.f, 0.f, _searchView.frame.size.width-30.f, _searchView.frame.size.height)];
    textLabel.backgroundColor = [UIColor clearColor];
    textLabel.textAlignment = NSTextAlignmentLeft;
    textLabel.textColor = [UIColor whiteColor];
    textLabel.font = [UIFont systemFontOfSize:14.0f];
    textLabel.text = @"八点钟学院";
    [_searchView addSubview:textLabel];
    
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(searchAction)];
    [_searchView addGestureRecognizer:tapGesture];
    [self addSubview:_searchView];
    
    //添加语音按钮
    _audioBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    UIImage *audioImage = [UIImage imageNamed:@"voice_white"];
    CGFloat audioBtnWidth = 12.f;
    CGFloat audioBtnHeight = audioImage.size.height*audioBtnWidth/audioImage.size.width;
    _audioBtn.frame = CGRectMake(_searchView.frame.size.width-20.f, (30-audioBtnHeight)/2, audioBtnWidth, audioBtnHeight);
    [_audioBtn setBackgroundImage:audioImage forState:UIControlStateNormal];
    [_audioBtn addTarget:self action:@selector(audioAction) forControlEvents:UIControlEventTouchUpInside];
    [_searchView addSubview:_audioBtn];
    
    
    //创建扫描按钮
    _scanBtn = [EOCButton buttonWithType:UIButtonTypeCustom];
    _scanBtn.frame = CGRectMake(10.f, 7.f, 30.f, 33.f);
    [_scanBtn setTitle:@"扫一扫" forState:UIControlStateNormal];
    [_scanBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_scanBtn setImage:[UIImage imageNamed:@"scan_white"] forState:UIControlStateNormal];
    [_scanBtn addTarget:self action:@selector(scanAction) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:_scanBtn];
    
    //创建消息按钮
    _messageBtn = [EOCButton buttonWithType:UIButtonTypeCustom];
    _messageBtn.frame = CGRectMake(SCREENSIZE.width-40.f, 7.f, 30.f, 33.f);
    [_messageBtn setImage:[UIImage imageNamed:@"message_white"] forState:UIControlStateNormal];
    [_messageBtn setTitle:@"消息" forState:UIControlStateNormal];
    [_messageBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_messageBtn addTarget:self action:@selector(messageAction) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:_messageBtn];
    
}

#pragma mark audio&&scan Button action
- (void)scanAction
{
    if (_scanActionBlock) {
        _scanActionBlock();
    }
}

- (void)audioAction
{
    if (_audioActionBlock) {
        _audioActionBlock();
    }
}

- (void)messageAction
{
    if (_messageActionBlock) {
        _messageActionBlock();
    }
}

#pragma mark - tapGesture Action
- (void)searchAction
{
    if (_searchActionBlock) {
        _searchActionBlock();
    }
}
@end

需要注意的是,这里使用了block来传递按钮的点击事件,非常方便,只需要在创建的时候,这样调用

_navigationView = [[EOCNavigationView alloc] initWithFrame:CGRectMake(0.f, 20.f, self.view.eoc_width, 44.f)];
        //创建navView的scanAction、audioAction、messageAction、searchAction的block
        __weak typeof(self)weakSelf = self;
        _navigationView.scanActionBlock = ^{
        };
        _navigationView.audioActionBlock = ^{
        };
        _navigationView.messageActionBlock = ^{
        };
        _navigationView.searchActionBlock = ^{
            [weakSelf goToSearchPage];
        };

然后就是使用了searchBg = [searchBg stretchableImageWithLeftCapWidth:30.f topCapHeight:0.f]; //左边30不拉伸这个方法第一个参数是左边不拉伸区域,第二个参数是上边不拉伸区域。

注意3

在封装继承自UIViewController的searchController中,在初始化方法中需要先设置frame,不然会显示白屏,_searchResultViewController添加不上

- (instancetype)initWithSearchResultControlloer:(UIViewController *)resultViewController {
    
    if ([super init]) {
        if (resultViewController) {
            //添加_searchResultViewController是添加在self.view上面,所以要先设置frame, 不然就是白屏
            self.view.frame = CGRectMake(0.f, 64.f, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height-64.f);
            _searchResultViewController = (EOCSearchResultViewCtrl *)resultViewController;
            _searchResultViewController.view.frame = CGRectMake(0.f, 0.f, self.view.eoc_width, self.view.eoc_height);
            //添加到我们的EOCClassSearchController上来
            [self addChildViewController:_searchResultViewController];
        }
    }
    return self;
}
注意4

在封装继承自UIViewController的searchController中,监听searchBar的输入不能用- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string这个方法,因为这样会使当输入第一个字符的时候,没有搜索结果展示,只有当输入第二个字符的时候才会走这个方法,出来搜索结果


所以要想第一个字符也能监听到,要使用[_searchBar addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];这个方法
- (void)textChange {  // 使用这个方法一开始输入搜索字符的时候,下面的view就添加上了
    if (_delegate && self.searchBar.text.length == 1) {
        
        //添加resultViewController的view;同时我把searachController添加到另外一个viewcontroller
        if (!self.parentViewController) {   // 防止当删除到只剩一个元素的时候也调用这段代码
            
            [self.view addSubview:_searchResultViewController.view];
            //添加searachController到mainSearchCtrl上
            [_delegate didPresentSearchController:self];
        }
    }
    
    // 当搜索关键字删除完了过后,要退出搜索结果界面
    if (_delegate && self.searchBar.text.length == 0) {
        [_searchResultViewController.view removeFromSuperview];
        [_delegate didDismissSearchController:self];
    }
    // 当更新的代理存在的时候,随时更新搜索内容
    if (_searchResultUpdater) {
        [_searchResultUpdater updateSearchResultsForSearchController:self];
    }
    
}
注意5

当从最上面所说的第一个图到第二个图的时候要使用方法

    EOCClassMainSearchCtrl *searchCtrl = [[EOCClassMainSearchCtrl alloc] init];
    [self addChildViewController:searchCtrl];
    [self.view addSubview:searchCtrl.view];
//    [self presentViewController:searchCtrl animated:NO completion:^{
//        
//    }];

当点击取消按钮从第二个图到第一个图的时候使用下面的方法

//    [self dismissViewControllerAnimated:NO completion:^{
//    }];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"searchBarRemove" object:nil];
    [self removeFromParentViewController];
    [self.view removeFromSuperview];

不能用上面注释的方法,不然会有很明显的延迟的效果



而且当点击取消按钮的时候还要发送通知,在第一个图的界面实现搜索框变短,按钮从小到大的动画效果。

注意6

在封装继承自UIViewController的searchController中需要定义两个代理协议,一个用于输入搜索关键字过后进行更新,一个用于展示和退出搜索结果界面。

#import <UIKit/UIKit.h>
#import "EOCClassSearchBar.h"
#import "EOCSearchResultViewCtrl.h"

@class EOCClassSearchController;

@protocol EOCSearchResultsUpdating <NSObject>
@required
// Called when the search bar's text or scope has changed or when the search bar becomes first responder.
- (void)updateSearchResultsForSearchController:(EOCClassSearchController *)searchController;
@end;

@protocol EOCSearchControllerDelegate <NSObject>
- (void)didPresentSearchController:(EOCClassSearchController *)searchController;
- (void)didDismissSearchController:(EOCClassSearchController *)searchController;
@end


@interface EOCClassSearchController : UIViewController

@property(nonatomic, strong)EOCClassSearchBar *searchBar;
@property(nonatomic, strong)EOCSearchResultViewCtrl *searchResultViewController;
@property(nonatomic, weak)id <EOCSearchResultsUpdating> searchResultUpdater;
@property(nonatomic, weak)id <EOCSearchControllerDelegate> delegate;

- (instancetype)initWithSearchResultControlloer:(UIViewController *)resultViewController;
@end
#pragma mark - EOCSearchResultsUpdating delegate
- (void)updateSearchResultsForSearchController:(EOCClassSearchController *)searchController {

    NSString *searchText = searchController.searchBar.text;
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(SELF CONTAINS %@)", searchText];
    EOCSearchResultViewCtrl *resultViewCtrl = (EOCSearchResultViewCtrl *)searchController.searchResultViewController;
    NSArray *dataArr = [EOCDataModel sharedDataModel].dataArr;
    resultViewCtrl.filterDataArr = [dataArr filteredArrayUsingPredicate:predicate];
}

#pragma mark - EOCSearchController delegate
- (void)didPresentSearchController:(EOCClassSearchController *)searchController {
    [self addChildViewController:searchController];
    [self.view addSubview:searchController.view];
}

- (void)didDismissSearchController:(EOCClassSearchController *)searchController {
    [searchController removeFromParentViewController];
    [searchController.view removeFromSuperview];
}
注意7

[self.searchController.searchBar becomeFirstResponder];这段代码要放在主界面的viewDidLoad方法中,防止出现卡顿

@implementation EOCClassMainSearchCtrl

- (void)viewDidLoad {
    
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self createNavigationView];
    [self.view addSubview:self.searchHistoryTable];
    //放在这里防止一开始会出现卡顿的效果
    [self.searchController.searchBar becomeFirstResponder];
}

需要注意的是在模拟器上还是会出现如下的卡顿情况,但是在真机上不会

注意8

按钮动画效果的实现

//页面完全显示
- (void)viewDidAppear:(BOOL)animated {
    
    [super viewDidAppear:animated];
    [UIView animateWithDuration:0.2f animations:^{
    self.searchController.searchBar.frame = CGRectMake(10.f, 8.f, SCREENSIZE.width-63.f, 28.f);
//        _scanBtn.alpha = 0.f;
        _scanBtn.hidden = YES;
    } completion:^(BOOL finished) {
    }];
    //basicAnimation来实现取消按钮的动画效果
    CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    basicAnimation.fromValue = @1;
    basicAnimation.toValue = @0;
    basicAnimation.duration = 0.2f;
    basicAnimation.delegate = self;
    
    basicAnimation.fillMode = kCAFillModeForwards;  // 保持前面动画的值,即就是缩到最小0的状态,然后再放大,防止缩小到0的状态后,恢复原状,再从0到1逐渐放大
    basicAnimation.removedOnCompletion = NO;
    
    [_messageBtn.layer addAnimation:basicAnimation forKey:nil];
    
    //不加下面这句代码延迟,会因为basicAnimation.removedOnCompletion = NO;这句代码造成EOCClassMainSearchCtrl不能被销毁,从而内存泄漏
    [self performSelector:@selector(removeanimtion) withObject:nil afterDelay:1.f];
    
}

- (void)removeanimtion {
    
    [_messageBtn.layer removeAllAnimations];
    
}
//动画代理方法,当动画结束后调用
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    //修改message button的文字
    _messageBtn.noImage = YES;
    [_messageBtn setImage:nil forState:UIControlStateNormal];
    [_messageBtn setTitle:@"取消" forState:UIControlStateNormal];
    [_messageBtn setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
    [_messageBtn addTarget:self action:@selector(messageAction) forControlEvents:UIControlEventTouchUpInside];
    //basicAnimation来实现
    CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    basicAnimation.fromValue = @0;
    basicAnimation.toValue = @1;
    basicAnimation.duration = 0.2f;
    
    basicAnimation.fillMode = kCAFillModeForwards;
    basicAnimation.removedOnCompletion = NO;
    
    [_messageBtn.layer addAnimation:basicAnimation forKey:nil];
    
}

先在第一个方法中,即页面完全显示的时候先将按钮从1到0缩小,然后动画结束后再执行动画从0到1放大,并且改变文字为“取消”。
kCAFillModeForwards:当动画结束后,layer会一直保持着动画最后的状态
removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards

注意9

关于动画中会影响内存泄漏的问题:在上面的注意8中如果不在动画结束过后,移除动画,会造成控制器无法被释放,从而造成内存泄漏,所以必须要加上下面这句代码

//不加下面这句代码延迟,会因为basicAnimation.removedOnCompletion = NO;这句代码造成EOCClassMainSearchCtrl不能被销毁,从而内存泄漏
    [self performSelector:@selector(removeanimtion) withObject:nil afterDelay:1.f];

- (void)removeanimtion {
    [_messageBtn.layer removeAllAnimations];
}
注意10

要实现在第二个图的时候,滑动下面历史记录键盘的时候,键盘隐藏,其中下面的历史记录是封装的一个tableView,所以要将滚动事件传递出来,这里用到了block,当然也可以用代理和通知,但是个人认为用block要方便一些

#import <UIKit/UIKit.h>

typedef void(^scrollActionBlock) ();
@interface EOCSearchTable : UITableView<UITableViewDelegate, UITableViewDataSource>
@property(nonatomic, strong)NSArray *searchHistoryArr;
@property(nonatomic, strong)scrollActionBlock scrollBlock;
@end
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (_scrollBlock) {
        _scrollBlock();
    }
}

然后在使用tableView的地方,实现block方法,使其不为空就好

        //创建搜索记录tableView
        _searchHistoryTable = [[EOCSearchTable alloc] initWithFrame:CGRectMake(0.f, 64.f, SCREENSIZE.width, SCREENSIZE.height-64.f) style:UITableViewStyleGrouped];
        __weak typeof(_searchController)weakSearch = _searchController;
        _searchHistoryTable.scrollBlock = ^{
            [weakSearch.searchBar resignFirstResponder];
        };

block默认会将外部的变量拷贝一份,在block中如果要对外部参数进行修改就要用__block,如果不修改只是用__weak就够了

本文为我在腾讯课堂八点钟学院学习所做的笔记

参考文章
UIImage部分拉伸——stretchableImageWithLeftCapWidth的使用
iOS开发基础知识:Core Animation(核心动画)
(iOS)__block和__weak认识

相关文章

  • 京东搜索框的实现(深度定制)

    要实现这个搜索框,我们需要先看一下效果,然后再进行拆分,分步实现,这样就没有你想想中的那么难了。 从这个图 点击搜...

  • Android

    顶部导航栏 仿京东搜索 顶部导航栏 今日头条导航栏 导航栏快速实现 瀑布流 搜索框带历史记录 tablayout ...

  • 模拟京东搜索框之获得失去焦点

    模拟京东搜索框之获得失去焦点

  • 深度优先广度优先

    深度优先搜索 广度优先搜索(队列实现)

  • 【javaweb】搜索框跳转百度搜索

    需求 自己输入框搜索,跳转到百度页面搜索。 思路 jsp实现搜索框,传递搜索内容到servlet servlet获...

  • Servlet+Ajax

    Servlet+Ajax 实现搜索框智能提示

  • 强势围观!JS仿智能搜索究竟有多牛逼?

    JS实现仿google、百度搜索框输入信息智能提示的实现方法 搜索框智能提示是一种全新的搜索方式,可以在进行关键词...

  • 百度语音搜索

    语音搜索。先实现搜索界面。 定义输入框:search_page.xml 这个搜索框可以手动输入,也可以点击语音图标...

  • iOS --- searchBar模糊搜索

    模糊搜索的实现思路:当搜索框开始编辑时对搜索框中的文本与后台给的资源相对比,包含搜索文本的展示在tableview...

  • Vuetify 实现搜索框

    实现思路 使用 Vuetify 中的 v-menu 组件实现,控制光标焦点,在输入框获取的焦点时弹出联想词汇菜单,...

网友评论

      本文标题:京东搜索框的实现(深度定制)

      本文链接:https://www.haomeiwen.com/subject/naisfxtx.html