VTMagic 搭建一个iOS新闻类APP及使用心得
- 还记得那是2年前的 6月份, 那时的我早上上班第一件事就是上简书 看看最新的技术文章,因为那时候每天平均都有10几篇iOS技术文章新鲜出炉,可惜自从XXX事件后,就没那种盛景了,好想写一首长恨歌。
2016.06.05 当九流书生 发布VTMagic的使用介绍 第一时间下载下来,运行后真的感觉高大尚,本人也做过新闻类APP ,也封装过类似的框架级控件,但就如 九流书生 文章里所说的一样:大多都比较粗糙,不利于后续维护和扩展. - 我也研究了很多 同类型的库,最终还是选择了VTMagic,所以研究一阵 直接重构了项目 并且稳定运行1年多了
对VTMagic 有一些 使用上的心得 写个新闻类demo 跟大家分享一下
点击 ZBNews 下载 查看代码
demo 所用接口是网络抓取 可能会在未来某个时候无法使用
- 因为作者九流书生 很长时间没有更新pods了,VTMagic 现在pods版本是1.2.4,并没有适配iOS11。 所以你如果使用pods 引用VTMagic 建议要用以下格式. 会多个属性displayCentered 和适配iOS11
pod 'VTMagic', :git => 'https://github.com/tianzhuo112/VTMagic.git'
- 上面提到displayCentered 属性 是被选中的menuItem是否居中显示 ,老版本VTMagic 被选中的menuItem 是不居中的。而是靠previewItems 属性控制的
/**
* 被选中的menuItem是否居中显示,默认NO
*/
@property (nonatomic, assign) BOOL displayCentered;
/**
* 导航菜单item的预览数,默认为1
*
* @warning displayCentered为YES时,该属性无效
*/
@property (nonatomic, assign) NSUInteger previewItems;
- 如果 你的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;
}
- 看各大新闻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];
}
- 预加载 这是VTMagic 尤为突出的 功能,但是部分人并不会使用此功能 ,包括作者的VTMagic Demo 也没有完全发挥此功能。
我说的前提是 needPreloading=YES;//预加载开关 为开的状态 ,如果不用预加载的 请忽略本条。
如果对VTMagic有了解的同学都知道 VTMagic 的子视图 都是在 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 因为会预加载下一个栏目,我们在当前栏目时,下一个栏目进行了网络请求 并在请求成功后刷新列表 滑动可看到已经提前加载好下一个栏目的页面了
-
页面重用 缓存
这个重用机制 作者已经说的很清楚了 看下图
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- 对于现在比较流行嵌套滚动 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
先介绍这么多,以后再有领悟会补充。另外ZBNews中还实现了阅读日历 ,收藏,离线下载 功能 当然没有细致的制作UI 只是功能的展示 最后点击 ZBNews 下载
一直在努力学习中,大家多多包涵。