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
英文地址,請注明出處:http://en.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);功能 將…