知乎日报前三周总结
目录
前言
首页
网络请求
上拉加载
详情页
加载WebView
左右滑动
主页与详情页同步更新
总结
前言
在这几周进行了知乎日报的仿写,这篇博客来总结一下前三周仿写的内容
首页
首页的界面如图所示,其实就是一个导航栏和一个数据视图组成的,这个数据视图的第一个cell中存放了一个无限轮播图,UI的仿写难度并不大,主要麻烦的地方在于对网络请求接口的使用和数据的处理。
网络请求
这里网络请求通过GCD来解决异步导致的数据未请求完成就加载UI的问题,
// 进行网络请求
dispatch_group_enter(group);
[manager NetWorkGetWithCompletion:^(NSDictionary * _Nonnull userData, NSError * _Nonnull error) {
if (!error) {
self.homeModel = [HomeModel yy_modelWithDictionary:userData];
NSArray *array = self.homeModel.stories;
for (SubModel *subModel in array) {
[self.allArray addObject:[subModel yy_modelToJSONObject]];
}
NSLog(@"cishi:%d",[self.allArray count]);
}
dispatch_group_leave(group); // 这个需要在所有请求的最后调用
}];
// 确保所有请求和图片加载完成后才更新 UI
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 在这里进行视图更新
self.homeView = [[HomeView alloc] initWithFrame:self.view.frame];
self.homeView.tableView.delegate = self;
self.homeView.tableView.dataSource = self;
self.homeView.tableView.userInteractionEnabled = YES;
[self.homeView.tableView reloadData];
[self.view addSubview:self.homeView];
});
这里就是把本来异步进行的网络请求,放进了一个串行队列中,当网络请求完成后,再通知视图更新UI。关于GCD的知识,笔者暂时只知道如何使用这一方法来解决异步的问题,后续学习了会再发博客进行补充。
这里申请到的文章图片只是图片的url,笔者使用了第三方库SDWebImage来加载图片,一开始我的思路是先将数据申请和图片加载都完成,并将数据和图片保存在数组中,加载UI时直接访问数组中对应元素,但是这样就会出现异步导致数组为空的问题,因为需要同时进行两个后台请求,当时无法解决,后来意识到在最外层的请求完成后再退出队列就好了。当时笔者改变了思路,将申请到的model传给view层,当view层需要访问图片时,再获取数组中的url并调用方法加载,这样既不会有问题了。
上拉加载
在首页还有一个比较重要的功能,也是笔者最新学到的,就是上拉加载的写法。
实现tableView中的下拉上拉刷新效果及相关基础概念
参考这篇文章,笔者在首页中实现了上拉加载的效果,简单来说,就是在tableView的下方创建一个视图,当tableView偏移量大于某一个值时,就调用方法获取新的数据,再将新的数据更新到UI上,并更新tableView下方的视图。主要这里获取新的数据时需要更新url,笔者使用了NSDate来处理日期。
- (void)reloadDate {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMdd"];
NSString* dateString = [formatter stringFromDate:self.date];
NSLog(@"%@", dateString);
dispatch_group_t group = dispatch_group_create();
Manager *manager = [Manager shareManeger];
// 进行网络请求
dispatch_group_enter(group);
[manager NetWorkGetBefore: dateString WithCompletion:^(NSDictionary * _Nonnull userData, NSError * _Nonnull error) {
if (!error) {
BeforeModel* beforeModel = [[BeforeModel alloc] init];
beforeModel = [BeforeModel yy_modelWithDictionary:userData];
[self.beforeArray addObject:beforeModel];
NSArray *array = beforeModel.stories;
for (BeforeSubModel *beforeSubModel in array) {
[self.allArray addObject:[beforeSubModel yy_modelToJSONObject]];
}
NSLog(@"%@",self.allArray);
}
dispatch_group_leave(group); // 这个需要在所有请求的最后调用
}];
// 确保所有请求和图片加载完成后才更新 UI
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 在这里进行视图更新
[self.homeView.tableView reloadData];
self.homeView.footer.frame = CGRectMake(0, self.homeView.tableView.contentSize.height + 300, 394, 50);
[self.homeView.footerLabel setText:@"下拉可以刷新"];
self.homeView.footerRefreshing = NO;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *oneDayBeforeComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:self.date];
oneDayBeforeComponents.day -= 1;
if (oneDayBeforeComponents.day < 1) {
NSRange dayRange = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:self.date];
oneDayBeforeComponents.day = dayRange.length;
oneDayBeforeComponents.month -= 1;
if (oneDayBeforeComponents.month < 1) {
oneDayBeforeComponents.month = 12;
oneDayBeforeComponents.year -= 1;
NSRange newDayRange = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:self.date];
oneDayBeforeComponents.day = newDayRange.length;
}
}
self.date = [calendar dateFromComponents:oneDayBeforeComponents];
});
}
- (void)dealFooter {
if (self.homeView.footerRefreshing) {
return;
}
CGFloat footerOffset = self.homeView.tableView.contentSize.height - self.homeView.tableView.frame.size.height + self.homeView.footerLabel.frame.size.height + 100;
// NSLog(@"%d %d %d", self.homeView.tableView.contentSize.height, self.homeView.tableView.frame.size.height, self.homeView.footerLabel.frame.size.height);
if (self.homeView.tableView.contentOffset.y >= footerOffset) {
[self footerBeginRefreshing];
}
}
- (void)footerBeginRefreshing {
if (self.homeView.footerRefreshing) {
return;
}
[self.homeView.footerLabel setText:@"正在刷新数据"];
self.homeView.footerRefreshing = YES;
// [UIView animateWithDuration:0.25 animations:^{
// UIEdgeInsets inset = self.homeView.tableView.contentInset;
// inset.top += self.homeView.footerLabel.bounds.size.height;
// self.homeView.tableView.contentInset = inset;
// }];
[self reloadDate];
}
详情页
加载WebView
详情页这里,webView的url可以通过接口获取到,需要调用方法将webView加载出来,笔者这里在使用webView前,加入了"WKWebView+AFNetworking.h"这个头文件。
NSURL *urlWeb = [NSURL URLWithString:self.allArray[self.page - 1][@"url"]];
NSURLRequest *webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];
[self.wkWebView loadRequest:webRequest];
[self.view addSubview: self.wkWebView];
这样就可以加载出webView
左右滑动
详情页的一个比较重要的功能就是要实现左右滑动,这里也是笔者认为详情页面最复杂的地方。笔者的思路是计算每一个cell的位置,当点击某个cell时,将位置往controller和view层传值,并根据cell的位置来决定webView的位置和拿来加载webView的url在数组中的位置。每当滑动视图滑动到每一天的最后一页时,就加载后一天的数据,每滑动一页,就加载当前页的视图。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.x <= 0) {
// 滚动到最左端,禁止继续向左滑动
scrollView.contentOffset = CGPointMake(0, 0);
}
NSInteger currentPage = (scrollView.contentOffset.x / [UIScreen mainScreen].bounds.size.width) + 1;
self.newpage= [NSNumber numberWithInteger:currentPage + 5];
// 当滚动视图向右滚动且快接近画布右边缘时,触发加载数据的操作
if (scrollView.contentOffset.x > ([self.allArray count] * [UIScreen mainScreen].bounds.size.width - [UIScreen mainScreen].bounds.size.width * 1.5) && self.isLoadingMoreData == NO) {
self.isLoadingMoreData = YES;
[self loadMoreData:(NSInteger)currentPage + 1];
}
if (currentPage != (self.webview.page) && isLoadingWebView == NO && self.isLoadingMoreData == NO && ![self.pageSet containsObject:self.newpage]) {
NSLog(@"yin:%d %d %d",self.webview.page ,currentPage, [self.allArray count]);
isLoadingWebView = YES;
self.webview.page = currentPage;
[self.pageSet addObject:self.newpage];
[[NSNotificationCenter defaultCenter] postNotificationName:@"newPage" object:nil userInfo:nil];
}
}
除此之外,还要实时更新下方工具栏中的点赞和评论数量,笔者这里不保存点赞和评论的数量,每一次滑动到一个新的视图,就重新加载一次点赞数和评论数。
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSInteger currentPage = (scrollView.contentOffset.x / [UIScreen mainScreen].bounds.size.width) + 1;
if(isLoadingWebView == NO && _isLoadingMoreData == NO) {
dispatch_group_t group = dispatch_group_create();
Manager *manager = [Manager shareManeger];
// 进行网络请求
dispatch_group_enter(group);
[manager NetWorkGetFor:self.allArray[currentPage][@"id"]WithCompletion:^(NSDictionary * _Nonnull userData, NSError * _Nonnull error) {
if (!error) {
ExtraModel* extraModel = [ExtraModel yy_modelWithDictionary:userData];
self.webview.likeLabel.text = [NSString stringWithFormat:@"%d",extraModel.popularity];
self.webview.commentLabel.text = [NSString stringWithFormat:@"%d",extraModel.comments];
}
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
}
}
主页与详情页同步更新
这里笔者通过两个数组来实现同步更新,一个存放所有文章数据的字典,一个存放每天数据的字典,在从主页点击详情页时,直接通过属性传值,而在详情页加载好数据后,就只能通知传值并调用方法更新tableView和上拉加载的底部视图就好
- (void)loadMoreData:(NSInteger)currentPage {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMdd"];
NSString* dateString = [formatter stringFromDate:self.date];
NSLog(@"%@", dateString);
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
dispatch_group_t group = dispatch_group_create();
Manager *manager = [Manager shareManeger];
// 进行网络请求
dispatch_group_enter(group);
[manager NetWorkGetBefore: dateString WithCompletion:^(NSDictionary * _Nonnull userData, NSError * _Nonnull error) {
if (!error) {
BeforeModel* beforeModel = [[BeforeModel alloc] init];
beforeModel = [BeforeModel yy_modelWithDictionary:userData];
NSArray *array = beforeModel.stories;
for (BeforeSubModel *beforeSubModel in array) {
[self.allArray addObject:[beforeSubModel yy_modelToJSONObject]];
}
[updateDictionary setValue:beforeModel forKey:@"beforeModel"];
[updateDictionary setValue:self.allArray forKey:@"allArray"];
}
dispatch_group_leave(group); // 这个需要在所有请求的最后调用
}];
// 确保所有请求和图片加载完成后才更新 UI
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 在这里进行视图更新
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *oneDayBeforeComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:self.date];
oneDayBeforeComponents.day -= 1;
if (oneDayBeforeComponents.day < 1) {
NSRange dayRange = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:self.date];
oneDayBeforeComponents.day = dayRange.length;
oneDayBeforeComponents.month -= 1;
if (oneDayBeforeComponents.month < 1) {
oneDayBeforeComponents.month = 12;
oneDayBeforeComponents.year -= 1;
NSRange newDayRange = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:self.date];
oneDayBeforeComponents.day = newDayRange.length;
}
}
self.date = [calendar dateFromComponents:oneDayBeforeComponents];
[updateDictionary setValue:self.date forKey:@"date"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"update" object:nil userInfo:updateDictionary];
[[NSNotificationCenter defaultCenter] postNotificationName:@"layoutNewScrollView" object:nil userInfo:nil];
self.isLoadingMoreData = NO; // 重置加载标志
if (currentPage != (self.webview.page) && isLoadingWebView == NO && self.isLoadingMoreData == NO && ![self.pageSet containsObject:self.newpage]) {
isLoadingWebView = YES;
self.webview.page = currentPage;
[self.pageSet addObject:self.newpage];
[[NSNotificationCenter defaultCenter] postNotificationName:@"newPage" object:nil userInfo:nil];
}
});
}
这里笔者通知传值用的字典dictionary初始化和赋值是在不同的区域进行的,因为这个dictionary中不仅要保存新申请到的数据,还要保存更新后的日期,所以初始化必须在让dictio全区可以访问的位置, 赋值一个在获取完数据之后,另一个在日期更新之后。
总结
后续的评论区和收藏中心两个界面,笔者已经完成了评论区的数据请求,后续学习UItextView来对界面布局,收藏中心需要调用第三方库,笔者也将在后续完成