天气预报总结

📅 2026/8/2 5:18:54
天气预报总结
天气预报总结文章目录天气预报总结前言异步处理首页详情页面总结前言我们的APP不可能永远使用本地数据网络请求作为大多数APP的硬性要求需要在软件里实现API调用JSON数据解析在我们的天气预报里我们要实现城市搜索和数据的刷新所以这次仿写主要考察的就是UI的排版和网络请求设计模式主要为MVC异步处理iOS 应用的所有 UI 更新刷新 TableView、响应点击、滚动列表都发生在主线程上。而网络请求可能耗时数秒应用无法响应用户操作系统会判定应用“无响应”并弹出“卡死”提示用户体验极差异步处理的作用将耗时任务网络请求、文件读写、大量计算放到后台线程执行主线程继续处理 UI 事件。等任务完成后再将结果传递回主线程更新 UIdispatch_async(dispatch_get_main_queue(),^{self.cityArrayarray;self.collection.hiddenNO;self.tableView.hiddenYES;[self.collection reloadData];});首页首页需要展示的是已添加城市的天气卡片和搜索框并支持添加城市到首页对于卡片和搜索列表我们使用cell就行里面把我们需要的城市温度等等数据放进去想要一个城市的数据就得通过相关的信息查询城市名称不适合我们精准查找但适合模糊搜索所以我们的搜索功能就是先通过searchController里的文字查询使用专门的查询api得到经纬度最精准再请求详细数据-(void)requestSearch:(NSString*)city{NSString*cityEncode[city stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];NSString*urlString[NSString stringWithFormat:https://geocoding-api.open-meteo.com/v1/search?name%count10languagezhformatjson,cityEncode];NSURL*url[NSURL URLWithString:urlString];NSURLSessionDataTask*task[[NSURLSession sharedSession]dataTaskWithURL:url completionHandler:^(NSData*data,NSURLResponse*response,NSError*error){if(error){NSLog(%,error);return;}NSDictionary*dict[NSJSONSerialization JSONObjectWithData:data options:0error:nil];NSArray*resultsdict[results];NSMutableArray*array[NSMutableArray array];for(NSDictionary*iteminresults){cityModel*model[[cityModel alloc]init];model.citynameitem[name];model.latitudeitem[latitude];model.longitudeitem[longitude];model.countryitem[country];model.admin1item[admin1];[array addObject:model];}dispatch_async(dispatch_get_main_queue(),^{self.cityArrayarray;self.collection.hiddenNO;self.tableView.hiddenYES;[self.collection reloadData];});}];[task resume];}搜索要注意的就是cell是搜索框输入时自己会刷新展示出来而在我们决定添加某个城市时要自动清除输入框里的文字不然cell还是会根据框内文字刷新遮盖视图而对于我们的天气卡片就要使用搜索获得的经纬度查询-(void)requestWeather:(cityModel*)city{NSString*urlString[NSString stringWithFormat:https://api.open-meteo.com/v1/forecast?latitude%longitude%currenttemperature_2m,weather_codehourlytemperature_2m,weather_codedailyweather_code,temperature_2m_max,temperature_2m_mintimezoneauto,city.latitude,city.longitude];NSURL*url[NSURL URLWithString:urlString];NSURLSessionDataTask*task[[NSURLSession sharedSession]dataTaskWithURL:url completionHandler:^(NSData*data,NSURLResponse*response,NSError*error){if(error){NSLog(%,error);return;}NSDictionary*dict[NSJSONSerialization JSONObjectWithData:data options:0error:nil];WeatherModel*model[[WeatherModel alloc]init];NSDictionary*currentdict[current];model.citycity.cityname;model.tem[NSString stringWithFormat:%℃,dict[current][temperature_2m]];NSNumber*codecurrent[weather_code];NSInteger weatherCode[code integerValue];if(weatherCode0){model.weather晴;}elseif(weatherCode1||weatherCode2){model.weather多云;}elseif(weatherCode3){model.weather阴;}elseif((weatherCode45weatherCode48)){model.weather阴;}elseif((weatherCode51weatherCode67)||(weatherCode80weatherCode82)||(weatherCode95weatherCode99)){model.weather雨;}elseif((weatherCode71weatherCode77)||weatherCode85||weatherCode86){model.weather雪;}else{model.weather阴;}model.time[NSString stringWithFormat:%,current[time]];dispatch_async(dispatch_get_main_queue(),^{BOOL existNO;for(WeatherModel*minself.weatherArray){if([m.latitude isEqualToNumber:model.latitude][m.longitude isEqualToNumber:model.longitude]){existYES;break;}}if(!exist){[self.weatherArray addObject:model];}self.cityArray[];self.searchController.searchBar.text;self.searchController.activeNO;[self.tableView reloadData];});}];[task resume];}在我们得到weather_code要挂定天气范围方便展示同时也可以根据文字决定背景图在主线程回调时还要进行是否有重复城市添加的检查再清空输入框里的文字在首页的cell排版时我们看我能会遇到中间没有间隔的问题给header一个空白View就行了天气卡片还要实现左滑删除的功能-(UISwipeActionsConfiguration*)tableView:(UITableView*)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath*)indexPath{UIContextualAction*deleteAction[UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:删除handler:^(UIContextualAction*action,UIView*sourceView,void(^completionHandler)(BOOL)){[self.weatherArray removeObjectAtIndex:indexPath.section];NSIndexSet*sections[NSIndexSet indexSetWithIndex:indexPath.section];[tableView deleteSections:sections withRowAnimation:UITableViewRowAnimationAutomatic];completionHandler(YES);}];UISwipeActionsConfiguration*configuration[UISwipeActionsConfiguration configurationWithActions:[deleteAction]];configuration.performsFirstActionWithFullSwipeNO;returnconfiguration;}这样左滑会出现一个按钮继续左滑可以直接删除详情页面我们点进一个卡片或者搜索就会展示详情页面我们在首页使用present搜索页面使用push首页使用present是因为我们需要在几个城市的详情页面里实现滑动切换的功能另外使用pageController实现底部小白点的展示UIPageViewController*pageVC[[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];pageVC.dataSourceself;pageVC.delegateself;[selfaddChildViewController:pageVC];[self.view addSubview:pageVC.view];pageVC.view.frameself.view.bounds;[pageVC didMoveToParentViewController:self];self.pageControllerpageVC;self.pageControl[[UIPageControl alloc]init];self.pageControl.numberOfPagesself.pages.count;self.pageControl.currentPageself.currentIndex;self.pageControl.pageIndicatorTintColor[[UIColor whiteColor]colorWithAlphaComponent:0.4];self.pageControl.currentPageIndicatorTintColorUIColor.whiteColor;[self.view addSubview:self.pageControl];[self.pageControl mas_makeConstraints:^(MASConstraintMaker*make){make.centerX.equalTo(self.view);make.bottom.equalTo(self.view.mas_bottom).offset(-15);}];在点击任意卡片的时候一定要保证索引被正确传递否则只能进去pages里的第一个界面不符合要求-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{pageViewController*vc[[pageViewController alloc]init];vc.weatherArrayself.weatherArray;vc.currentIndexindexPath.section;[selfpresentViewController:vc animated:YES completion:nil];}那么我们从首页给pageView传值后还不够还要pageView给我们的详情页传经纬度方便详情页刷新各种数据这里要注意的是我们不需要从首页传所有的数据其他数据可以在页面进去后再请求这样可以减少不跟手的情况增加流畅度for(WeatherModel*modelinself.weatherArray){cardViewController*vc[[cardViewController alloc]init];vc.citymodel.city;vc.latitudemodel.latitude;vc.longitudemodel.longitude;[self.pages addObject:vc];}那么现在我们就到了详情页的内部这里我们要实现苹果天气预报一样的视觉效果就要使用iOS的毛玻璃-(UIVisualEffectView*)createBlurView{UIBlurEffect*effect[UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialLight];UIVisualEffectView*blurView[[UIVisualEffectView alloc]initWithEffect:effect];blurView.alpha0.9;returnblurView;}UIBlurEffect模糊效果的核心它是一个视觉效果描述对象定义了模糊的样式如SystemUltraThinMaterialLight但不直接渲染。系统会基于其背后内容的颜色、亮度等信息实时计算并生成动态模糊效果UIVisualEffectView承载模糊效果的容器它有两个关键的层UIVisualEffectView自身负责渲染模糊背景。contentView是它的子视图容器所有需要显示在毛玻璃之上的 UI 控件文字、图片等都应添加到contentView中以确保它们显示在模糊层上方。当blurView覆盖在其他视图上时背后的内容会被实时模糊处理。UIBlurEffectStyleSystemUltraThinMaterialLight这是 iOS 13 引入的材质化样式会根据系统深色/浅色模式自动适配浅色模式呈现浅色半透明毛玻璃深色模式呈现深色半透明毛玻璃样式效果UIBlurEffectStyleExtraLight极浅色模糊UIBlurEffectStyleLight浅色模糊UIBlurEffectStyleDark深色模糊UIBlurEffectStyleRegular常规材质适配深/浅色模式UIBlurEffectStyleProminent突出材质将内容添加到contentView上就可以实现毛玻璃效果了我们的这些数据部分里几个需要单独拿出来讲渐变视图self.gradientLayer[CAGradientLayer layer];self.gradientLayer.colors[(__bridge id)[UIColor systemGreenColor].CGColor,(__bridge id)[UIColor systemOrangeColor].CGColor];self.gradientLayer.startPointCGPointMake(0,0.5);self.gradientLayer.endPointCGPointMake(1,0.5);self.gradientLayer.cornerRadius1;[self.temLine.layer addSublayer:self.gradientLayer];CAGradientLayer是 专门用于绘制颜色渐变的图层类。它是CALayer的子类colors渐变的颜色数组定义渐变中使用的颜色序列至少需要两个颜色locations颜色分布的位置第一个颜色必须是0.0最后一个必须为1.0startPoint和endPoint渐变的方向与范围水平渐变(0, 0.5) → (1, 0.5)垂直渐变(0.5, 0)→ (0.5, 1)对角线渐变(0, 0)→ (1, 1)在比较最近一周高低温的时候会用到再通过数学计算实现渐变条的长短变化就可以有和苹果差不多的效果旋转视图self.img2.transformCGAffineTransformMakeRotation(direction*M_PI/180.0);CGAffineTransform是 Core Graphics 框架中用于仿射变换的结构体所有变换都相对于视图的center或anchorPoint进行这里我也只会用它来实现旋转其他的并未了解括号里的就是我们旋转的弧度这样可以实现风向的可视化通过箭头的转向实现曲线使用UIBezierPath绘制路径UIBezierPath*path[UIBezierPath bezierPath];[path moveToPoint:CGPointMake(10,110)];[path addQuadCurveToPoint:CGPointMake(160,110)controlPoint:CGPointMake(90,45)];以(160, 110)为终点(90, 45)为控制点将路径渲染到屏幕self.sunLayer[CAShapeLayer layer];self.sunLayer.pathpath.CGPath;self.sunLayer.strokeColor[UIColor whiteColor].CGColor;self.sunLayer.fillColorUIColor.clearColor.CGColor;self.sunLayer.lineWidth2;[self.contentView.layer addSublayer:self.sunLayer];CAShapeLayer负责渲染路径由 GPU 加速strokeColor设置线条颜色为白色fillColor clear表示不填充内部只描边这样一条曲线就出现在屏幕上了后面创建一个小白点结合日升日落时间在这条线上移动就可以实现太阳的可视化了那么我们在写详情界面的时候尽量使用一个collectionView并复用优化性能另外在使用pages翻页的时候我们可以提前把本页的上一页与下一页给加载出来切换时视觉上更流畅总结本次天气预报涉及了网络请求MVC架构和更多UI的学习让我们开始能体会到用户体验的重要性