iOS-高仿优雅的好奇心日报

JGHCar 8年前
   <p>好奇心日报 关于好奇心有这样一句话:</p>    <p>之所以叫好奇心日报,是因为我们认为好奇心是人类最美好的品质之一,我们筛选最有价值的信息,你能看到全球最有想法,最有关注的各界动态,以及他们背后的故事</p>    <p>正是出于好奇心偶然间下载安装了这个 <strong>好奇心日报</strong> ,打开这个APP首先吸引我的就是它简洁干净的界面和优雅的配色,整个首页只有一个带有logo的悬浮按钮,然后就是带有高清配图不同与其他新闻平台的新闻文章,图标基本是简单的黑、黄、白三种颜色加单线条图形的设计。最后让我下定决心仿它的是它那简单到自然的动画效果。说这么多当然不是给好奇心日报打广告,只是想说我为什么写这个项目;额,就这么简单!</p>    <p>先看看 JFQDaily <strong>效果:</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0fa26cdfd1dddd67136e40cf79998822.gif"></p>    <p>如果你是一个iOS开发入门级的猿,有兴趣话可以下载 JFQDaily源码 然后结合本篇博客来看,相信你会有所收获的,代码写的接地气,注释详细!</p>    <h3><strong>一、准备工作</strong></h3>    <p>1、高仿,原生图片图标自然必不可少,利用 iOS images Extractor 抓取 <strong>好奇心日报</strong> 的图片,如何使用iOS images Extractor抓取APP图片,我的 iOS直播APP-点赞动画的实现 这篇文章下面有介绍。</p>    <p>2、用青花瓷( Charles )提取密码: kgya抓取 <strong>好奇心日报</strong> 数据。</p>    <p>3、筛选数据:</p>    <ul>     <li>GET请求,拼接url路径,第一次获取数据url是:<br> <a href="/misc/goto?guid=4959726738337245216" rel="nofollow,noindex">http://app3.qdaily.com/app3/homes/index/0.json?</a></li>     <li>获取last_key后上拉加载时GET的URL<br> <a href="/misc/goto?guid=4959726738428199520" rel="nofollow,noindex">http://app3.qdaily.com/app3/homes/index/1478819276_1478777270.json?</a></li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/108fe869f42258d772043565d06309bf.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9be8df3682e25cd51b434359f90588e0.png"></p>    <p>Paste_Image.png</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4886a89d0b2d506eed37a3c47859152e.png"></p>    <p>上面是用chalers所抓取到的数据,分析并拿到你需要的数据就行,上面标注的都是项目中需要用到的数据,具体筛选过程就不再分析了,无非是打开 <strong>好奇心日报</strong> 看APP所展示的信息和抓到的数据做对比,找到相应的映射关系。如果你想走的更远独立行走是必要的。具体数据分析里的细节问题可以认真看源码 Models 文件夹类的 数据模型 文件。好啦准备工作完成啦,开始动手建工程码代码吧!</p>    <h3><strong>二、项目文件结构</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/af203b6f7d2c933692ff5b1685e4a527.png"></p>    <p>1、 AppDeleteggate文件夹</p>    <ul>     <li>放着AppDelegate .h和.m文件</li>    </ul>    <p>2、Tools:工具类</p>    <ul>     <li><strong>NSString+JFMessage:</strong> NSString的类扩展,添加了计算文本高度和将毫秒转换成日期的类方法</li>     <li><strong>JFLoopView:</strong> 无限循环图片轮播器,关于JFLoopView可以看 一行代码实现图片无限轮播器</li>     <li><strong>MBProgressHUD+JFProgressHUD:</strong> MBProgressHUD的类扩展,添加了一个创建MBProgressHUD类方法,方便调用。</li>     <li><strong>JFTimer:</strong> 定时器,在 一行代码实现图片无限轮播器 中有讲到。</li>     <li><strong>JFConfigFile:</strong> 这个里面是一些高频的宏定义</li>    </ul>    <p>3、Models:数据模型</p>    <ul>     <li>这里是根据之前使用chalers筛选的数据建立的数据模型</li>    </ul>    <p>4、DataManager:数据管理器</p>    <ul>     <li>使用第三方框架AFNetworking创建的数据管理器,使用GET请求相关数据。</li>    </ul>    <p>5、ViewControllers:控制器</p>    <ul>     <li><strong>JFHomeViewController:</strong> 首页控制器</li>     <li><strong>JFReaderViewController:</strong> 文章阅读器控制器,使用WXWebView搭建。</li>    </ul>    <p>6、views:界面</p>    <ul>     <li><strong>JFSuspensionView:</strong> 悬浮按钮View</li>     <li><strong>JFHomeNewsTableViewCell:</strong> 首页cell,继承自UITableViewCell</li>     <li><strong>JFMenuView:</strong> 菜单界面</li>     <li><strong>JFNewsClassificationView</strong> 新闻分类界面继承自UIView</li>    </ul>    <p>7、pods:项目所用到的第三方框架</p>    <ul>     <li>Masonry :Masonry是目前最流行的AutoLayout框架。</li>     <li>AFNetworking :是一个非常方便的网络请求库,可以轻松实现各种网络请求,比如经常使用的GET请求、POST请求等。</li>     <li>MJRefresh :李明杰老师写的下拉刷新框架,使用方法很简单。</li>     <li>MJExtension :json数据转模型的框架,也是李明杰老师写的,用法都很简单。</li>     <li>SDWebImage :目前最受欢迎的图片下载第三方框架,使用率很高。</li>     <li>MBProgressHUD :是一个显示HUD窗口的第三方类库,用法简单。</li>    </ul>    <p>各框架的具体用法网上很多资料,不再赘述!</p>    <h3><strong>三、代码</strong></h3>    <p>1、搭建数据模型</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d24303d699e20f0c422ddea16cdaebf2.png"></p>    <p>这是我们用chalers抓取的json格式的数据,根据 MJExtension 的使用方法建立数据模型,先分别创建JFResponseModel、JFFeedsModel、JFPostModel、JFCategoryModel四个类,继承自NSObject。</p>    <p>然后我们慢慢从json数据的最里层向外层声明你所 <strong>需要</strong> 的对应参数,切记参数可以自行选择创建,但是模型的类型一个不能少,参数名一定要一样,如果命名有冲突 MJExtension 提供的有相应的方法进行映射。例如:将冲突的参数 description 名映射到 subhead</p>    <pre>  <code class="language-objectivec">/** 设置模型属性名和字典key之间的映射关系 */  + (NSDictionary *)mj_replacedKeyFromPropertyName {      /* 返回的字典,key为模型属性名,value为转化的字典的多级key */      return @{@"subhead":@"description"};  }  </code></pre>    <p>1.1 JFCategoryModel数据模型</p>    <pre>  <code class="language-objectivec">#import     @interface JFCategoryModel : NSObject     /** 新闻类型(设计、娱乐、智能等)*/  @property (nonatomic, copy) NSString *title;     @end  </code></pre>    <p>1.2 JFPostModel数据模型</p>    <pre>  <code class="language-objectivec">#import     @class JFCategoryModel;     @interface JFPostModel : NSObject     /** 新闻标题*/  @property (nonatomic, copy) NSString *title;  /** 副标题*/  @property (nonatomic, copy) NSString *subhead;  /** 出版时间*/  @property (nonatomic, assign) NSIntegerpublish_time;  /** 配图*/  @property (nonatomic, copy) NSString *image;  /** 评论数*/  @property (nonatomic, assign) NSIntegercomment_count;  /** 点赞数*/  @property (nonatomic, assign) NSIntegerpraise_count;  /** 新闻文章链接(html格式)*/  @property (nonatomic, copy) NSString *appview;     @property (nonatomic, strong) JFCategoryModel *category;     @end  </code></pre>    <p>1.3 JFFeedsModel数据模型</p>    <pre>  <code class="language-objectivec">#import     @class JFPostModel;     @interface JFFeedsModel : NSObject     /** 文章类型(以此来判断cell(文章显示)的样式)*/  @property (nonatomic, copy) NSString *type;     /** 文章配图 */  @property (nonatomic, copy) NSString *image;     @property (nonatomic, strong) JFPostModel *post;     @end  </code></pre>    <p>上面所提到的参数名冲突导致的参数名不一致问题的解决方法:</p>    <pre>  <code class="language-objectivec">#import "JFPostModel.h"     @implementationJFPostModel     /** 设置模型属性名和字典key之间的映射关系 */  + (NSDictionary *)mj_replacedKeyFromPropertyName {      /* 返回的字典,key为模型属性名,value为转化的字典的多级key */      return @{@"subhead":@"description"};  }     @end  </code></pre>    <p>1.4 JFResponseModel数据模型</p>    <pre>  <code class="language-objectivec">#import     @class JFFeedsModel;     @interface JFResponseModel : NSObject     /** 下拉加载时判断是否还有更多文章 false:没有 true:有*/  @property (nonatomic, copy) NSString *has_more;     /** 下拉加载时需要拼接到URL中的key*/  @property (nonatomic, copy) NSString *last_key;     @property (nonatomic, strong) JFFeedsModel *feeds;     @end  </code></pre>    <p>1.5 JFBannersModel数据模型</p>    <p>JFBannersModel模型与JFFeedsModel参数一样,所以继承自JFFeedsModel就好</p>    <pre>  <code class="language-objectivec">#import "JFFeedsModel.h"     @interface JFBannersModel : JFFeedsModel     @end  </code></pre>    <p>看起来模型很麻烦,只要你理清思路,掌握 MJExtension 用法,建立模型是很简单的,最主要是模型建好后,在后面使用数据时会非常方便。</p>    <p>2、DataManager数据管理器</p>    <p>JFHomeNewsDataManager.h文件中添加一个请求新闻数据的方法和一个请求数据成功后回调的block方法。</p>    <pre>  <code class="language-objectivec">#import     typedef void(^JFHomeNewsDataManagerBlock)(iddata);     @interface JFHomeNewsDataManager : NSObject     //  请求数据成功后返回新闻数据回调的block  @property (nonatomic, copy) JFHomeNewsDataManagerBlocknewsDataBlock;     //  请求新闻数据  - (void)requestHomeNewsDataWithLastKey:(NSString *)lastKey;     - (void)newsDataBlock:(JFHomeNewsDataManagerBlock)block;     @end  </code></pre>    <p>JFHomeNewsDataManager.m</p>    <pre>  <code class="language-objectivec">#import "JFHomeNewsDataManager.h"     #import  #import "JFConfigFile.h"     #define kTimeOutInterval 10     @implementationJFHomeNewsDataManager     #pragma mark - 创建请求者  - (AFHTTPSessionManager *)manager {      AFHTTPSessionManager *manager = [AFHTTPSessionManagermanager];      manager.requestSerializer.timeoutInterval = kTimeOutInterval;      manager.responseSerializer = [AFHTTPResponseSerializerserializer];      //设置相应内容类(这里根据所请求的数据类型可自行选择)      manager.responseSerializer.acceptableContentTypes = [NSSetsetWithObjects:@"application/json",                                                          @"text/html",                                                          @"image/jpeg",                                                          @"image/png",                                                          @"application/octet-stream",                                                          @"text/json",                                                          nil];      return manager;  }     #pragma mark - GET方式请求新闻数据  - (void)requestHomeNewsDataWithLastKey:(NSString *)lastKey {      AFHTTPSessionManager *manager = [self manager];      //拼接URL      NSString *urlString = [NSStringstringWithFormat:@"http://app3.qdaily.com/app3/homes/index/%@.json?",lastKey];         [managerGET:urlStringparameters:nilprogress:^(NSProgress * _NonnulldownloadProgress) {         } success:^(NSURLSessionDataTask * _Nonnulltask, id  _NullableresponseObject) {           //  JSON数据转字典          NSDictionary *dataDictionary = [NSJSONSerializationJSONObjectWithData:responseObjectoptions:NSJSONReadingMutableContainerserror:nil];             if (self.newsDataBlock) {              self.newsDataBlock([dataDictionaryvalueForKey:@"response"]);          }         } failure:^(NSURLSessionDataTask * _Nullabletask, NSError * _Nonnullerror) {         }];  }     - (void)newsDataBlock:(JFHomeNewsDataManagerBlock)block {      self.newsDataBlock = block;  }     @end  </code></pre>    <p>使用GET方法请求数据,将last_key参数拼接到URL中:</p>    <pre>  <code class="language-objectivec">//拼接URL NSString *urlString = [NSString stringWithFormat:@"http://app3.qdaily.com/app3/homes/index/%@.json?",lastKey];  </code></pre>    <p>打断点看我们请求到的JSON数据转换成字典dataDictionary里的内容:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7fe1860e7d49b6738f80a4b2928c69db.png"></p>    <p>response里的数据是我们需要处理的,所以用 valueForKey: 方法拿到其数据返回给JFHomeViewController:</p>    <pre>  <code class="language-objectivec">[dataDictionaryvalueForKey:@"response"]  </code></pre>    <p>数据处理部分到此算是告一段落,接下来就是要把数据展示到界面上,然后实现好奇心日报的交互动画。</p>    <p>3、UI布局</p>    <p>3.1 项目中实现的三种不同的cell样式</p>    <p>cellType对应的就是数据模型JFFeedsModel中的type</p>    <ul>     <li>cellType = 0,UITableViewCell的样式是上方新闻配图、然后是新闻标题,最下面是副标题。</li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d13b3ae06e955104aeca06be73a65e7c.png"></p>    <ul>     <li>cellType = 1,UITableViewCell的样式是新闻配图在右侧,左侧是新闻标题,在其下面是新闻种类、评论数和点赞数。</li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0bc2e7ea524194ca7e65844c32060bb7.png"></p>    <p>cellType = 2,UITableViewCell的样式和cellType = 0时基本一致,就多了最下面的新闻种类、评论数和点赞数。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/cd9374c022e118cc247b29be86a9f4ca.png"></p>    <p>显然用苹果提供的cell是不行的,所以创建JFHomeNewsTableViewCell继承自UITableViewCell,然后我们来自定义cell。</p>    <p>JFHomeNewsTableViewCell.h文件中声明属性:</p>    <pre>  <code class="language-objectivec">#import     @interface JFHomeNewsTableViewCell : UITableViewCell     /** cell的类型(0、1、2)*/  @property (nonatomic, copy) NSString *cellType;  /** 配图*/  @property (nonatomic, copy) NSString *newsImageName;  /** 标题*/  @property (nonatomic, copy) NSString *newsTitle;  /** 副标题*/  @property (nonatomic, copy) NSString *subhead;     /**  *  新闻类型(设计、智能、娱乐等)  */  @property (nonatomic, copy) NSString *newsType;  /** 该条新闻的评论数*/  @property (nonatomic, copy) NSString *commentCount;  /** 点赞数*/  @property (nonatomic, copy) NSString *praiseCount;  /** 新闻发布时间*/  @property (nonatomic, assign) NSIntegertime;     @end  </code></pre>    <p>动态的设置cell的样式(frame)是在 - (void)layoutSubviews; 方法中,这里我把相关代码都放在了 - (void)customUI; 方法中,这里使用了 Masonry 自动布局。</p>    <pre>  <code class="language-objectivec">- (void)layoutSubviews {      [super layoutSubviews];      [self customUI];  }  </code></pre>    <p>使用 Masonry 时有一点一定要 <strong>注意</strong> ,必须先把子控件添加到父控件上才能用 Masonry 去自动布局,否则父控件上没有相应的子控件何谈布局呢。</p>    <p>在JFHomeViewController.m文件中重写UITableView的 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; 代理方法来动态设置cell的高度;同样是根据类型判断。</p>    <pre>  <code class="language-objectivec">/// 根据cell类型返回cell高度  - (CGFloat)tableView:(UITableView *)tableViewheightForRowAtIndexPath:(NSIndexPath *)indexPath {      JFHomeNewsTableViewCell *cell = self.cell;      if ([cell.cellTypeisEqualToString:@"0"]) {          return 330;      }else if ([cell.cellTypeisEqualToString:@"2"]) {          return 360;      }else {          return 130;      }  }  </code></pre>    <p>3.2 悬浮按钮(JFSuspensionView)</p>    <p>3.2.1 悬浮按钮实现的原理:</p>    <ul>     <li>其实很简单,就是在JFHomeViewController控制器的View上添加一个JFSuspensionView,只是要在homeNewsTableView之上,使用下面方法或者使用 - (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview; 方法,此时当你滑动UITableView的时候按钮就是悬浮不动的。</li>    </ul>    <pre>  <code class="language-objectivec">- (void)loadView {      [super loadView];         [self.viewaddSubview:self.homeNewsTableView];      [self.viewaddSubview:self.jfSuspensionView];  }  </code></pre>    <p>3.2.2 在JFSuspension.h文件中用枚举定义了悬浮按钮的四种Tag类型和四种block回调方法,其实四种Tag类型一一对应四个回调函数,通过判断Tag类型来执行相应的block函数。</p>    <pre>  <code class="language-objectivec">#import     /// 悬浮按钮种类(tag)枚举  typedef NS_ENUM(NSInteger, JFSuspensionButtonStyle) {      JFSuspensionButtonStyleQType = 1,  //  Qlogo样式 (弹出JFMenuView)      JFSuspensionButtonStyleCloseType,  //  关闭样式(关闭JFMenuView)      JFSuspensionButtonStyleBackType,    //  返回样式(返回到JFHomeViewController根View)      JFSuspensionButtonStyleBackType2    //  返回样式2(返回到JFMenuView)  };     typedef void(^JFSuspensionViewBlock)();     @interface JFSuspensionView : UIView     /** 悬浮按钮,设置按钮样式(tag)*/  @property (nonatomic, assign) NSIntegerJFSuspensionButtonStyle;     /** 弹出菜单界面*/  @property (nonatomic, copy) JFSuspensionViewBlockpopupMenuBlock;     /** 关闭菜单界面*/  @property (nonatomic, copy) JFSuspensionViewBlockcloseMenuBlock;     /** 返回到homeNewsViewController*/  @property (nonatomic, copy) JFSuspensionViewBlockbackBlock;     /** 返回到JFMenuView*/  @property (nonatomic, copy) JFSuspensionViewBlockbackToMenuViewBlock;     - (void)popupMenuBlock:(JFSuspensionViewBlock)block;     - (void)closeMenuBlock:(JFSuspensionViewBlock)block;     - (void)backBlock:(JFSuspensionViewBlock)block;     - (void)backToMenuViewBlock:(JFSuspensionViewBlock)block;     @end  </code></pre>    <p>悬浮按钮绑定的 - (void)clickSuspensionButton:(UIButton *)sender 点击事件处理方法,通过tag判断需执行的事件。</p>    <pre>  <code class="language-objectivec">- (void)clickSuspensionButton:(UIButton *)sender {      if (_suspensionButton.tag == JFSuspensionButtonStyleQType || _suspensionButton.tag == JFSuspensionButtonStyleCloseType) {            //需要做的事情...      }         //弹出菜单界面      if (_suspensionButton.tag == JFSuspensionButtonStyleQType) {            //需要做的事情...      }         //关闭菜单界面      if (_suspensionButton.tag == JFSuspensionButtonStyleCloseType) {        //需要做的事情...      }         //返回到homeNewsViewController      if (_suspensionButton.tag == JFSuspensionButtonStyleBackType) {            //需要做的事情...      }         //返回到JFMenuView      if (_suspensionButton.tag == JFSuspensionButtonStyleBackType2) {            //需要做的事情...      }  }  </code></pre>    <p>3.3.3 上滑隐藏悬浮按钮,下滑显示悬浮按钮。</p>    <p>在JFHomeViewController中实现UIScrollDelegate代理方法</p>    <pre>  <code class="language-objectivec">#pragma mark --- UIScrollDelegate  /// 滚动时调用  - (void)scrollViewDidScroll:(UIScrollView *)scrollView {      if (scrollView.contentOffset.y > _contentOffset_Y + 80) {          [self suspensionWithAlpha:0];      } else if (scrollView.contentOffset.y   </code></pre>    <p>3.3 菜单(JFMenuView)界面布局</p>    <p>下图层级关系对照源码看,清晰明了!(JFNewsClassificationView层级关系和JFMenuView一样)</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d750749c3505f5137a8b409c728716d4.png"></p>    <p>懒加载模糊层:</p>    <pre>  <code class="language-objectivec">- (UIVisualEffectView *)blurEffectView {      if (!_blurEffectView) {          UIBlurEffect *blurEffect = [UIBlurEffecteffectWithStyle:UIBlurEffectStyleDark];          _blurEffectView = [[UIVisualEffectViewalloc] initWithEffect:blurEffect];          _blurEffectView.frame = self.frame;      }      return _blurEffectView;  }  </code></pre>    <h3><strong>四、弹簧动画效果</strong></h3>    <p>弹簧动画效果是用 <strong>非死book</strong> 开源的 pop动画引擎 ,简单使用的话推荐看: POP介绍与使用实践(快速上手动画) ,下面这段代码就是实例化了一个弹簧动画(POPPropertyAnimation),用来实现菜单界面的弹出效果。</p>    <pre>  <code class="language-objectivec">/** pop动画  *  POPPropertyAnimation    动画属性  *  view                    动画对象  *  offset                  偏移量  *  speed                   动画速度  */  - (void)popAnimationWithView:(UIView *)viewOffset:(CGFloat)offsetspeed:(CGFloat)speed {      POPSpringAnimation *popSpring = [POPSpringAnimationanimationWithPropertyNamed:kPOPLayerPositionY];      popSpring.toValue = @(view.center.y + offset);      popSpring.beginTime = CACurrentMediaTime();      popSpring.springBounciness = 11.0f;      popSpring.springSpeed = speed;      [viewpop_addAnimation:popSpringforKey:@"positionY"];  }  </code></pre>    <p>菜单的隐藏动画使用的是苹果提供的UIView的动画,如下隐藏菜单的顶部View和底部View。</p>    <pre>  <code class="language-objectivec">/// 动画隐藏headerView和footerView  - (void)hideMenuViewAnimation {      [UIViewanimateWithDuration:0.1 animations:^{          [self headerViewOffsetY:-KHeaderViewH];          [self footerViewOffsetY:JFSCREENH_HEIGHT];      } completion:^(BOOL finished) {          //隐藏JFMenuView          [self setHidden:YES];      }];  }  </code></pre>    <p>如上用原生的UIView动画加 pop动画引擎 就可以实现悬浮按钮和菜单view的弹簧效果,若想动画效果更加自然,是需要耐心的调整 pop动画引擎 属性。</p>    <p>总结:这篇文章不是一个细致讲解这个项目的文档,结合这篇文章去看 <strong> JFQDaily源码 </strong> 相信你会有所收获的,毕竟是三天写出来的东西,代码可能有不规范的地方,欢迎指出纠正,出于好奇心和喜欢,一气呵成的写完了主要功能。</p>    <p>来自:http://ios.jobbole.com/90974/</p>    <p> </p>