目录
前言
首页
搜索页
详情页
浏览页
前言
这周学习了iOS中简单的网络请求,并完成了天气预报的仿写,这篇博客来做一下总结。天气预报主要要实现四个界面,接下来分四个界面分别讲解一下。
首页
首页的布局就是上方两个很简单的控件添加按钮和“天气”logo,下面一个动态变化的数据视图tableview。
这个数据视图的每一个单元格都需要一个城市实时的天气信息。
我的思路是数据视图的行数由一个数组的数量来确定,这个数组存放的是城市的名字,并且对于每一个城市的名字,都要申请网络请求来获得城市ID,进一步获得当前天气状态和七天的天气状态,从而获得数据视图的数据。
在进行更新数据源这一步时,我遇到的一个问题就是网络请求的异步,由于网络请求的异步机制,在有多个网络请求时,程序不是按顺序执行,申请完一个请求再进行下一个,而是多个网络请求同时在后台进行,因此在网络请求的回调里获取完某一项数据更新数据源时,就总会出现数据源为空或者数组越界的情况。
我的解决思路是:分为两种情况,第一种是需要网络请求有序完成,我采取网络请求嵌套的方法,例如获取城市天气需要先获得城市ID,那么我就在获取城市ID的回调中来获取所有与天气有关的数据;第二种是网络请求可以无序完成,那我就在每一个网络请求的回调里都做一次判断,当我所需要的数据都不为空并且数组元素数量达到要求时,进行数据视图的reloadData。
这里给出获取城市ID的API和获取实时天气的API以做示范
- (void)createURLForCityID {
[self.cityIDMutableArray removeAllObjects];
for (NSString* city in self.cityMutableArray) {
//处理字符
NSString* urlString = [NSString stringWithFormat:@"https://geoapi.qweather.com/v2/city/lookup?location=%@&key=34e1f7a3ef5544d393fcafaea08f0f1b&range=cn", city];
NSLog(@"%@", urlString);
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
//创建URL
NSURL* url = [NSURL URLWithString:urlString];
//创建请求类
NSURLRequest* request = [NSURLRequest requestWithURL:url];
//创建会话
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//根据会话创建任务
NSLog(@"12%@", urlString);
NSURLSessionTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"无法获取城市ID");
} else {
NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error) {
NSLog(@"无法获取城市ID");
} else {
NSArray* array = dictionary[@"location"];
NSDictionary* nowcity = array[0];
NSLog(@"%@", nowcity[@"id"]);
[self.cityIDMutableArray addObject:nowcity[@"id"]];
NSLog(@"citycount:%d %d",self.cityMutableArray.count, self.cityIDMutableArray.count);
if (self.cityMutableArray.count == self.cityIDMutableArray.count) {
[self createURLForNow];
[self createURLForDay];
}
}
}
}];
[dataTask resume];
}
}
- (void)createURLForNow {
[self.tempMutableArray removeAllObjects];
[self.stateMutableArray removeAllObjects];
for (NSString* cityID in self.cityIDMutableArray) {
//处理字符
NSString* urlString = [NSString stringWithFormat:@"https://devapi.qweather.com/v7/weather/now?location=%@&key=34e1f7a3ef5544d393fcafaea08f0f1b", cityID];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
//创建URL
NSLog(@"%@", urlString);
NSURL* url = [NSURL URLWithString:urlString];
//创建请求类
NSURLRequest* request = [NSURLRequest requestWithURL:url];
//创建会话
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//根据会话创建任务
NSURLSessionTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"无法获取天气信息");
} else {
NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error) {
NSLog(@"无法获取天气信息");
} else {
NSDictionary* nowWeather = dictionary[@"now"];
[self.tempMutableArray addObject:[nowWeather[@"temp"] stringByAppendingString:@"°C"]];
if (self.lowAndHighMutableArray.count == self.cityMutableArray.count && self.stateMutableArray.count == self.cityMutableArray.count && self.tempMutableArray.count == self.cityMutableArray.count) {
[self.tableView reloadData];
}
}
}
}];
[dataTask resume];
}
}
搜索页
搜索页面的布局其实也很简单,一个searchBar和一个同样动态变化的数据视图tableview。
这个界面要实现一个模糊搜索的功能,这里其实获取城市ID的API是有模糊搜索的功能的,所以只需要在调用API申请网络数据后,将得到的城市数据放入数组里,再重新加载数据视图就可以了
详情页
这个界面的布局相对比较复杂,但其实也还好,就是在数据视图上实现五个cell,关于布局的部分只是繁琐难度并不大就不多说了。这个界面需要接收上一个界面点击某个单元格时那个城市的ID(注意一定要是ID,笔者一开始传的是城市的名字,出现的问题就是当出现同名城市时,往往只能获取固定某一个城市的天气),在获取完ID后,再在详情页申请网络请求获取天气信息。笔者这里判定网络请求完成的条件非常复杂,这里给出我的判定条件
if (self.lowAndHigh != NULL && self.state != NULL && ![self.temp isEqualToString:@""] && !self.tableView && self.tempForHoursMutableArray.count == 24 && self.hoursMutableArray.count == 24 && self.daysMutableArray.count == 7 && self.tempForDaysMutableArray.count == 7 && self.sunrise != NULL && self.sunset != NULL && self.see != NULL && self.rain != NULL && self.iconForHoursMutableArray.count == 24 && self.iconForDaysMutableArray.count == 7) {
[self createBackgroundView:self.state];
[self createATableview];
//NSLog (@"tianshu:%ld",self.tempForDaysMutableArray.count);
//NSLog(@"tempOK");
}
这个界面还要实现一个将城市添加到主页的功能,那么就获取当前城市ID,使用通知传值传到首页,遍历首页ID数组查重,如未重复,则在城市和城市ID数组中添加当前城市名和ID即可。(注意:查重时一定要使用城市ID,因为可以存在不同身份同名的城市,例如:黑龙江省的西安和陕西西安应该是可以同时出现在首页的)
浏览页
浏览页的本质就是一个滚动视图,只是这个滚动视图上的图片不是通过image类型获取的,而是直接拿的视图控制器的view属性,多次创建不同的详情页视图控制器,再将详情页的view放入滚动视图就可以实现。需要注意的是,这里需要将详情页视图控制器作为浏览页的子视图控制器,再将他的视图view放入滚动视图,否则就会出现一系列错位问题。
除此之外,添加子视图控制器和添加子视图控制器视图的顺序也会导致代码运行的差别,如果先添加视图,那么在添加视图时会调用一次子视图控制器的viewWillApear,当父视图控制器调用viewWillApear时,又会调用一次子视图控制器的viewWillApear。颠倒顺序的话,则子视图控制器只调用一次viewWillApear。
self.scrollview = [[UIScrollView alloc] init];
self.scrollview.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
self.scrollview.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width * self.cityMutableArray.count, 800);
self.scrollview.delegate = self;
self.scrollview.pagingEnabled = YES;
NSLog(@"%ld", self.cityMutableArray.count);
for (int i = 0; i < self.cityMutableArray.count; i++) {
CGFloat xOffset = i * self.view.bounds.size.width;
NSLog(@"%lf", xOffset);
DetailViewController* detailViewController = [[DetailViewController alloc] init];
//detailViewController.exitButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
detailViewController.cityName = self.cityMutableArray[i];
detailViewController.cityID = self.cityIDMutableArray[i];
detailViewController.view.frame = CGRectMake(xOffset, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:detailViewController];
[self.scrollview addSubview:detailViewController.view];
}
self.scrollview.contentOffset = CGPointMake(_nowPage * [UIScreen mainScreen].bounds.size.width, 0);