iOS —— 天气预报仿写总结

在iOS中,最常见的网络请求方式是NSURLSession,它是苹果推荐的现代API,简单安全且易于拓展。


一次完整的网络请求流程:

  1. 构造 NSURL 对象

  2. 创建 NSURLSessionDataTask

  3. 发起请求(resume)

  4. 在回调中解析数据

  5. 回到主线程更新 UI

下面,我们用一个简单的程序来试一下。

NSString *city = @"Beijing";
NSString *apiKey = @"你的API密钥";
NSString *urlString = [NSString stringWithFormat:@"https://api.weatherapi.com/v1/current.json?key=%@&q=%@&lang=zh", apiKey, city];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {if (error) {NSLog(@"请求失败:%@", error.localizedDescription);return;}NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];NSLog(@"天气数据:%@", json);dispatch_async(dispatch_get_main_queue(), ^{// 更新 UI});
}];
[task resume];

首页

    [[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(handleAddCityNotification:)name:@"AddNewCityNotification"object:nil];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(handleDeleteCityNotification:)name:@"DeleteCityNotification"object:nil];

接收两个通知,一个是搜索页面的城市,另一个是详情页面的删除指令。


 

-(void) handleAddCityNotification:(NSNotification *)notification {NSDictionary *userInfo = [notification userInfo];NSString *newCity = userInfo[@"cityName"];BOOL exists = NO;for (NSDictionary *city in self.cityData) {if ([city[@"name"] isEqualToString:newCity]) {exists = YES;break;}}if (!exists) {[self.cityData addObject:@{@"name": newCity}];[self createUrl];}
}- (void)handleDeleteCityNotification:(NSNotification *)notification {NSDictionary *userInfo = [notification userInfo];NSString *cityName = userInfo[@"cityName"];// 从城市列表中删除该城市NSMutableArray *citiesToRemove = [NSMutableArray array];for (NSDictionary *city in self.cityData) {if ([city[@"name"] isEqualToString:cityName]) {[citiesToRemove addObject:city];}}[self.cityData removeObjectsInArray:citiesToRemove];[self.tableView reloadData];
}

这个页面要执行两个操作: 点击加号添加城市,删除城市。

这里给出我的网络请求代码: 

- (void)createUrl {if (self.cityData.count == 0) return;dispatch_group_t group = dispatch_group_create();NSURLSession *session = [NSURLSession sharedSession];NSMutableArray *tempDicArray = [NSMutableArray array];NSLog(@"1");for (NSDictionary *city in self.cityData) {dispatch_group_enter(group);NSString *cityName = city[@"name"];NSString *urlString = [NSString stringWithFormat:@"https://api.weatherapi.com/v1/current.json?key=2db0265c1d084416b9275428252207&q=%@&lang=zh   ", cityName];urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];NSURL *url = [NSURL URLWithString:urlString];NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {if (error) {NSLog(@"错错错!! %@: %@", city, error.localizedDescription);dispatch_group_leave(group);return;}if (data) {NSError *jsonError;//解析数据NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];if (jsonError) {NSLog(@"JSON解析失败: %@", jsonError.localizedDescription);} else {NSLog(@"API响应: %@", dic);// 检查是否有错误if (dic[@"error"]) {NSLog(@"API返回错误: %@", dic[@"error"][@"message"]);} else {@synchronized (tempDicArray) {//赋值到tempDicArray[tempDicArray addObject:dic];NSLog(@"成功添加城市: %@", cityName);}}}} else {NSLog(@"无响应数据");}dispatch_group_leave(group);}];[task resume];
}dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"所有请求完成,获取到%ld个城市数据", tempDicArray.count);//最终到dicArrayself.dicArray = tempDicArray;[self.tableView reloadData];});
}
// 添加支持滑动删除
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {return YES;
}

搜索页面

-(void) addCityToMain:(NSDictionary *)cityInfo {NSString *cityName = cityInfo[@"name"];NSDictionary *userInfo = @{@"cityName": cityName};[[NSNotificationCenter defaultCenter] postNotificationName:@"AddNewCityNotification" object:nil userInfo:userInfo];// 关闭添加页面
//    [self dismissViewControllerAnimated:YES completion:nil];
}
//选中哪个就传给主页
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {[tableView deselectRowAtIndexPath:indexPath animated:YES];if (indexPath.row < self.searchResults.count) {NSDictionary *city = self.searchResults[indexPath.row];DetailViewController *detailVC = [[DetailViewController alloc] init];detailVC.cityName = city[@"name"];detailVC.canAddCity = YES;UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:detailVC];nav.modalPresentationStyle = UIModalPresentationFullScreen;[self presentViewController:nav animated:YES completion:nil];}
}

当我们点击其中一行时,我们会跳转到该城市

城市详细天气

给出我的每小时天气预报代码

- (void)setupHourlyForecastData {NSArray *forecastDays = self.weatherData[@"forecast"][@"forecastday"];if (forecastDays.count == 0) return;//WeatherAPI 返回的 forecast 结构中,每天包含一个 hour 数组(24 小时)。为了支持“跨天展示”,你先拼接所有天的小时数组// 拼接所有 forecastday 的 hour 数据NSMutableArray *allHours = [NSMutableArray array];for (NSDictionary *day in forecastDays) {NSArray *hours = day[@"hour"];if (hours) [allHours addObjectsFromArray:hours];}if (allHours.count == 0) return;UIView *hourlyCard = [self.contentView viewWithTag:1000];UIScrollView *scrollView = [hourlyCard viewWithTag:1001];if (!scrollView) return;CGFloat itemWidth = 60;CGFloat spacing = 10;//当前小时NSDate *now = [NSDate date];NSDateFormatter *formatter = [[NSDateFormatter alloc] init];formatter.dateFormat = @"yyyy-MM-dd HH";NSString *nowHourString = [formatter stringFromDate:now];NSInteger startIndex = 0;//找出当前时间匹配的小时for (int i = 0; i < allHours.count; i++) {NSString *hourString = [allHours[i][@"time"] substringToIndex:13];if ([hourString isEqualToString:nowHourString]) {startIndex = i;break;}}//主要是防止数组越界NSInteger count = MIN(24, allHours.count - startIndex);//给7个格子赋值for (int i = 0; i < count; i++) {NSDictionary *hour = allHours[startIndex + i];NSString *time = [hour[@"time"] substringFromIndex:11];NSString *temp = [NSString stringWithFormat:@"%.0f°", [hour[@"temp_c"] floatValue]];NSString *iconUrl = [NSString stringWithFormat:@"https:%@", hour[@"condition"][@"icon"]];UIView *hourView = [[UIView alloc] initWithFrame:CGRectMake(i * (itemWidth + spacing), 0, itemWidth, 70)];UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, itemWidth, 20)];timeLabel.text = time;timeLabel.font = [UIFont systemFontOfSize:12];timeLabel.textColor = [UIColor whiteColor];timeLabel.textAlignment = NSTextAlignmentCenter;[hourView addSubview:timeLabel];UIImageView *icon = [[UIImageView alloc] initWithFrame:CGRectMake(10, 20, 40, 30)];icon.contentMode = UIViewContentModeScaleAspectFit;[hourView addSubview:icon];[self loadIcon:iconUrl forImageView:icon];UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 50, itemWidth, 20)];tempLabel.text = temp;tempLabel.font = [UIFont systemFontOfSize:12];tempLabel.textColor = [UIColor whiteColor];tempLabel.textAlignment = NSTextAlignmentCenter;[hourView addSubview:tempLabel];[scrollView addSubview:hourView];}scrollView.contentSize = CGSizeMake((itemWidth + spacing) * count, 70);
}//每小时天气

同时,我下面的每部分都以卡片形式呈现

//卡片
- (UIView *)createCardViewWithFrame:(CGRect)frame {UIView *card = [[UIView alloc] initWithFrame:frame];card.backgroundColor = [UIColor colorWithWhite:1 alpha:0.2];card.layer.cornerRadius = 20;card.layer.borderWidth = 1;card.layer.borderColor = [UIColor colorWithWhite:1 alpha:0.3].CGColor;return card;
}
//卡片标题
- (UILabel *)createSectionTitleLabel:(NSString *)title frame:(CGRect)frame {UILabel *label = [[UILabel alloc] initWithFrame:frame];label.text = title;label.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold];label.textColor = [UIColor whiteColor];return label;
}

我的背景可以根据天气不同自动切换,代码如下:


- (void)setBackgroundBasedOnWeather {if (!self.weatherData) return;NSDictionary *current = self.weatherData[@"current"];NSDictionary *condition = current[@"condition"];NSInteger code = [condition[@"code"] integerValue];NSString *bgImageName;// 晴天if (code == 1000) {bgImageName = @"pic1.jpg";}// 多云或阴天else if (code >= 1003 && code <= 1009) {bgImageName = @"pic2.jpg";}// 雨(小雨~大雨)else if ((code >= 1063 && code <= 1087) || (code >= 1150 && code <= 1195) || (code >= 1240 && code <= 1246)) {bgImageName = @"pic3.jpg";}// 雪(小雪~暴雪)else if ((code >= 1066 && code <= 1074) || (code >= 1114 && code <= 1237)) {bgImageName = @"pic4.jpg";}// 雷暴、雨夹雪等恶劣天气else if (code >= 1273 && code <= 1282) {bgImageName = @"pic5.jpg";}// 默认else {bgImageName = @"pic1.jpg";}self.backgroundImageView.image = [UIImage imageNamed:bgImageName];
}
//日期字符串转换为周几
- (NSString *)formatDate:(NSString *)dateString {NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init];inputFormatter.dateFormat = @"yyyy-MM-dd";NSDate *date = [inputFormatter dateFromString:dateString];NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];outputFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"zh_CN"];outputFormatter.dateFormat = @"EEE";return [outputFormatter stringFromDate:date];
}

详情页的删除按钮:

- (void)deleteCity {if (self.cityName) {NSDictionary *userInfo = @{@"cityName": self.cityName};[[NSNotificationCenter defaultCenter] postNotificationName:@"DeleteCityNotification"object:niluserInfo:userInfo];[self dismissViewControllerAnimated:YES completion:nil];}
}

关于多个城市横向滑动查看详情,我使用了UIPageController。

- (id)init {self = [super initWithTransitionStyle:UIPageViewControllerTransitionStyleScrollnavigationOrientation:UIPageViewControllerNavigationOrientationHorizontaloptions:nil];return self;
}

这段代码实现了页面的横向滑动效果。

// MainVC.m- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {[tableView deselectRowAtIndexPath:indexPath animated:YES];NSMutableArray *cityNames = [NSMutableArray array];for (NSDictionary *city in self.cityData) {[cityNames addObject:city[@"name"]];}CitiesDetailViewController *containerVC = [[CitiesDetailViewController alloc] init];containerVC.cityNames = cityNames;containerVC.initialIndex = indexPath.section;containerVC.modalPresentationStyle = UIModalPresentationFullScreen;[self presentViewController:containerVC animated:YES completion:nil];
}
// CitiesDetailViewController.m- (void)viewDidLoad {[super viewDidLoad];self.delegate = self;self.dataSource = self;self.cities = self.cityNames;           // 城市名列表由外部传入self.currentIndex = self.initialIndex;  // 初始城市索引也由外部设置DetailViewController *initialVC = [self detailViewControllerForIndex:self.initialIndex];[self setViewControllers:@[initialVC] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
#pragma mark - Page View Controller Data Source
// 获取前一个VC
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {//下标NSInteger index = [(DetailViewController *)viewController index];//不是第一个就去上一个if (index > 0) {return [self detailViewControllerForIndex:index - 1];}return nil;
}//获取后一个VC
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {NSInteger index = [(DetailViewController *)viewController index];if (index < self.cities.count - 1) {return [self detailViewControllerForIndex:index + 1];}return nil;
}//进入指定页面
- (DetailViewController *)detailViewControllerForIndex:(NSInteger)index {if (index < 0 || index >= self.cities.count) return nil;DetailViewController *detailVC = [[DetailViewController alloc] init];detailVC.cityName = self.cities[index];detailVC.index = index;detailVC.canAddCity = NO;return detailVC;
}- (void)deleteCurrentCity {if (self.cities.count == 0) {[self dismissViewControllerAnimated:YES completion:nil];return;}// 获取当前显示的视图控制器DetailViewController *currentVC = (DetailViewController *)self.viewControllers.firstObject;NSInteger currentIndex = currentVC.index;// 防止索引越界if (currentIndex >= self.cities.count) {[self dismissViewControllerAnimated:YES completion:nil];return;}NSString *cityName = self.cities[currentIndex];// 发送删除通知NSDictionary *userInfo = @{@"cityName": cityName};[[NSNotificationCenter defaultCenter] postNotificationName:@"DeleteCityNotification"object:niluserInfo:userInfo];// 从数据源中删除NSMutableArray *mutableCities = [self.cities mutableCopy];[mutableCities removeObjectAtIndex:currentIndex];self.cities = [mutableCities copy];// 如果删除后没有城市了,关闭页面if (self.cities.count == 0) {[self dismissViewControllerAnimated:YES completion:nil];return;}// 计算新的当前索引NSInteger newIndex;if (currentIndex >= self.cities.count) {newIndex = self.cities.count - 1;} else {newIndex = currentIndex;}// 确定导航方向:如果删除的是最后一个,则向前翻一页,否则保持当前页UIPageViewControllerNavigationDirection direction = UIPageViewControllerNavigationDirectionForward;if (newIndex < currentIndex) {direction = UIPageViewControllerNavigationDirectionReverse;}// 获取新的视图控制器DetailViewController *newVC = [self detailViewControllerForIndex:newIndex];if (newVC) {[self setViewControllers:@[newVC]direction:directionanimated:YEScompletion:nil];self.currentIndex = newIndex;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93165.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 8.4 Windows 版安装记录与步骤参考

导语&#xff1a; MySQL 作为广泛使用的开源数据库管理系统&#xff0c;是许多开发者和学习者的必备工具。最近有朋友询问安装过程&#xff0c;正好整理了 MySQL 8.4 在 Windows 系统下的安装步骤和一些注意事项&#xff0c;分享给有需要的朋友做个参考。关于 MySQL&#xff1a…

七、搭建springCloudAlibaba2021.1版本分布式微服务-skywalking9.0链路追踪

前言链路追踪介绍 对于一个大型的几十个&#xff0c;几百个微服务构成的微服务架构系统&#xff0c;通常会遇到下面的一系列问题。 如何串联整个调用链路&#xff0c;快速定位问题&#xff1f;如何澄清各个微服务之间的依赖关系&#xff1f;如何进行各个微服务接口的性能分析&a…

深入理解大语言模型生成参数:temperature、top\_k、top\_p 等全解析

在使用大语言模型&#xff08;如 GPT-4、LLaMA、ChatGLM 等&#xff09;进行文本生成任务时&#xff0c;很多开发者会面对各种“生成参数”&#xff0c;如 temperature、top_k、top_p、repetition_penalty 等。这些参数虽然看起来抽象&#xff0c;但掌握它们的意义和配置技巧&a…

vulhub Web Machine(N7)靶场攻略

下载地址&#xff1a; https://download.vulnhub.com/webmachine/Web-Machine-N7.ova 使用方法&#xff1a; 靶场下载好以后不用解压&#xff0c;需要使用Oracle VirtualBox虚拟机打开&#xff0c;用VMware会报错。安装Oracle VirtualBox虚拟机时安装地址不能随便选择&#…

【机器学习深度学习】模型微调:多久才算微调完成?——如何判断微调收敛,何时终止训练

目录 前言 一、微调过程的目标&#xff1a;优化模型表现 二、微调需要多久&#xff1f; 微调时间无法确定 三、如何判断微调何时收敛&#xff1f; 3.1 观察Loss的下降趋势 3.2 损失值趋于平稳&#xff0c;意味着收敛 如何识别收敛&#xff1f; 3.3 验证Loss的波动&…

红队视角:实战渗透测试中漏洞利用的进阶技巧与防御

红队作为渗透测试的 “攻击方”&#xff0c;其核心价值不仅在于发现漏洞&#xff0c;更在于挖掘漏洞的深度利用方式 —— 通过绕过防护措施、组合低危漏洞形成攻击链&#xff0c;暴露企业真实安全风险。从红队视角解析漏洞利用的进阶技巧&#xff0c;既能帮助防御方理解攻击思路…

OpenHarmony BUILD.gn中执行脚本

在OpenHarmony编译构建中笔者经常遇到这样的场景——需要执行sh脚本完成某些操作。笔者将OpenHarmony BUILD.gn中执行脚本的方法分享如下&#xff1a; 前置知识点 1.能够把自定义的子系统加入OpenHarmony源码的编译构建&#xff0c;请参考&#xff1a;https://ost.51cto.com/…

QUIC协议如何在UDP基础上解决网络切换问题

一、UDP 四元组的本质局限UDP 本身无连接状态&#xff0c;其数据包仅通过四元组寻址。但 QUIC 在 UDP 之上构建了完整的连接语义。二、QUIC 的连接迁移核心机制1. 连接标识符&#xff08;Connection ID&#xff09;关键设计&#xff1a;每个 QUIC 连接拥有全局唯一 64-bit Conn…

力扣131:分割回文串

力扣131:分割回文串题目思路代码题目 给你一个字符串 s&#xff0c;请你将 s 分割成一些 子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 思路 从题目中我们可以总结出这道题的三个需要解决的问题&#xff1a; 如何判断回文串如何找到一种方案里的所…

代驾小程序系统开发:引领出行行业数字化转型

随着数字技术的飞速发展&#xff0c;出行行业正经历着深刻的数字化转型。代驾小程序系统作为这一转型的重要推手&#xff0c;以其高效、便捷、智能的特点&#xff0c;引领着出行行业向数字化、网络化、智能化方向发展。一、数字化管理&#xff0c;提升运营效率代驾小程序系统通…

数独求解器与生成器(回溯算法实现)

摘要本毕业设计旨在利用MATLAB技术实现一个基于回溯算法的数独求解器与生成器。通过深入分析数独游戏的规则和回溯算法的原理&#xff0c;设计并实现了数独求解的核心算法&#xff0c;同时开发了数独生成功能&#xff0c;能够生成符合规则的有效数独谜题。系统采用MATLAB图形用…

[数据结构]#7 哈希表

哈希表&#xff08;Hash Table&#xff09;&#xff0c;有时也称为散列表&#xff0c;是一种数据结构&#xff0c;它提供了一种快速存取数据的方法。哈希表利用一个被称为哈希函数的机制将键映射到表中的一个位置来直接访问记录&#xff0c;以此加快查找的速度。哈希表通常支持…

C++ 23种设计模式-工厂模式

工厂模式是一种创建型的设计模式&#xff0c;他提供了一种创建对象的最佳方式&#xff0c;而无需指定将要创建对象的具体类。包括&#xff1a;简单工厂模式、工厂方法模式、抽象工厂模式。简单工厂模式组成成员&#xff1a;抽象产品类、具体产品类 A、B、C等、工厂类工作原理&a…

vue3 el-table 行的某个特定值来决定某些列是否显示

在 Vue 3 中使用 Element Plus 的 <el-table> 组件时&#xff0c;如果你想要根据行的某个特定值来决定某些列是否显示&#xff0c;你可以通过自定义列渲染函数&#xff08;render 函数&#xff09;来实现这一需求。下面是一个如何实现该功能的步骤说明和示例代码。步骤 1…

电商数据采集API与爬虫技术结合的全网比价方案

一、技术选型与工具准备API优先策略官方API接入&#xff1a;京东、淘宝、拼多多等平台提供商品详情API&#xff0c;需注册开发者账号获取API Key。例如&#xff1a;京东API支持实时获取商品价格、库存、评价数据。淘宝API通过RESTful接口返回JSON格式的商品信息&#xff0c;需O…

Socket详解

一.定义Socket&#xff08;套接字&#xff09;是网络编程的核心&#xff0c;它允许不同主机或同一主机的不同进程之间进行通信&#xff0c;Socket API 提供了一套标准的接口&#xff0c;支持 TCP、UDP、IP 等协议分为以下三个类型&#xff1a;SOCK_STREAM: 用于tcp协议&#xf…

如何实现打印功能

一、AI赋能提供思路基本框架<!-- 隐藏的打印内容&#xff08;默认不显示&#xff09; --> <div id"print-container" style"display: none;"><h1>退货单打印内容</h1><table><!-- 打印专用的表格结构 --></table&g…

Android 架构演进:从 MVC 到 MVVM 的设计之道

在 Android 开发初期&#xff0c;很多开发者会把所有逻辑塞进 Activity—— 网络请求、数据处理、UI 更新全堆在一起&#xff0c;导致代码超过数千行&#xff0c;改一个按钮点击都要翻半天。这种 “面条式代码” 的根源是缺乏架构设计。随着应用复杂度提升&#xff0c;MVC、MVP…

使用 gh-pages 将 next.js15 静态项目部署到 github pages

以下我使用 next.js15 写的 Todo List 为例,假设我们本地已经存在一个 next.js15 的 Todo List 项目。 说明:解决了项目部署到 github pages 后访问不到 css、js、字体以及访问不到 public 目录下的图片问题。 第一步 安装 gh-pages: npm i gh-pages第二步 在 public 目…

rename系统调用及示例

21. rename - 重命名文件或目录 函数介绍 rename系统调用用于重命名文件或目录&#xff0c;也可以将文件或目录移动到另一个位置。如果目标文件已存在&#xff0c;则会被替换。 函数原型 #include <stdio.h>int rename(const char *oldpath, const char *newpath);功能 将…