由于一直做公司项目,所以有些技能没有去实践过,所以一直想做一个项目练练手,然后找到了MONO这个软件,里面的内容界面都做的很精致,用charles抓了一下,发现接口都是可以获取的,于是就开始做了.目前只做了一部分,记录了过程中一些觉得值得记录的问题,然后分享给大家.
如果大家觉得这篇文章对你有帮助,希望大家能给个star,你们的鼓励是我前进的动力 --项目地址.
注意这里只是给出个简单思路,详细过程还请看源码.
一.全屏拖拽效果
全屏拖拽首先导入FDFullscreenPopGesture和RTRootNavigationController这两个库,然后在tabbar中将RTRootNavigationController设为你每个视图控制器的根视图就可以实现这种push效果了.FDFullscreenPopGesture是实现全屏拖拽手势,RTRootNavigationController是改变导航栏的动画效果.#
二.导航栏视图填充满
本身导航栏左侧是始终有一个返回按钮的位置的,如果直接设置view为titleview的话并不能填充满,我的做法是在view上放一个backview再在backview上放控件,backview的x设为-25,这样的话就可以将导航栏填充满了.
三.简化UITableViewDelegate,UITableViewDataSource代理方法
首先注册cell
[self.tableView registerClass:[RecommendImageBgCell class] forCellReuseIdentifier:NSStringFromClass([RecommendImageBgCell class])];
[self.tableView registerClass:[RecommendReadCell class] forCellReuseIdentifier:NSStringFromClass([RecommendReadCell class])];
[self.tableView registerClass:[RecommendImagesCell class] forCellReuseIdentifier:NSStringFromClass([RecommendImagesCell class])];
[self.tableView registerClass:[RecommendMusicCell class] forCellReuseIdentifier:NSStringFromClass([RecommendMusicCell class])];
[self.tableView registerClass:[RecommendVideoCell class] forCellReuseIdentifier:NSStringFromClass([RecommendVideoCell class])];
[self.tableView registerClass:[RecommendPicturesCell class] forCellReuseIdentifier:NSStringFromClass([RecommendPicturesCell class])];
[self.tableView registerClass:[RecommendTeaCell class] forCellReuseIdentifier:NSStringFromClass([RecommendTeaCell class])];
高度的计算因为我用的是SDAutolayout进行布局,所以只用一行代码就可以将所有的cell的高度计算下来.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
RecommendModel *model = _dataArray[indexPath.row];
return [self.tableView cellHeightForIndexPath:indexPath model:model keyPath:@"recommendModel" cellClass:NSClassFromString(model.cellIdentifier) contentViewWidth:SCREEN_WIDTH];
}
这里注意所有cell的model名字需要一直,而且他们都是可以共用同样的model.
接下来就是设置cell了,这里我给出了详细的注释
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RecommendModel *model = _dataArray[indexPath.row];//获取model
//MNBaseTableViewCell为所有cell的父类
MNBaseTableViewCell *cell;
NSString *cellIdentifier;
//后台会根据内容的不同给出不同的object_type,根据这个来设置不同cell的identifier.
cellIdentifier = model.cellIdentifier;
//因为给每个cell注册过了,所以这里拿到identifier就可以找到具体的cell
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
//这里用kvc给cell赋值
[cell setValue:_dataArray[indexPath.row] forKey:@"recommendModel"];
return cell;
}
四.文字闪烁出现
效果是下面这样的,当拖动到一定位置的时候图片上面的文字闪烁出现,之后就不再闪烁了.
这上面的label用了第三方RQShineLabel,其实原理就是先让所有文字字体颜色透明度为0,再加个定时器,随机让文字字体颜色透明度变为1.
那么如何在拖拽到一定位置的时候再执行闪烁方法而不是刚加载这个cell的时候就执行呢.我的做法是这样的.
//cell将要被加载
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//判断是否是需要闪烁文字的cell
if ([cell isKindOfClass:[RecommendImageBgCell class]]) {
RecommendImageBgCell *bgCell = (RecommendImageBgCell *)cell;
_bgCell = bgCell;
//如果tableview刚刷新出来,这个cell就在界面上的话就执行闪烁方法
if (tableView.contentOffset.y <= 0 && bgCell.y <= 0) {
[_bgCell shineText];
return;
}
//给一个全局变量来监视cell滑动情况
_bgCellY = bgCell.y;
}
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//当cell滑动到一定位置的时候就闪烁
if ((_bgCellY - scrollView.contentOffset.y <= (SCREEN_HEIGHT / 4 + KTabBarHeight)) && _bgCellY > 0) {
[_bgCell shineText];
_bgCellY = -10;
}
}
五.音乐播放效果
效果如下所示,当点击cell播放音乐的时候,CD会转起来,导航栏最右侧的CD按钮也会转起来.
[图片上传中...(13.gif-7a2593-1528170837139-0)]
首先音频播放这块我用的是FreeStreamer,写一个单例类MNMusicPlayer继承自FSAudioStream,里面除了初始化和单独的一些方法外还为音乐播放的各种状态添加了通知.通过通知来决定动画的显示或消失.
好,接下来就在cell上自定义2个view,一个是旋转的MusicCDView,一个是下面歌曲进度的MusicProgressView.
在MusicCDView中,先创建一个CABasicAnimation,再通过给self.layer添加或移除动画来实现CD旋转和隐藏效果.
//创建一个全局的动画
_anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
_anim.fromValue = [NSNumber numberWithFloat:0.f];
_anim.toValue = [NSNumber numberWithFloat: M_PI *2];
_anim.duration = 10;
_anim.autoreverses = NO;
_anim.fillMode = kCAFillModeForwards;
_anim.repeatCount = MAXFLOAT;
动画效果
//播放音乐
-(void)playMusic
{
if (self.alpha != 1) {
[UIView animateWithDuration:0.5 animations:^{
self.alpha = 1;
}];
}
[self.layer addAnimation:_anim forKey:@"rotaion"];
}
//暂停音乐
-(void)stopMusic
{
if (self.alpha != 0) {
[UIView animateWithDuration:0.5 animations:^{
self.alpha = 0;
}];
}
[self.layer removeAnimationForKey:@"rotaion"];
}
最后在收到通知的时候执行就行了.
@weakify(self)
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"stopMusic" object:nil] subscribeNext:^(NSNotification *notification) {
@strongify(self)
[self stopMusic];
}];
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"playMusic" object:nil] subscribeNext:^(NSNotification *notification) {
@strongify(self)
//播放音乐的URL和此URL地址相同时才播放
if ([notification.object isEqualToString:self.model.music_url]) {
[self playMusic];
}
}];
同时别忘了给他的父视图设置.clipsToBounds = YES属性,不然CD盘就全部显示出来了.
六.图片裁剪
在图片浏览中,小图的ImageView的宽高比都是1:1的比例,而原图的比例并不是,所以需要截取图片正中间1:1的部分,这样点击浏览图片效果就会很舒服,效果如下:
这里用到了imageView.layer.contentsRect这个属性来截取需要显示的图片部分,点击查看contentsRect.(这里服务器给出了原图的宽和高了)
这里再解释一下.
比如一张图片如下所示,它的宽高比是2:1.
d0c8a786c9177f3e2fb5a1987ccf3bc79e3d56a5.jpg
如果要截取左半部分,则这样写:imageView.layer.contentsRect = CGRectMake(0, 0, 0.5, 1);前2个参数表示x和y的锚点,如果用(0,0)的话表示从最左上角开始切割,如果用(0,0.5)表示从最左边高度的一半那里开始切割.后面2个参数表示截取的宽高比.所以这里(0, 0, 0.5, 1)后就成了这样:
要截取正中间这部分1:1的图片只需要设置成这样就可以了:
imageView.layer.contentsRect = CGRectMake((1- 0.5)/2, 0, 0.5, 1);
好了,现在我们假设一张图片它的宽和高分别是width和height,且width > height.那么我们要截取正中间1:1的部分就可以写成这样了:
imageView.layer.contentsRect = CGRectMake((1 -(float)height / width) / 2, 0, (float)height / width, 1);
同理,高图就写成这样:
imageView.layer.contentsRect = CGRectMake(0, (1- (float)width / height) / 2, 1, (float)width / height);
在代码中判断是宽图还是高图再用具体的方法来截取就可以了.
七.收藏动画
做这个的时候走了些弯路,最后的做法是在收藏按钮上面放两个imageview,再设置imageview的animationImages.
八.网页滑动隐藏导航栏和状态栏.
这里的做法也很简单,如下:
-(BOOL)prefersStatusBarHidden
{
return hiddenStatusBar;
}
hiddenStatusBar是一个全局Bool变量,在滑动的时候判断滑动方向后来改变hiddenStatusBar的值,再调用[self setNeedsStatusBarAppearanceUpdate]方法来刷新状态栏的显示或隐藏.导航栏的隐藏和显示就用[self.navigationController setNavigationBarHidden:BOOL animated:YES];因为最开始不知道setNeedsStatusBarAppearanceUpdate方法,所以绕了些弯路.
九.网页阅读模式
最开始看到这个阅读模式的时候,我误以为是safari阅读模式,经过仔细分析发现不是,他这个两个不同的数据源而已.他们针对第三方来源的阅读网页给出来两个数据.一个是网址url(非阅读模式),一个是返回html字段(阅读模式),只需要添加2个WKWebView,转换阅读模式的时候只需要改变具体的WKWebView的hidden就行了.
十.加载失败与重新加载
在视图控制器的基类中写两个方法就可以,一个是加载失败的方法.如下:
//加载失败显示失败按钮与文字,参数为点击按钮执行方法
-(void)showPageLoadingFailedWithReloadTarget:(id)target action:(SEL)action
{
if (!pageLoadingView) {
pageLoadingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - NaviH - KTabBarHeight)];
pageLoadingView.backgroundColor = [UIColor colorWithRed:0.86 green:0.89 blue:0.91 alpha:1];
}
[pageLoadingView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.view addSubview:pageLoadingView];
UIButton *reloadImageBtn = [UIButton buttonWithType:UIButtonTypeCustom];
reloadImageBtn.frame = CGRectMake(0, 0, 40, 40);
reloadImageBtn.center = CGPointMake(pageLoadingView.width/2, pageLoadingView.height/2);
[pageLoadingView addSubview:reloadImageBtn];
[reloadImageBtn setImage:[UIImage imageNamed:@"refresh_btn"] forState:UIControlStateNormal];
UIButton *reloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
reloadBtn.frame = CGRectMake(0, reloadImageBtn.bottom + 20, 200, 30);
reloadBtn.centerX = pageLoadingView.width/2;
[pageLoadingView addSubview:reloadBtn];
reloadBtn.titleLabel.font = [UIFont systemFontOfSize:12];
[reloadBtn setTitleColor:[UIColor colorWithRed:0.66 green:0.66 blue:0.66 alpha:1] forState:UIControlStateNormal];
[reloadBtn setTitle:@"加载失败,请点击重试" forState:UIControlStateNormal];
if (target){
[reloadImageBtn addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
[reloadBtn addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
}
}
//显示正在加载背景图片
#pragma mark - 正在加载动画
-(void)showPageLoadingProgress
{
if (!pageLoadingView) {
pageLoadingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - NaviH - KTabBarHeight)];
pageLoadingView.backgroundColor = [UIColor colorWithRed:0.86 green:0.89 blue:0.91 alpha:1];
}
[pageLoadingView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.view addSubview:pageLoadingView];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 25, 25)];
imageView.center = CGPointMake(pageLoadingView.width/2, pageLoadingView.height/2);
[pageLoadingView addSubview:imageView];
imageView.image = [UIImage imageNamed:@"head-mask-bg"];
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
animation.duration = 1;// 动画时间
NSMutableArray *values = [NSMutableArray array];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)]];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1.0)]];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)]];
animation.values = values;
animation.repeatCount = FLT_MAX;
[imageView.layer addAnimation:animation forKey:nil];
}
十一.状态栏与导航栏的隐藏与显示
先说状态栏,这里先创建一个BOOL值的成员变量 hiddenStatusBar,再使用方法
-(BOOL)prefersStatusBarHidden
{
return hiddenStatusBar;
}
在滑动的时候判断滑动方向,改变hiddenStatusBar的值,再调用下面这个方法,这是用来调用prefersStatusBarHidden方法并刷新状态栏状态的
[self setNeedsStatusBarAppearanceUpdate];
导航栏很简单,判断滑动方向然后再调用
[self.navigationController setNavigationBarHidden:NO animated:YES];
或者
[self.navigationController setNavigationBarHidden:YES animated:YES];
结尾.
这个项目也是费了一些时间和精力,现在整理其中有遇到的或学习到的一些知识来分享给大家,希望能够帮助到大家,目前只做了一部分,剩下的会慢慢做下去,然后再把过程中的知识点整理出来分享给大家.希望大家能给个star,你们的鼓励是我前进的动力,谢谢大家.
网友评论
虽然不知道原MONO项目是怎么做的,但像在产品公司里面,如果集成太多第三方库,可能会引起卡顿,不知道作者是否关注过内存方面的控制.一般来说都会进行仿写优化.
iOS目前高仿项目出产量还是比较少吧,所以楼主还是很棒的,加油