VTMagic 搭建一个iOS新闻类APP及使用心得

  • 还记得那是2年前的 6月份, 那时的我早上上班第一件事就是上简书 看看最新的技术文章,因为那时候每天平均都有10几篇iOS技术文章新鲜出炉,可惜自从XXX事件后,就没那种盛景了,好想写一首长恨歌。
    2016.06.05 当九流书生 发布VTMagic的使用介绍 第一时间下载下来,运行后真的感觉高大尚,本人也做过新闻类APP ,也封装过类似的框架级控件,但就如 九流书生 文章里所说的一样:大多都比较粗糙,不利于后续维护和扩展.
  • 我也研究了很多 同类型的第三方库。大多库都是,子页面有多少就要创建多少个类,内存压力很大,子页面的生命周期也无法响应,最终还是选择了VTMagic,核心特性:页面重用,预加载,每滑动子页面可以响应生命周期,这几点特性真的很棒,研究一阵 直接重构了项目 并且稳定运行1年多了

对VTMagic 有一些 使用上的心得 写个新闻类demo 跟大家分享一下
点击 ZBNews 下载 查看代码
demo 所用接口是网络抓取 可能会在未来某个时候无法使用

1.pods 代码不是最新的问题

因为作者九流书生 很长时间没有更新pods了,VTMagic 现在pods版本是1.2.4,并没有适配iOS11。 所以你如果使用pods 引用VTMagic 建议要用以下格式. 会多个属性displayCentered 和适配iOS11

pod 'VTMagic', :git => 'https://github.com/tianzhuo112/VTMagic.git'

2.新属性的使用

上面提到displayCentered 属性 是被选中的menuItem是否居中显示 ,老版本VTMagic 被选中的menuItem 是不居中的。而是靠previewItems 属性控制的

/**
*  被选中的menuItem是否居中显示,默认NO
*/
@property (nonatomic, assign) BOOL displayCentered;

/**
*  导航菜单item的预览数,默认为1
*
*  @warning displayCentered为YES时,该属性无效
*/
@property (nonatomic, assign) NSUInteger previewItems;

3. 系统返回滑动手势的失效问题

如果 你的APP 要隐藏导航栏 ,并且还需要系统的返回滑动手势,不要使用
[self.navigationController setNavigationBarHidden:YES animated:YES]; 此方法会屏蔽系统返回滑动手势。
要使用 self.navigationController.navigationBar.hidden = YES;
在要跳转的页面 有需要显示导航栏的页面
在viewWillAppear 方法里self.navigationController.navigationBar.hidden = NO;即可

//MainViewController
- (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
   //此方法会使 系统侧滑返回手势失效
   //[self.navigationController setNavigationBarHidden:YES animated:YES];
   self.navigationController.navigationBar.hidden = YES;
}

4.加载数据问题

看各大新闻APP 如果把子视图比做ABC.. .都是在浏览A 界面自动加载数据后 在往右滑动到B界面 或是多个界面,在回到A界面时数据不会自动加载数据了,除非下拉刷新,或是过了一定时间在回到A界面才能在自动加载数据。 VTMagic是在在子视图控制器中加载数据的请求前 要加入此判断.
另外建议在 viewWillDisappear 做取消网络请求和结束刷新的操作

- (void)viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
  NSTimeInterval currentStamp = [[NSDate date] timeIntervalSince1970];
   if (self.dataArray.count&& currentStamp - _mainModel.lastTime < timeOut) {
       return; //必须加此判断  否则会出现数据重复加载
   }
   _mainModel.lastTime = currentStamp;
      //网络请求方法 或是 进入刷新状态
}

- (void)viewWillDisappear:(BOOL)animated {
   [super viewWillDisappear:animated];
   [self.viewModel cancelRequestWithMenuInfo:_menuInfo]; // 取消不必要的网络请求
   [self endRefresh];
}

5.预加载

预加载 这是VTMagic 尤为突出的 功能,但是部分人并不会使用此功能 ,包括作者的VTMagic Demo 也没有完全发挥此功能。
我说的前提是 needPreloading=YES;//预加载开关 为开的状态 ,如果不用预加载的 请忽略本条。
如果对VTMagic有了解的同学都知道 VTMagic 的子视图如果只是一个ViewController ,都是在 viewDidAppear 进行网络请求,
可我们在进行页面左右滑动的时候并没有在视觉上感受到预加载,感觉还是从A滑动到了B页面显示全部后B才去加载数据的,如果在加上自动触发下拉刷新的动画 B页面很长时间才显示UI和数据。 这和理想状态不符。

  • 我们先在viewDidAppear 进行带自动刷新的网络请求
- (void)viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
   self.collectionView.scrollsToTop = YES;
   NSInteger pageIndex = [self vtm_pageIndex];
   SLog(@"当前页面索引: %ld", (long)pageIndex);
   [self requestListData];//加载数据
}
loading111GIF.gif

健康栏目 滑动到 命理栏目 命理栏目已经全部出来 之后才去加载的。这是预加载吗? 你看到预加载了吗? 肯定不是。

  • 下面我来实现真正的预加载
    如果使用VTMagic的同学知道 每个子视图 都会引用 MenuInfo
.h
@class MenuInfo;
@interface RACChannelViewController : BaseViewController
/**
*  菜单信息
*/
@property (nonatomic, strong) MenuInfo *menuInfo;
@end

在.m 重写 大家在VTMagic Demo里看到 setMenuInfo 都是看里面写的 读缓存的逻辑
其实 这个方法才是预加载的入口 当我们加载A界面 时,B界面的这个方法也会触发 依次触发 。就可以实现预加载了。我们可以先看下log

.m
#pragma mark - accessor methods
- (void)setMenuInfo:(MenuInfo *)menuInfo {
   _menuInfo = menuInfo;
    NSLog(@"预加载: %@",_menuInfo.title);
   //VTMagic官方Dome 加载列表缓存的方法  [self loadLocalData];
}
loading222Gif.gif

log 显示 当我们在移动到一个栏目时 会预加载当前栏目的下一个栏目

下面我们就 - (void)setMenuInfo:(MenuInfo *)menuInfo 中加入网络请求并在请求成功后刷新列表

#pragma mark - accessor methods
- (void)setMenuInfo:(MenuInfo *)menuInfo {
   _menuInfo = menuInfo;
   _page=1;
   [self.viewModel requestListDataWithPage:_page menuInfo:_menuInfo requestType:ZBRequestTypeRefresh];
   SLog(@"预加载: %@",_menuInfo.title);

   //VTMagic官方Dome  加载列表缓存的方法  [self loadLocalData]; 
}
loading333GIF.gif

可以看到在setMenuInfo 因为会预加载下一个栏目,我们在当前栏目时,下一个栏目进行了网络请求 并在请求成功后刷新列表 滑动可看到已经提前加载好下一个栏目的页面了

6. 页面重用 缓存

这个重用机制 作者已经说的很清楚了 看下图


1F74CB23-2A2A-44CF-A515-28D55A34CC5C.png

但是因为重用机制 使得页面在左右滑动俩个子视图页面后,会出现页面数据混乱的问题,这个作者也给出了解决方案

- (void)viewDidDisappear:(BOOL)animated {
   [super viewDidDisappear:animated];
    [self savePageInfo];  // //保存页面数据 VTMagic官方Dome 的方法
}

#pragma mark - accessor methods
- (void)setMenuInfo:(MenuInfo *)menuInfo {
   _menuInfo = menuInfo;
   _page=1;
   SLog(@"预加载: %@",_menuInfo.title);
   [self loadLocalData];// VTMagic官方Dome 加载列表缓存的方法
}

//VTMagic官方Dome 的方法
- (void)savePageInfo {
    [[DataManager sharedInstance] savePageInfo:self.dataArray menuId:_menuInfo.menu_id];
}
- (void)loadLocalData {
    NSArray *cacheList = [[DataManager sharedInstance] pageInfoWithMenuId:_menuInfo.menu_id];
    [self.dataArray addObjectsFromArray:cacheList];
    [self.tableView reloadData];
}

就是在A页面 消失时 触发viewDidDisappear 保存当前页面的列表数组,在用户滑动回 A 页面时 提前触发setMenuInfo 取出列表数组 重新刷新列表
虽然这个方法运行没有问题,但是并不是最优的解决方案

下面放出我的解决方案,也许作者本人没有这么 做

- (void)viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
   self.tableView.scrollsToTop = YES;
    _page=1;
   [self request:_page requestType:ZBRequestTypeRefresh];//重新数据
}

#pragma mark - accessor methods
- (void)setMenuInfo:(MenuInfo *)menuInfo {
   _menuInfo = menuInfo;
/*
   //伪代码
     if(有缓存){
     //读取缓存 刷新列表  
     }else{
      //重新请求 刷新列表 存储缓存
     }
*/
   SLog(@"预加载: %@",_menuInfo.title);
   _page=1;
    [self request:_page requestType:ZBRequestTypeCache];     //如有缓存使用缓存,无缓存重新加载
}

在viewDidAppear 重新请求或者调用下拉刷新
在setMenuInfo 方法里 进行(如果有缓存使用缓存,无缓存重新加载)策略的请求 这样既满足的预加载, 有兼顾了缓存。

进一步解释步骤:当栏目在A时 , 栏目B已经执行setMenuInfo ,假如B没有缓存 进行了重新请求并存储缓存,栏目A浏览一会 滑动到栏目B, 栏目B执行viewDidAppear 进行刷新请求 并更新了缓存, 同时栏目C执行setMenuInfo ,依次滑动并都请求成功A->B->C->D->E->F,都是同样的步骤.
当用户网往回滑动A<-B<-C<-D<-E<-F 也是先执行 setMenuInfo 方法,因为都浏览过都请求成功了 都有了缓存,setMenuInfo 里面会执行读取缓存 的步骤。

另外在设置 页面可以清除缓存 ,之后在回到主页进行反复调试

request: requestType: 方法 最终调到是我封装的网络请求 ZBNetworking 里面就有这个策略 //如有缓存使用缓存,无缓存重新加载,简直是完美搭配 看最终效果

loading444GIF.gif

7. 嵌套滚动问题

对于现在比较流行嵌套滚动 VTMagic 框架本身不支持 它的headerView 和底部容器是加载在一个UIView上面没法进行嵌套滚动 只有一个隐藏headerView的方法
在子视图 调用一下

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
   CGFloat yOffset = scrollView.contentOffset.y;
   if (yOffset>0) {
       self.magicController.magicView.againstStatusBar = YES;
       [self.magicController.magicView setHeaderHidden:YES duration:0.35];
   }else{
       self.magicController.magicView.againstStatusBar = NO;
       [self.magicController.magicView setHeaderHidden:NO duration:0.35];
   }
}
loading555GIF.gif

2019.6.28日更新

有另外一个框架 可以结合VTMagic 一起使用达到嵌套滚动效果,而且性能方面没有问题,点击GKPageScrollView,查看,我已联系作者,请其添加了VTMagic例子。这样又有嵌套滚动效果,又可以继续使用VTMagic。
注意 GKPageScrollView Demo 中不太完善 。忘记写[self addChildViewController:self.magicController]; 了。可以在代码中找到懒加载的pageView里添加。

- (UIView *)pageView {
   if (!_pageView) {
       [self addChildViewController:self.magicController];//添加此行代码
       _pageView = self.magicController.view;
   }
   return _pageView;
}
1_GIF.gif

8. meunButton 添加竖线的问题,

在VTMagic 中 菜单按钮是在
VTMagicViewDataSource 协议中代理方法中创建的 如下

- (UIButton *)magicView:(VTMagicView *)magicView menuItemAtIndex:(NSUInteger)itemIndex{
   static NSString *itemIdentifier = @"itemIdentifier";
   MenuButton *menuItem = [magicView dequeueReusableItemWithIdentifier:itemIdentifier];
   if (!menuItem) {
       menuItem = [MenuButton buttonWithType:UIButtonTypeCustom];
       menuItem.titleLabel.font = [UIFont fontWithName:@"Helvetica" size:16.f];
       [menuItem setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
       [menuItem setTitleColor:[UIColor greenColor] forState:UIControlStateSelected];
   }
   menuItem.verticalLineHidden=(_menuList.count-1==itemIndex)?YES:NO;
   return menuItem;
}

在代理中 返回的必须是UIButton 类型,添加竖线可以创建一个继承UIButton的类,在里面添加一个竖线的view ,代理里面用自己创建的类 替换UIButton返回就可以了。


D02292ED-1DE9-43BD-B68A-FFD3B8E6C515.png

9. 滑动到第一个页面 继续滑动响应全屏手势pop出页面

当UIScrollView在水平方向滑动到第一个时,默认是不能全屏滑动返回的,注意是全屏滑动,系统的滑动手势是滑动边缘触发,我集成的是FDFullscreenPopGesture来全屏滑动,但是滑动到第一个页面 继续滑动无法响应全屏手势pop出页面。
解决办法是写一个 UIScrollView的分类 ,重写手势代理方法,这个解决方案对所有以UIScrollView及其子类为基础的全屏滑响应手势pop功能都有用,所以不限制在VTMagic使用,其他类似库也可以
具体代码下载demo看UIScrollView+ZBKit类


Scrollgif.gif

先介绍这么多,以后再有领悟会补充。另外ZBNews中还实现了阅读日历 ,收藏,离线下载 功能 当然没有细致的制作UI 只是功能的展示 最后点击 ZBNews 下载

一直在努力学习中,大家多多包涵。

推荐阅读更多精彩内容