9月中旬,開始動手做我的畢業設計了,之前一直在糾結做啥,后來想想,既然是做畢業設計,那就大膽地做點自己沒接觸過的東西吧。然后網上查找資料得知做天氣預報需要用到開放的API,而且要用那種現在還在維護的,而且又免費的(對於我們學生黨來說,這個是挺好的)。天氣預報App的核心是從天氣API請求數據,請求得到的一般是JSON數據(這個JSON數據之前都沒接觸過),然后把JSON數據解析,然后在視圖上顯示出來。總得來說,這里應該涉及到“網絡請求”,“JSON解析”這兩個大塊吧,正好用來學習新的東西,並且練練手,於是就決定做個天氣預報的App了。
- 第一步,找天氣API
各種百度,然后找到了比較新的網站,分別是“心知天氣”和“和風天氣”。都需要注冊,注冊之后會得到一個key,用來進行數據請求的。兩個網站都有付費和免費的兩種選擇,如圖,比較完之后,二話不說,選擇了和風天氣的API。
- 第二步,找解析JSON的第三方框架
各種百度之后也找到了大家推薦的一些,如:JSONKit、Mantle、AFNetworking、SBJSON、MJExtension等等等等。。。最后參考一篇教程,還是選擇了Mantle,當然不是說其他不好用,個人喜好而已。
Mantle的使用也很簡單。
1、新建一個繼承自MTLModel的類,並讓他遵循<MTLJSONSerializing>協議,注意這里還要#import <MTLModel.h>
2、在.h文件定義你的模型,如:
#import <Mantle/Mantle.h> #import <MTLModel.h> @interface TSCondition : MTLModel<MTLJSONSerializing> @property (nonatomic, strong) NSString *cityName; // 城市名字 @property (nonatomic, strong) NSString *nowCond; // 當前天氣狀況 @property (nonatomic, strong) NSString *nowTmp; // 當前溫度 @property (nonatomic, strong) NSString *winddir; // 風向 @property (nonatomic, strong) NSString *windsc; // 風力 @property (nonatomic, strong) NSString *date; // 日期 @property (nonatomic, strong) NSString *maxTmp; // 最高溫度 @property (nonatomic, strong) NSString *minTmp; // 最低溫度 @property (nonatomic, strong) NSString *weatherqlty; // 空氣質量 @end
3、在.m文件中實現類方法,實現JSON數據到模型的映射
+ (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"cityName": @"basic.city", @"nowCond": @"now.cond.txt", @"nowTmp": @"now.tmp", @"winddir": @"now.wind.dir", @"windsc": @"now.wind.sc", @"date": @"basic.update.loc", @"maxTmp": @"daily_forecast.tmp.max", @"minTmp": @"daily_forecast.tmp.min", @"weatherqlty": @"aqi.city.qlty" }; }
4、當然要接受到JSON數據要調用下面的方法,並且傳入帶有你要的數據的字典
//創建NSDictionary NSData *JSONData = ...//接口的響應數據 NSDictionary *JSONDict = [NSJSONSerialization JSONObjectWithData: JSONData options: 0 error: nil]; //使用MTLJSONSerialization創建模型對象 CATProfile *profile = [MTLJSONAdapter modelOfClass: CATProfile.class fromJSONDictionary: JSONDict error: NULL];
經過重復的練習,熟悉了用Mantle把JSON數據轉模型之后,我就開始搭建App的UI了
- 第三步,建立天氣預報頁面
(展示天氣頁面)
這里主要懶加載了一個backgroundView作為背景圖片,一個tableView以及多個XIB文件做成的Cell組成,我參考手機上自帶的天氣App做了一點美化,就是添加一個tableHeaderView,然后在上面添加一個ScrollView,在ScrollView上顯示城市和日期,達到它一直懸浮在最上層的效果,下面的cell都在它底下滑過。(后來發現其實這里可以不添加tableHeaderView,把tableView的位置下移就好了。。。)
=====================10.12日編輯==============================
對界面進行了調整,繼續ing。。。 - 第四步,創建UIpagecontrol
基本定好天氣預報信息如何展示之后,就要想,怎么展示多個天氣頁面呢?為了實現這個需求,我想到了用UIpagecontrol
這里主要用到一下屬性和方法
@property(nonatomic,strong) NSMutableArray *viewControllerArr; // 存放視圖的數組 @property(nonatomic,assign) NSInteger curPage ; // 記錄當前 @property(nonatomic,assign) NSInteger totalPages ; // 記錄總頁數 // ***翻頁方法 // 往后翻 -(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController{ self.curPage = ((ViewController *)viewController).page; UIImage *bgImg = [self createImageWithColor:((ViewController *)viewController).view.backgroundColor]; [self.navigationController.navigationBar setBackgroundImage:bgImg forBarMetrics:UIBarMetricsDefault]; self.pageControl.currentPage = self.curPage; if (self.curPage < self.totalPages - 1 && self.curPage != self.totalPages) { return self.viewControllerArr[self.curPage + 1]; }else{ return nil; } } // 往前翻 -(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController{ self.curPage = ((ViewController *)viewController).page; UIImage *bgImg = [self createImageWithColor:((ViewController *)viewController).view.backgroundColor]; [self.navigationController.navigationBar setBackgroundImage:bgImg forBarMetrics:UIBarMetricsDefault]; self.pageControl.currentPage = self.curPage; if (self.curPage > 0 && self.curPage != self.totalPages) { return self.viewControllerArr[self.curPage - 1]; }else{ return nil; } }
- 第五步,添加城市,使用searchBar
實現這個需求,我選擇在navigationBar 上添加一個按鈕,跳轉到一個帶搜索欄的視圖,實現輸入城市名字,添加天氣頁面的功能
這里的難點是,添加視圖后,UIpagecontrol的頁面添加和翻頁邏輯問題,經過一段時間努力還是解決了。 - 第六步,添加第三方側欄LLSlideMenu
我想使用這個側欄管理天氣頁面,因為我是用一個數組存儲城市名字去加載天氣頁面的,所以我的思路是,在側欄里面添加一個tableview,然后把數組的每一個元素顯示上去,然后實驗tableviewcell的編輯方法,達到移除相應的cell,即刪除相應的城市名字和城市頁面。
=====================10.20日編輯==============================
更新了程序運行的某些邏輯性問題,及以下 - 第七步,添加頁面下拉刷新功能
這里使用的是李明傑老師的MJRefresh,簡單易用,好好看文檔就能上手,想當初也是看李明傑老師的iOS視頻入門的,真心膜拜。 - 第八步,添加網絡狀態判斷,用hud提示完善用戶體驗
這里使用的是iOS自帶的框架SystemConfiguration.framework,並往項目里加入Reachability.h和.m文件
編寫自己的類,並添加類方法去判斷網絡狀態
+ (NSInteger)networkStatus { Reachability *reachability = [Reachability reachabilityWithHostName:@"http://www.heweather.com"];//網址可改 return [reachability currentReachabilityStatus]; }
- 第九步,實現程序從后天進入活躍狀態,自動更新數據
涉及到程序生命周期的知識,參考之前自己的文章http://www.cnblogs.com/jierism/p/5730373.html,使用“通知”,讓程序從后台進入活躍狀態時調用更新數據的方法,思路是獲取上次請求數據時的時間與當前系統時間比較,大於60分鍾則更新數據 - 第十步,用Coredata存儲數據
1.添加框架
2.建立實體
3.搭建上下文環境
使用這段萬能的代碼,只需把@“DataModel”修改為你相應數據模型的文件名 -
// 以下代碼用於加載之前所定義的數據模型,並連接到一個SQLite數據存儲中。 // 實際上任何采用Core Data的應用,以下代碼的內容都是相同的。 - (NSManagedObjectModel *)managedObjectModel { if (_managedObjectModel == nil) { NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"DataModel" ofType:@"momd"]; NSURL *modelURL = [NSURL fileURLWithPath:modelPath]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; } return _managedObjectModel; } - (NSString *)documentsDirectory { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths lastObject]; return documentsDirectory; } - (NSString *)dataStorePath { return [[self documentsDirectory] stringByAppendingPathComponent:@"DataStore.sqlite"]; } - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (_persistentStoreCoordinator == nil) { NSURL *storeURL = [NSURL fileURLWithPath:[self dataStorePath]]; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; NSError *error; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { NSLog(@"Error adding persistent store %@,%@",error,[error userInfo]); abort(); } } return _persistentStoreCoordinator; } -(NSManagedObjectContext *)managedObjectContext { if (_managedObjectContext == nil) { NSPersistentStoreCoordinator *coordinator = self.persistentStoreCoordinator; if (coordinator != nil) { _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; } } return _managedObjectContext; }
4.編寫保存數據和查詢數據的方法,並在程序需要的地方調用
可以參考http://blog.csdn.net/q199109106q/article/details/8563438這篇博客入門 - 到此程序的主要功能算是搞定了,后面還在考慮添加其他功能。。。在側欄里面實現,大家有什么好點子可以推薦一下。
=====================11.2日編輯============================== - 第十一步,實現給圖片添加天氣信息水印的功能
這個idea是在我學習Quartz 2D相關知識的時候萌生出來的,因為之前也用過QQ上的水印相機,感覺還挺有意思的,所以我就給我的App增加了這個功能,當然,技術有限,這個功能只能在生成后才能看見效果,並不能做到預覽效果,其實我也想實現像一些水印相機App那樣,在拍攝過程中就能看到水印。。。只能說技術有待提高。
實現:1.使用到UIImagePickerController和UIActionSheet,實現訪問相機和相冊功能
2.核心方法如下
// 在圖片上加水印 + (instancetype)imageWithImage:(UIImage *)image andViewController:(ViewController *)controller { // 開啟位圖上下文 UIGraphicsBeginImageContextWithOptions(image.size, NO, 0); [image drawAtPoint:CGPointZero]; // 設置文字的樣式 NSDictionary *CityDict = @{ NSFontAttributeName : [UIFont fontWithName:@"KaiTi_GB2312" size: 40], NSForegroundColorAttributeName : [UIColor whiteColor] }; NSDictionary *dict = @{ NSFontAttributeName : [UIFont fontWithName:@"KaiTi_GB2312" size: 25], NSForegroundColorAttributeName : [UIColor whiteColor] }; CGFloat cityX = image.size.width - image.size.width / 4; CGFloat cityY = image.size.height / 13; // 畫上城市名 [controller.cityName drawAtPoint:CGPointMake(cityX , cityY) withAttributes:CityDict]; CGFloat imgX = cityX + 80; CGFloat imgY = cityY - 20; CGFloat imgW = 80; // 畫上天氣圖片 UIImage *condImg = [UIImage stringWithWeather:controller.jsonDic[@"now"][@"cond"][@"txt"]]; [condImg drawInRect:CGRectMake(imgX, imgY, imgW , imgW)]; CGFloat tempX = cityX + 40; CGFloat tempY = cityY + 60; // 畫上溫度計 UIImage *shape = [UIImage imageNamed:@"Shape"]; [shape drawInRect:CGRectMake(tempX-20, tempY, 14, 22)]; // 畫上溫度和天氣情況 NSString *tempAndCond = [NSString stringWithFormat:@"%@℃ %@",controller.jsonDic[@"now"][@"tmp"],controller.jsonDic[@"now"][@"cond"][@"txt"]]; [tempAndCond drawAtPoint:CGPointMake(tempX, tempY) withAttributes:dict]; // 畫上APP標記 NSString *ts = @"天\n時"; [ts drawInRect:CGRectMake(10, image.size.height - 80,40,80) withAttributes:dict]; // 獲得新圖片 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); //關閉上下文 UIGraphicsEndImageContext(); return newImage; }
使用:翻到你想要顯示的城市天氣頁面,選擇照片或者拍攝,完了之后再相冊就能查看到生成的圖片
效果圖: - 另外修改了一下界面
=====================11.8日編輯==============================
最近和風天氣的API更新了,所以我的一些URL進行了修改。現在API的付費版本可以查詢外國的天氣了~ - 第十二步,新增了一個UISwitch按鈕,實現用戶可以選擇是否自動更新天氣信息,並用偏好設置記錄它的開關狀態,只需幾步
// 保存 - (void)isRefrash:(UISwitch *)sender { // [NSUserDefaults standardUserDefaults]可以直接操作偏好設置文件夾 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // 自動幫我們生成一個plist文件存放在偏好設置的文件夾 [defaults setBool:sender.on forKey:@"switchStatus"]; // 同步:把內存中的數據和沙盒同步 [defaults synchronize]; } // 在UISwitch加載時讀取狀態 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [self.refashSwitch setOn:[defaults boolForKey:@"switchStatus"]];
- 第十三步,學習了一下sketch的簡單操作,給自己的App做了個圖標(ps:修改了好多次。。。)