iOS学习
- 前言
- 一:首页
- 1.网络请求异步
- 菊花控件的实现
- 二:网页webView
- webView的使用
- webView的滑动刷新
- 主页面与网页沟通
- 评论区
- 数据持久化
- 总结
前言
该学期完成了知乎日报的仿写,实现评论的展开与收回,FMDB数据持久化,
一:首页
效果展示:

1.网络请求异步
知乎日报需要同时用到多个网络请求,此时可以采用单例模式,可以有效缩短代码量。网络申请单例模式介绍
但是这还涉及一个请求先后顺序的问题。比如说我们要先获取到新闻内容,再将View的控件进行赋值。代码的运行不会等网络申请结束后才往下运行。就用户体验来讲也是如此。
在使用AFNetworking等网络请求库时,它们通常会处理底层的多线程操作,将网络请求放在后台线程中执行,以确保不会阻塞主线程。这是为了避免在主线程中执行网络请求导致的界面卡顿和不流畅的用户体验。
例如这段代码:
-(void) GetScrollerModel
{self.str_date = [DateModel getCurrentDateString];[[Manger sharedSingleton] NetWorkWithScroller:^(ScrollerModel *Model_Scroller) {self.dict_Scroller = [Model_Scroller ModelToDict:Model_Scroller];for(int i = 0; i < 5; i++) {[self.array_Scroller_ID addObject: self.dict_Scroller[@"top_stories"][i][@"id"]];[self.array_Scroller_URL addObject:self.dict_Scroller[@"top_stories"][i][@"url"]];[self.array_Scroller_Image addObject:self.dict_Scroller[@"top_stories"][i][@"image"]];[self.array_Scroller_title addObject:self.dict_Scroller[@"top_stories"][i][@"title"]];[self.array_Scroller_hint addObject:self.dict_Scroller[@"top_stories"][i][@"hint"]];}self.dict_data = [Model_Scroller ModelToDict:Model_Scroller];[self.array_data addObject:self.dict_data];} andError:^(NSError *error) {NSLog(@"GetScrollerModel错误:%@",error);}];[[Manger sharedSingleton] NetWorkWithTheme:^(CellModel *Model) {self.dict_data = [Model ModelToDict:Model];NSLog(@"dice:%@",self.dict_data[@"stories"][1][@"hint"]);[self.array_data addObject:self.dict_data];} andError:^(NSError *error) {NSLog(@"CellModel错误:%@",error);} andNSString:(NSString *)self.str_date];[self setupTableView];
}
会出现两个问题:
- 网络请求未完成时,还没获取到数据,就加载了View视图。
- 由于多个网络请求是并发执行的,它们的完成顺序可能与代码中的调用顺序不一致。如果后续的操作依赖于前一个请求的结果,可能会导致错误或不一致的状态。
因此我们可以通过如下代码来解决:
-(void) GetScrollerModel
{self.str_date = [DateModel getCurrentDateString];[[Manger sharedSingleton] NetWorkWithScroller:^(ScrollerModel *Model_Scroller) {self.dict_Scroller = [Model_Scroller ModelToDict:Model_Scroller];for(int i = 0; i < 5; i++) {[self.array_Scroller_ID addObject: self.dict_Scroller[@"top_stories"][i][@"id"]];[self.array_Scroller_URL addObject:self.dict_Scroller[@"top_stories"][i][@"url"]];[self.array_Scroller_Image addObject:self.dict_Scroller[@"top_stories"][i][@"image"]];[self.array_Scroller_title addObject:self.dict_Scroller[@"top_stories"][i][@"title"]];[self.array_Scroller_hint addObject:self.dict_Scroller[@"top_stories"][i][@"hint"]];}self.dict_data = [Model_Scroller ModelToDict:Model_Scroller];[self.array_data addObject:self.dict_data];[[Manger sharedSingleton] NetWorkWithTheme:^(CellModel *Model) {self.dict_data = [Model ModelToDict:Model];NSLog(@"dice:%@",self.dict_data[@"stories"][1][@"hint"]);[self.array_data addObject:self.dict_data];dispatch_async(dispatch_get_main_queue(), ^{[self setupTableView];});} andError:^(NSError *error) {NSLog(@"CellModel错误:%@",error);} andNSString:(NSString *)self.str_date];} andError:^(NSError *error) {NSLog(@"GetScrollerModel错误:%@",error);}];}
对于第一个问题,我们采用GCD即dispatch_async(dispatch_get_main_queue(), ^{ });方法。使用这个方法后,我们的代码会在后台线程的进程执行完之后,返回我们的主线程进行任务执行。从而保障UI视图加载时数据请求完成。
对于第二个问题,我采用的是嵌套的方法解决,用一个网络请求嵌套一个网络请求,这样网络请求就会按照预想中的顺序运行。
菊花控件的实现
菊花控件其实就是一个UIActivityIndicatorView属性。我们只需要在开头定义,然后加入到视图中即可。
注意:如果不显示多半是菊花控件加入顺序不对。
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{NSString *str_new = self.array_data[1+_falg][@"date"];if(scrollView.contentOffset.y > self.tableView_First.contentSize.height - self.view.bounds.size.height - 50) {//菊花控件[self.activityIndicator startAnimating];[[Manger sharedSingleton] NetWorkWithTheme:^(CellModel *Model) {[self.array_data addObject:[Model ModelToDict:Model]];self.falg ++;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[self.tableView_First reloadData];[self.activityIndicator stopAnimating];});} andError:^(NSError *error) {NSLog(@"xialaerror:%@",error);} andNSString:(NSString *) str_new];}
}
这里我们可以使用dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});方法,来自定义刷新延迟几秒结束,此处设置的是一秒。
二:网页webView
效果展示:

此处点进来是一个webView,属于 WebKit 框架。
webView的使用
WebView 是一种用于在应用程序中显示网页内容的组件。它允许开发者将网页嵌入到应用程序中,用户可以直接在应用内浏览网页,而不需要跳转到外部的浏览器(如 Safari 或 Chrome)。
首先导入其所属的类
#import <WebKit/WebKit.h>
然后遵守WKNavigationDelegate协议,这个协议主要是包含一些方法函数。并为其创造属性,如下所示:
#import <WebKit/WebKit.h>
@interface ZHWebVC : UIViewController<WKNavigationDelegate>
@property(nonatomic,strong) WKWebView *webView;
@end
在实现文件中,我们只需要初始化后,通过loadRequest方法使用URL进行加载。
NSURL *url = [NSURL URLWithString:self.array_url[self.load_real]];NSURLRequest *request = [NSURLRequest requestWithURL:url];[self.webView loadRequest:request];
下面是一些相关方法:
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {// NSLog(@"收到响应,决定是否跳转");decisionHandler(WKNavigationResponsePolicyAllow); // 允许跳转
}// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {// NSLog(@"发送请求,决定是否跳转");decisionHandler(WKNavigationActionPolicyAllow); // 允许跳转
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {// NSLog(@"页面加载失败: %@", error.localizedDescription);
}// 接收到服务器重定向请求后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {// NSLog(@"接收到服务器重定向请求");
}
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {// NSLog(@"页面开始加载");
}// 页面加载完成时调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {NSLog(@"页面加载完成");
}
webView的滑动刷新
这里要实现左滑右滑一起刷新。
右滑的实现
其实右滑就是一个无限轮播图,但是需要跟网络申请和控件加载结合起来。我们需要根据网络申请得到的URL进行网页的申请,但是每当滑动到最后一个申请的数据时,就需要进行新的数据申请。同时,加载过的网页不要再次加载。
只需要在-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView方法中,实现这两个需求。
- 先计算当前的页面,判断每当页面数是否达到目前已经申请的最后一个,如果已经达到,则进行新的网络请求。如未达到,则判断是否加载网页。
- 建立一个set容器,将每一个加载过的页数保存在其中,每一次滑动到新的页面时,判断是否加载过。
代码实现:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{int indext = scrollView.contentOffset.x / self.view.bounds.size.width;self.load_real = indext;if(indext >= (self.number_now +self.flag_scroll - 1)) {self.flag_scroll++;}if(![self.set_load containsObject:[NSString stringWithFormat:@"%d",indext]]) {if(indext == (self.array_url.count - 1)) {NSString *str_new = [NSString stringWithFormat:@"%@",self.array_date[1+self.cell_falg][@"date"]];[[Manger sharedSingleton] NetWorkWithTheme:^(CellModel *Model) {[self.array_date addObject:[Model ModelToDict:Model]];self.cell_falg ++;for(int i = 0; i < [self.array_date[1+self.cell_falg][@"stories"] count]; i++) {[self.array_url addObject:self.array_date[1+self.cell_falg][@"stories"][i][@"url"] ];[self.arrayID addObject:self.array_date[1+self.cell_falg][@"stories"][i][@"id"] ];[self.arrayImage addObject:self.array_date[1+self.cell_falg][@"stories"][i][@"images"][0]];}NSLog(@"nue url:%@",self.array_url);dispatch_async(dispatch_get_main_queue(), ^{[self LoadWeb];});} andError:^(NSError *error) {NSLog(@"xialaerror:%@",error);} andNSString:(NSString *) str_new];NSLog(@"%d",indext);} else {[self LoadWeb];}}NSString *currentID = self.arrayID[self.load_real];self.favoriteButton.selected = [[ZHDatabaseManager sharedManager] isFavorited:currentID];
}
注意:我们还需要更新我们的滚动视图画布大小。
这段代码有一点非常不好,使用了大量的本地变量来记载数据。
左滑实现
左滑实现想清楚较为简单,这里的实现方法用了比较简单的方法。由于左滑的页数上固定的,所以可以根据点进去的单元格section和row来预留出位置。即可实现左滑。
代码实现其实就是一句,创建画布时预设左边部分。
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width * (self.number_now+self.flag_scroll), self.view.bounds.size.height);
主页面与网页沟通
进行右滑时,到了边缘会进行新的网络请求,而主页面也需要根据滑过的内容,将申请的新数据加载到主页面的控件中。这需要两个页面进行沟通,采用通知传值的方法解决。
此处是通过点击返回按钮返回,示例如下:
- (void)backButtonClicked {NSDictionary *userInfo = @{@"array_date": self.array_date,@"cell_flag": @(self.cell_falg)};[[NSNotificationCenter defaultCenter] postNotificationName:@"WebViewBackNotification" object:nil userInfo:userInfo];self.navigationController.toolbarHidden = YES;[self.navigationController popViewControllerAnimated:YES];
}
然后在首页中接受通知更新即可:
- (void)handleWebViewBack:(NSNotification *)notification {NSDictionary *userInfo = notification.userInfo;self.array_data = userInfo[@"array_date"];self.falg = [userInfo[@"cell_flag"] intValue];[self.tableView_First reloadData];
}
评论区
效果如下:

评论区需要用Masonory和UITextView控件实现行高自适应,重点在于一开始的控件布局时,contentView要与TextView合理关联。
实现步骤如下:
先进行Masonory关联布局。
[self.imageViewHead mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self.contentView).offset(10);
// make.top.equalTo(self.labelHead.mas_bottom).offset(20);make.top.equalTo(self.contentView).offset(10);make.height.equalTo(@40);make.width.equalTo(@40);}];[self.labelName mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self.imageViewHead.mas_right).offset(15);make.top.equalTo(self.labelHead.mas_bottom).offset(15);make.width.equalTo(@200);make.height.equalTo(@20);}];[self.textView mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(self.imageViewHead.mas_bottom);make.left.equalTo(self.labelName);make.right.equalTo(self.contentView);}];[self.commentView mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(self.textView.mas_bottom);make.left.equalTo(self.textView.mas_left);make.right.equalTo(self.contentView.mas_right);//make.height.mas_equalTo(0);}];[self.labelTime mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(self.commentView.mas_bottom).offset(5);make.bottom.equalTo(self.contentView.mas_bottom).offset(-10);make.left.equalTo(self.textView).offset(5);make.width.equalTo(@120);}];[self.btnBack mas_makeConstraints:^(MASConstraintMaker *make) {make.height.equalTo(@20);make.bottom.equalTo(self.labelTime.mas_bottom);make.right.equalTo(self.contentView).offset(-20);make.width.equalTo(@20);}];[self.btnLike mas_makeConstraints:^(MASConstraintMaker *make) {make.height.equalTo(@25);make.bottom.equalTo(self.labelTime.mas_bottom).offset(+3);make.right.equalTo(self.btnBack.mas_right).offset(-50);make.width.equalTo(@25);}];
注意,我们这里不设置回复文本的高度,而是通过文字来撑开文本。
采用通过动态调整 UITextView 的内容和高度来实现回复文本的收缩展开,我们只需要先计算回复文本是否高度是否超过既定数值
如果内容超过两行且未展开,截取前两行内容,并在末尾添加“展开”链接。,然后为“展开”链接设置点击事件。
如果已展开,显示完整内容,并在末尾添加“收起”链接。为“收起”链接设置点击事件。
如果内容不超过两行,直接显示完整内容。
这里还使用了NSAttributedString类,该类可以为其中文字添加点击事件。
数据持久化
效果:

数据持久化需要依靠数据库实现,用数据库来保存并且传递信息。
这里以收藏为例。实际上都是一样的代码逻辑
- (void)favoriteButtonTapped:(UIButton *)sender {NSString *currentID = self.arrayID[self.load_real];NSString *currentURL = self.array_url[self.load_real];NSString *imageURL = self.arrayImage[self.load_real];NSLog(@"image:%@",imageURL);// 获取标题NSDictionary *currentStory;int storyIndex = self.load_real;for (NSDictionary *dateSection in self.array_date) {NSArray *stories = dateSection[@"stories"];if (storyIndex < stories.count) {currentStory = stories[storyIndex];break;}storyIndex -= stories.count;}NSString *title = currentStory[@"title"];if (!sender.selected) {// 添加收藏BOOL success = [[ZHDatabaseManager sharedManager] addFavoriteWithID:currentIDurl:currentURLimage:imageURLtitle:title];if (success) {sender.selected = YES;[self showToast:@"收藏成功"];}} else {// 取消收藏BOOL success = [[ZHDatabaseManager sharedManager] removeFavoriteWithID:currentID];if (success) {sender.selected = NO;[self showToast:@"已取消收藏"];}}
}
点击收藏,则将当前页面的信息保存进数据库,然后改变按钮状态。取消收藏则相反。
总结
这次项目学习了非常多的内容,往后要加紧实践。
