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 下载

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

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,936评论 3 118
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 每年都会有那么一抹金黄闯入你的眼睛, 今年秋天,成都已进入“满城尽带黄金甲”的景象。 你不用刻意寻找,银杏会与你不...
    美邻共享阅读 938评论 0 0
  • 早上,我问了他。他很淡定,笑了笑问我怎么?又生气了? 我怎么也想不到,我们现在为什么变成了这样!其实我任性,闹闹小...
    菲er_阅读 168评论 0 0