<<高仿MONO>>项目知识点整理

由于一直做公司项目,所以有些技能没有去实践过,所以一直想做一个项目练练手,然后找到了MONO这个软件,里面的内容界面都做的很精致,用charles抓了一下,发现接口都是可以获取的,于是就开始做了.目前只做了一部分,记录了过程中一些觉得值得记录的问题,然后分享给大家.

如果大家觉得这篇文章对你有帮助,希望大家能给个star,你们的鼓励是我前进的动力 --项目地址.

注意这里只是给出个简单思路,详细过程还请看源码.

一.全屏拖拽效果

全屏拖拽

首先导入FDFullscreenPopGestureRTRootNavigationController这两个库,然后在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,你们的鼓励是我前进的动力,谢谢大家.

github地址,欢迎star

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,960评论 3 119
  • 【今早起床】5点20【清晨功课】脉轮静坐、祈祷与心灵禅舞【家务检视】保持干净,物品及时归位【健康运动】坚持6杯水、...
    心静若水1阅读 198评论 0 0
  • 如果你喜欢一个人,不好意思开口说,憋在心里又难受,你可以在热恋进行时暗示Ta表达自己的爱意。当然,现在传递爱的信息...
    05f3ae22a228阅读 175评论 0 0
  • 每天上班其实也并不累,但是还是感觉身体被掏空。于是急切的想要来场说走就走的旅行。但是怂人就是怂人,找不到小伙伴,又...
    亦尘亦尘阅读 178评论 0 1
  • 1.堆内存和栈内存的区别是什么? 线程的堆内存空间是共享的,栈内存空间才是独立的(堆共享,栈独立)。 2.wait...
    吴国友阅读 94评论 0 0