专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

半糖iOS版首页实现与基本原理揭秘

Cocoa开发者社区  · 公众号  · ios  · 2017-06-23 11:17

正文

推荐人: chen316711


很久以前,一个学弟的曾问过我如何实现半糖iOS版本首页效果,我当时一看觉得这个效果挺酷炫,然后去github上搜了一下,很多自称是仿半糖首页的,我下载之后发现其实很多代码都没有实现主要的代码。有些代码也做了一些简单的尝试,但是最后都放弃了,所以说这个效果还是没有很好的实现。我于是打算研究一下这个有趣的效果,经过工作之余一段时间的研究。有时候路上也会想一想,做了很多的尝试,一点一点的把遇到的问题解决了。于是写下这篇文章,把自己的一些尝试和想法与大家分享。


有的开发者可能会觉得这么简单的东西别拿来忽悠我,可以自己亲自尝试去做一个,并没有想象的那么简单。


实现上滑的的效果


这一步是首页效果的基础,实现这一步后,才有继续其它步的必要,这里面难度不是很大,关键是要想到一个好方法不容易。下面就具体讲讲是如何实现的。


有一点可以确定的是,使用的肯定是KVO的做法。通过监听contentOffset的变化来进行相应的处理。但是具体怎么做,怎么来划分层次,真的是一个让人脑壳痛的问题。


怎么下手呢,开始真的毫无思绪,然后想到了一个利器,Reveal。不管别的,先用Reveal看看图层结构再说。关于Reveal的使用,在我的另外一篇文章里面有。 使用Reveal查看任意iOS App的图层结构 。通过图层查看后,下面是一个ScrollView,上面是几个TableView。于是这个立刻把我带入了坑,很多网上的Demo都是这样尝试,把TableView放到ScrollView上面,然后对它们的contentOffset都添加监听。通过判断偏移量来禁用TableView或是Scrollew的手势。经过无数次的尝试最后还是放弃了,手势冲突这个问题不可能这样很好的解决。


然后我搜了资料,有人说可以使用contentInset这个属性,这是用来设定ScrollView及其子类的内容显示区域,通过改变这个属性的值,达到类似的滑动的效果。也就是在KVO的实现方法里面不断的改变contentInset的值,然后模拟上推的效果。没有试过这个属性的可以尝试一下。我使用之后,出现的问题就是卡顿,特别卡,而且很难控制值,这就造成了完全没有流畅性可言。间接说明了使用这个根本没法达到这个效果。


然后我实在是想不到什么好办法,打算用UISwipGesturer,不过想想这就算了吧。太愚蠢了,而且也会很麻烦。像我这样懒的,总想少写几行代码。怎么办呢,再想想。有一天在地铁上拿出半糖的APP来研究,突然灵光一闪,想到了。因为既然这么流畅,那一定是使用了原生的UITableView,然后再使用scrollIndicatorInsets这个属性就可以伪装出tableView是从下面开始的效果,然后我的tableView从坐标(0,0)的位置开始。上面添加一个空白的View把内容往下面撑即可实现类似的效果。如图(为了便于看清楚布局,我给每个视图留了一个边距)



能想到这一步其实完成了很大的工作了。接下来就是给上面的搜索框和轮播页面添加坐标变化的事件了。


头部三个View的坐标改变


给TableView添加监听,然后在如下方法里面根据contentOffset的值改变轮播和分类选择控价的坐标。


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

{

UITableView *tableView = (UITableView *)object;


if (![keyPath isEqualToString:@"contentOffset"]) {

[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

return;

}


CGFloat tableViewoffsetY = tableView.contentOffset.y;

self.lastTableViewOffsetY = tableViewoffsetY;


if ( tableViewoffsetY>=0 && tableViewoffsetY<=136) {

self.segmentScrollView.frame = CGRectMake(0, 200-tableViewoffsetY, SCREEN_WIDTH, 40);

self.cycleScrollView.frame = CGRectMake(0, 0-tableViewoffsetY, SCREEN_WIDTH, 200);

}else if( tableViewoffsetY < 0){

self.segmentScrollView.frame = CGRectMake(0, 200, SCREEN_WIDTH, 40);

self.cycleScrollView.frame = CGRectMake(0, 0, SCREEN_WIDTH, 200);


}else if (tableViewoffsetY > 136){

self.segmentScrollView.frame = CGRectMake(0, 64, SCREEN_WIDTH, 40);

self.cycleScrollView.frame = CGRectMake(0, -136, SCREEN_WIDTH, 200);

}

}


我们需要添加一个坐标的限制,因为偏移量有时候会无限大或者是无限小。而我们的轮播和分类选择器的区间是固定不变的。所以需要找对坐标进行限制,一旦偏移量超过了这个坐标就不进行改变,而是保持固定的值不变。


为了模块的划分清晰一些,我把上面的搜素框单独的划分到了JQHeaderView里面。所以需要把外面的tableView传到里面去。然后在里面同样进行了监听然后事件处理。


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

{


if (![keyPath isEqualToString:@"contentOffset"]) {

[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

return;

}

UITableView *tableView = (UITableView *)object;

CGFloat tableViewoffsetY = tableView.contentOffset.y;


UIColor * color = [UIColor whiteColor];

CGFloat alpha = MIN(1, tableViewoffsetY/136);


self.backgroundColor = [color colorWithAlphaComponent:alpha];


if (tableViewoffsetY < 125){


[UIView animateWithDuration:0.25 animations:^{

self.searchButton.hidden = NO;

[self.emailButton setBackgroundImage:[UIImage imageNamed:@"home_email_black"] forState:UIControlStateNormal];

self.searchBar.frame = CGRectMake(-(self.width-60), 30, self.width-80, 30);

self.emailButton.alpha = 1-alpha;

self.searchButton.alpha = 1-alpha;



}];

} else if (tableViewoffsetY >= 125){


[UIView animateWithDuration:0.25 animations:^{

self.searchBar.frame = CGRectMake(20, 30, self.width-80, 30);

self.searchButton.hidden = YES;

self.emailButton.alpha = 1;

[self.emailButton setBackgroundImage:[UIImage imageNamed:@"home_email_red"] forState:UIControlStateNormal];

}];

}


}


做完以上工作后,我们应该可以看到的是这样的效果。



添加下拉刷新的文字效果


下拉刷新我单独分离出来了JQRefreshHeaader文件。实现的原理一样是用了KVO。使用偏移量进行相应的图片替换,在某个偏移量开始出现图片,在另一个偏移量结束。这中间每两个像素的偏移量替换为一张图片, 然后隐藏其它所有的图片,就显示当前的图片,当偏移量的绝对值大于某个值时,显示所有的图片,小于某个值时隐藏所有的图片。当然这里面还值得推敲,感觉可以简化一些步骤,


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

{



if (![keyPath isEqualToString:@"contentOffset"]) {

[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

return;

}


UITableView *tableView = (UITableView *)object;

CGFloat tableViewoffsetY = tableView.contentOffset.y;


if ( tableViewoffsetY <= 0  &&tableViewoffsetY > -35) {


[self hideAllImageView];


}else if(tableViewoffsetY < -35){


if (tableViewoffsetY < -59) {


[self showAllImageView];

}else {

CGFloat offset = fabs(tableViewoffsetY)-35;

NSInteger imageCount = offset/2.0;//两个偏移量切换一张图片

[self hideImageViewExcept:imageCount];

}


}else if (tableViewoffsetY <136){


}


}


把这里面使用的图片是我自己用PS做的,所以看起来很丑,实现后的效果如下 :




添加分类滑动


首先是单纯的实现左右滑动的效果,这里我使用了简单的ScrollView来实现这个效果。上面的分类选择是一个ScrollView,下面的也是ScrlloView,在切换时候修改对应的偏移量即可。当然实现方式很多,网上也有很多的框架,不过该项目的分类选择控件需要实现上下滑动,所以我还是自己实现了。实现原理很简单


首先是点击上面的分类控件实现下面ScrollView的滑动。我们只需要在改变改变分类控件偏移量的同时改变下面内容ScrollView的偏移量


[UIView animateWithDuration:0.3 animations:^{

if (index == 0) {

self.currentSelectedItemImageView.frame = CGRectMake(PADDING, self.segmentScrollView.frame.size.height - 2,currentButton.frame.size.width, 2);


}else{


UIButton *preButton = self.titleButtons[index - 1];


float offsetX = CGRectGetMinX(preButton.frame)-PADDING*2;


[self.segmentScrollView scrollRectToVisible:CGRectMake(offsetX, 0, self.segmentScrollView.frame.size.width, self.segmentScrollView.frame.size.height) animated:YES];


self.currentSelectedItemImageView.frame = CGRectMake(CGRectGetMinX(currentButton.frame), self.segmentScrollView.frame.size.height-2, currentButton.frame.size.width, 2);

}

self.bottomScrollView.contentOffset = CGPointMake(SCREEN_WIDTH *index, 0);


}];


然后们在滑动下面的ScrollView的时候滑动在代理方法里面分类选择控件也跟着进行滑动即可。


[UIView animateWithDuration:0.3 animations:^{

if (index == 0) {

self.currentSelectedItemImageView.frame = CGRectMake(PADDING, self.segmentScrollView.frame.size.height - 2,currentButton.frame.size.width, 2);


}else{



UIButton *preButton = self.titleButtons[index - 1];


float offsetX = CGRectGetMinX(preButton.frame)-PADDING*2;


[self.segmentScrollView scrollRectToVisible:CGRectMake(offsetX, 0, self.segmentScrollView.frame.size.width, self.segmentScrollView.frame.size.height) animated:YES];


self.currentSelectedItemImageView.frame = CGRectMake(CGRectGetMinX(currentButton.frame), self.segmentScrollView.frame.size.height-2, currentButton.frame.size.width, 2);

}


}];


这样简单实现的滑动控件肯定有很多值得优化的地方,最简单优化就是把下面的UIScrollView换成UICollectionView,这样就可以复用Cell从而优化内存。


实现后的基本效果如下


为分类滑动添加上下滑动的交互







请到「今天看啥」查看全文