在系列的第一部分,你通過Cocoapods設置了你的工程,為控制器添加了視圖並且實際了它們,最終我們通過創建模型來反應天氣的變化我們就可以完成一個吸引人的應用了。
在第二部分,我們會最終完成這個應用,在第二部分,我們會通過weatherAPI來完成接通UI來刷新數據。你將會學到ReactiveCocoa的使用語法,和非常依賴數據獲取的和UI 更新事件。
讓我們開始:
你有兩個選擇來開始這一部分的,你可以選擇通過看第一部分來一步一步完成前面的代碼,或者來這里http://cdn4.raywenderlich.com/wp-content/uploads/2013/11/SimpleWeather-Part-1.zip下載代碼。
在這個輔導開始之前,我們需要創建為你的app創建天氣模型,現在你需要通過OpenWeatherMapAPI來讓你的app接收到數據。你將通過WXClient和WXManager這兩個類來保存,你獲取到並解析的數據。 WXClient的唯一責任就是創建API請求並且解析它們。
Working with ReactiveCocoa
確保你正在使用SimpleWeather.xcworkspace文件。打開WXClient.h文件,添加下面的導入語句
@import CoreLocation;
#import<ReactiveCocoa/ReactiveCocoa/ReactiveCocoa.h>
提示:也許你之前從來沒有看到過@import指令,你還可以看看這個 What’s New in Objective-C and Foundation in iOS 7.
在WXClient.h: 中添加下面的四個公共借口聲明方法
@import Foundation
- (RACSignal *)fetchJSONFormURL:(NSURL *)url;
- (RACSignal *)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate;
- (RACSignal *)fetchHourlyForecastForLocation:(CLLocationCoordinate2D)coordinate;
- (RACSignal *)fetchDailyForecastForLocation:(CLLocationCoordinate2D)coordinate;
是的,在這上面也許有一個微小的事情你沒有認出來
也許現在是介紹ReactiveCocoa一個很好的時間段。
ReactiveCocoa(RAC)是一個Objective-c的框架,用於響應事編程,用於組合和轉換值序列的框架
在https://github.com/blog/1107-reactivecocoa-for-a-better-world這里提供了一份關於ReactiveCocoa非常好的介紹,也就是:
-
組成對未來數據的操作的能力.
-
一種以盡量減少轉台.
-
聲明的形式來定義行為和屬性之間的關系.
-
統一的高級異步操作接口.
-
建立在KVO之上的一個可愛的API.
例如,你可以觀察username屬性的變化,像這樣:
[RACAble(self.username) subscribeNext:^(NSString *newName){ NSLog(@“%@“,newName); }]
這個subscribeNext代碼塊被稱為self.username變化時的值。新的值被傳遞到代碼塊中。
下面時ReactiveCocoa在github上的示例
[[RACSignal
combineLatest:@[ RACAble(self.password), RACAble(self.passwordConfirmation) ]
reduce:^(NSString *currentPassword, NSString *currentConfirmPassword) {
return [NSNumber numberWithBool:[currentConfirmPassword isEqualToString:currentPassword]]; }]
subscribeNext:^(NSNumber *passwordsMatch) {
self.createEnabled = [passwordsMatch boolValue]; }];
這個RACSignal
對象捕捉當前和未來值。信號可以被鏈接,結合反應給觀察者。信號會不會執行將取決於它是否被同意執行。
就是說[mySignal fetchCurrentConditionsForLocation:someLocation];會被創建和返回一個信號,但是不會做其他事情。你將在稍后明白怎樣訂閱和反應
打開WXClient.m添加下面的導入頭文件:
#import “WXCondition.h”
#import “WXDailyForecast.h”
在導入部分,添加私有接口:
@interface WXClient()
@property (nonatomic, strong)NSURLSession *session;
@end
找個接口有管理你的API請求的URL session單個屬性。
在@implementation
和@end
:之間添加 init 方法
-(id)init {
if (self = [super init]){
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config];
}
return self;
}
這個為defaultSessionConfiguration
.簡單的創建session
注意:如果你在這之前沒有接觸過NSURLSession你可以看看 NSURLSession tutorial
Building the Signals
你需要創建一個主方法來建立信號從而獲取RUL 中的數據。你已經知道三種方法來返回信息,一個時當前的信息,一個時小時天氣預報,一個時每天的天氣預報。
用ReactiveCocoa代替三個獨立的方法,首次使用也許會有些陌生,但是我們會一步一步來完成。
在WXClient.m添加下面的方法
- (RACSignal *)fetchJSONFromURL:(NSURL *)url{
NSLog(@“Fetching:%@”,url.absoluteString);
//1
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber>subscriber) {
//2
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHander:^(NSData *data,NSURLResponse *response,NSError *error)
{
//TODO:Handle retrieved data
}];
//3
[dataTask resume];
//4
return [RACDisposable disposableWithBlock:^{[dataTask cancel]; }];
}] doError:^(NSError *error){
//5
NSLog(“%@“,error);}];
}
通過一步步介紹,你會看到代碼完成下面的事情
1.返回信號,但是記住,直到該信號被訂閱才會執行。-fetchJSONFromURL:利用其他方法創建對象和使用對象,這種行為也被稱為factory pattern(工場模式)
2.創建NSURLSessionDataTask(iOS7中新的東西)從URL中獲取數據。你將隨后添加解析數據。
3.一旦有人預定信號就啟動網絡請求
4.創建並返回RACDisposable
對象來清理已經被銷毀的信號
5.添加一個“副作用”,來添加錯誤日志。
注意:可以通過找個網站來更好的理解ReactiveCocoa
找到// TODO: Handle retrieved data中的-fetchJSONFromURL:替換稱下面的代碼:
if (!error){
NSError *jsonError = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if(!jsonError){
//1
[subscriber sendNext:json];
}
else{
// 2
[subscriber sendError:jsonError];
}
}
else {
//2
[subscriber sendError:error];
}
//3
[subscriber sendCompleted];
下面顯示上面的每一段代碼時干啥的
1.當JSON數據存在並且沒有錯誤,將JSON序列作為一個數據或者字典發送給用戶
2.如果有錯誤就通知用戶
3.無論請求成功與否都讓用戶知道請求已經完成。
也許-fetchJSONFromURL:
這個方法有點長,但是它使得你的特定的API請求在最后會很簡單。
Fetching Current Conditions
仍然時在WXClient.m方法中,添加下面的方法:
- (RACSignal *)fetchCurrentConditionsForLocation: (CLLocationCoordinate2D)coordinate{
//1
NSString *urlString = [NSString stringWithFormat:@“http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial”,coordinate.latitude,coordinate.longitude];
NSURL *url = [NSURL URLWithString:urlString];
//2
return [[self fetchJSONFormURL:url] map:^(NSDictionary *json){
//3
return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:json error:nil];
}];
}
依次給每段代碼解釋:
1. 利用CLLocationCoordinate2D
對象規定URL為緯度和經度格式
2. 用你剛才創建信號的方法。由於返回值是一個信號,所以你可以調用它的其他ReactiveCocoa 方法。NSDictionary中的一個實例—在這里,你將映射返回值到另一個不同的值中。
3.使用MTLJSONAdapter來轉換成JSON的WXCondition對象,使用你的WXCondition創建 MTLJSONSerializing協議
Fetching the Hourly Forecast
現在,在WXClient.m中添加下面的犯法,來給特定坐標顯示實時預報:
- (RACSignal *)fetchHourlyForecastForLocation: (CLLocationCoordinate2D)coordinate{
NSString *urlString = [NSString stringWithFormat:@“http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&units=imperial&cnt=12”,coordinate.latitude,coordinate.longitude];
NSURL *url = [NSURL URLWithString:urlString];
//1
return [[self fetchJSONFormURL:url] map^(NSDictionary *json){
//2
RACSequence *list = [json[@“list”] rac_sequence];
//3
return [[list map:^(NSDictionary *item) {
//4
return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:item error:nil];
//
}] array];
}];
}
這是一個很短的方法,但是有很多細節:
1.使用-fetchJSONFromURL再次格式化為JSON格式作為映射。注意有多少代碼來使用這個方法來保存
2.從JSON的”list”鍵來創建 RACSequence 。RACSequence
s讓你通過執行ReactiveCocoa來操作列表。
3.映射對象到新的列表,這里被稱為-map
在這個列表中的每個對象,都將返回新的對象到列表。
4.再次使用MTLJSONAdapterr將WXCondition對象轉換成JSON
5.使用RACSequence中的 -map: 返回另一個 RACSequence,利用這個方法可以簡單的獲取所謂NSArray
.返回的數據
Fetching the Daily Forecast
仍然在WXClient.m:中添加下面的方法
- (RACSignal *)fetchDailyForecastForLocation:(CLLocationCoordinate2D)coordinate{
NSString *urlString = [NSString stringWithFormat:@”http://api.openweathermap.org/data/2.5/forecast/daily?lat=%f&lon=%f&units=imperial&cnt=7“,coordinate.latitude,coordinate.longitude];
NSURL *url = [NSURL URLWithString:urlString];
// use the generic fetch method and map results to convert into an array of mantle objects
return [[self fetchJSONFormURL:url] map:^(NSDictionary *json) {
//從JSON 列表中選擇一個列創建sequence
RACSequence *list = [json[@“list”] rac_sequence];
//use a function to map results from JSON to mantle objects
return [[list map:^(NSDictionary *item){
return [MTLJSONAdapter modelOfClass:[WXDailyForecast class] fromJSONDictionary:item error:nil;]
}]]
}]
}
到這里你是否感覺很熟悉?是啊, 這里的方法完全一樣 -fetchHourlyForecastForLocation:
除了它使用WXDailyForecast而不是WXCondition和獲取daily預報。
運行你的程序,這時候你不會看到任何新加入的東西,但是這是讓你休息一下並確保美歐任何錯誤或者警告的好機會。
Managing & Storing Your Data
是時候來完成WXManager這個類了,這是將一切融合的一個類。這個類將實現你的應用程序中的一些關鍵功能:
-
它遵循單例設計模式singleton design pattern
-
它視圖找到設備的位置
-
找到位置后,獲取響應的氣象數據
打開WXManager.h並且用下面的代碼替換內容
@import Foundation;
@import CoreLocation;
#import <ReactiveCocoa/ReactiveCocoa/ReactiveCocoa.h>
//1
#import “WXCondition.h”
@interface WXManager:NSObject <CLLocationManagerDelegate>
//2
+ (instancetype)sharedManager;
//3
@property (nonatomic, strong, readonly) CLLocation *currentLocation;
@property (nonatomic, strong, readonly) WXCondition *currentCondition;
@property (nonatomic, strong, readonly) NSArray *hourlyForecast;
@property (nonatomic, strong, readonly) NSArray *dailyForecast;
//4
- (void)findCurrentLocation;
@end
這里沒有什么非常重要的知識點,但是上面注釋的地方需要注意
1.注意如果你沒有導入WXDailyForecast.h,你將總是使用WXCondition作為預報類。WXDailyForecast.h只是幫助覆蓋的JSON轉換成Objective-c
2.利用instancetype替換WXManager,所以子類會返回適當的類型。
3.這些屬性將儲存你的數據,由於WXManager是一個單例,所以這些屬性可以訪問任何地方。設置公共屬性為只讀(readonly
)是因為只有管理者才可以更改。
4.這種方法啟動或者刷新將獲取到整個位置和天氣的數據
現在打開WXManager.m添加下面的倒入文件
#import “WXClient.h”
#import <TSMessages/TSMessage.h>
右側下方的進口,粘貼在專用接口,如下所示
@interface WXManager () 下面是關於上面屬性的解釋: 1.聲明你在公共接口中加入相同的屬性,但是這一次把他們定義為讀寫(readwrite),因此您可以在后台更改值 2.聲明為定位的發現和數據抓取其他一些私人位置,添加@implementation和@end:之間的下列通用單例構造函數:
- (id)init { |
你使用更ReactiveCocoa方法來觀察和反應值的變化。下面是上面的方法的作用
1.創建一個位置管理器並且以self
.來設置它的委托
2.為管理者創建WXClient對象。這將處理所有的網絡和數據請求,根據我們的最佳實踐的這個主張。
3.類似KVO但是比KVO更加強大,不明白KVO你可以看這里Key-Value Observing
4.為了繼續, currentLocation不能為零
5.-flattenMap:非常類似於-map:: ,將值扁平化代替映射值,並返回包含所有三個信號中的一個對象。通過這種方式,你可以考慮所有三個進程作為單個工作單元。
6.將信號傳遞到主線程上的用戶。
7.這不是很好的做法,從你的模型中的UI交互,但出於演示的目的,每當發生錯誤時你會顯示一個標語。
下一步,以顯示准確的天氣預測,我們需要確定設備的位置
Finding Your Location
接下來,您將添加時的位置,發現觸發的天氣抓取的代碼。下面的代碼添加到WXManager.m:中
- (void)findCurrentLocation {
self.isFirstUpdate = YES; [self.locationManager startUpdatingLocation];}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
// 1
if (self.isFirstUpdate) {
self.isFirstUpdate = NO; return; }
CLLocation *location = [locations lastObject];
// 2
if (location.horizontalAccuracy > 0) {
// 3
self.currentLocation = location; [self.locationManager stopUpdatingLocation]; }}
上面的方法是相當簡單的:
1.總是忽略第一個位置更新,因為它幾乎總是緩存
2.一旦你有適當的精度的位置,停止更新
3.設置currentLocation鍵將觸發你在init執行前設置的RACObservable
Retrieve the Weather Data
最后,現在是時候添加三個取其中調用客戶端上的方法的方法和保存價值的manager。所有這三種方法都捆綁起來,由RACObservable訂閱創建的init方法之前添加。您將返回客戶端返回相同的信號,這也可以訂閱。
將下面的代碼添加到WXManager.m:中
- (RACSignal *)updateCurrentConditions {
return [[self.client fetchCurrentConditionsForLocation:self.currentLocation.coordinate] doNext:^(WXCondition *condition) {
self.currentCondition = condition; }];}
- (RACSignal *)updateHourlyForecast {
return [[self.client fetchHourlyForecastForLocation:self.currentLocation.coordinate] doNext:^(NSArray *conditions) {
self.hourlyForecast = conditions; }];}
- (RACSignal *)updateDailyForecast {
return [[self.client fetchDailyForecastForLocation:self.currentLocation.coordinate] doNext:^(NSArray *conditions) {
self.dailyForecast = conditions; }];}
它看起來像一切都連接起來,蓄勢待發。別急!該應用程序實際上並沒有告訴管理者做任何事情
打開 WXController.m並導入管理文件的頂部,如下所示
#import “WXManager.h”
在- viewDidload最后添加下面的代碼
[[WXManager shareManager] findCurrentLocation];
這只是要求管理類,開始尋找設備的當前位置。
生成並運行您的應用程序,系統會提示您是否允許使用位置服務。你仍然不會看到任何UI的更新,但檢查控制台日志,你會看到類似以下內容
2013-11-05 08:38:48.886 WeatherTutorial[17097:70b] Fetching: http://api.openweathermap.org/data/2.5/weather?lat=37.785834&lon=-122.406417&units=imperial2013-11-05 08:38:48.886 WeatherTutorial[17097:70b] Fetching: http://api.openweathermap.org/data/2.5/forecast/daily?lat=37.785834&lon=-122.406417&units=imperial&cnt=72013-11-05 08:38:48.886 WeatherTutorial[17097:70b] Fetching: http://api.openweathermap.org/data/2.5/forecast?lat=37.785834&lon=-122.406417&units=imperial&cnt=12
它看起來有點遲鈍,但輸出是指所有的代碼是否工作正常,網絡請求發火正常。
Wiring the Interface
這是最后一次顯示所有你獲取,映射和存儲的數據。你將利用ReactiveCocoa觀察新數據到達WXManager和界面時的更新。
還在WXController.m,去的 - viewDidLoad中的底部,並添加下面的代碼只是上面的
[ [ WXManager sharedManager ] findCurrentLocation ] ;行
1.在WXManager單例中觀察currentCondition key
2.因為你更新UI所以要提供在主線程上的變化
3.更新與氣象數據的文本標簽;你使用newCondition的文字,而不是單例。以保證用戶參數是新值
4.使用映射的圖像文件名創建一個圖像,並將其作為該視圖的圖標
生成並運行您的應用程序,你會看到當前溫度,當前條件,並表示當前條件下的圖標。所有的數據都是實時的,所以你的價值觀可能不會匹配那些下文。但是,如果你的位置是舊金山,它似乎總是約65度。幸運的舊金山!:]
// 1[[RACObserve([WXManager sharedManager], currentCondition) |
ReactiveCocoa Bindings
ReactiveCocoa帶來了自己的Cocoa Bindings的形式為iOS
不知道是什么綁定?概括地說,他們是一個技術,它提供了保持模型和視圖的值而無需編寫大量的同步的一種手段“膠水代碼”。它們允許你建立一個視圖和數據塊之間的間接連接, “結合”它們,使得在一方的變化反映在另一個中
這是一個非常強大的概念,不是嗎
好了,拿起你的下巴掉在地上。現在是時候繼續前進。
注意:對於強大的綁定更多的例子,你可以看看 ReactiveCocoa Readme.。
添加下面你在上一步中添加的代碼下面的代碼:
// 1RAC(hiloLabel, text) = [[RACSignal combineLatest:@[
// 2
RACObserve([WXManager sharedManager], currentCondition.tempHigh),
RACObserve([WXManager sharedManager], currentCondition.tempLow)]
// 3
reduce:^(NSNumber *hi, NSNumber *low) {
return [NSString stringWithFormat:@"%.0f° / %.0f°",hi.floatValue,low.floatValue]; }]
// 4
deliverOn:RACScheduler.mainThreadScheduler];
1.在RAC ( ... )宏有助於保持語法干凈。從該信號的返回值被分配給 hiloLabel對象的文本項。
2.觀察了高溫和低溫的currentCondition key。
3.降低您的組合信號的值轉換成一個單一的值,注意該參數的順序信號的順序相匹配。
4。同樣,因為你正在處理的用戶界面,提供一切都在主線程。
生成並運行您的應用程序,你應該看到的高/低標號沿着與UI的其余部分,像這樣的左下方更新時間
// 1RAC(hiloLabel, text) = [[RACSignal combineLatest:@[ |
上面的代碼結合高低溫值到hiloLabel的text屬性。這里有一個詳細的看看你完成這件事情:
Displaying Data in the Table View
現在,你已經獲取所有的數據,你可以在表視圖整齊地顯示出來。你會在標題單元格(如適用)分頁表視圖中顯示六個最新的每小時和每天的預測。該應用程序會出現有三個頁面:一個是目前的條件,一個是逐時預報,以及一個用於每日預報。
之前,你可以添加單元格的表視圖,你需要初始化和配置一些日期格式化
在WXController.m頂部添加下面的兩個屬性
@property (nonatomic, strong) NSDateFormatter *hourlyFormatter;
@property (nonatomic, strong) NSDataFormatter *dailyFormatter;
由於日期格式化是昂貴的創建,我們將實例化他們在我們的init方法和存儲使用這些屬性對它們的引用
還是在同一個文件中,添加直屬@implementation
下面的代碼
- (id)init { |
你可能想知道為什么你初始化這些日期格式化在-init和而不是viewDidLoad中其他事物一樣。好問題!
- viewDidLoad中實際上可以在一個視圖控制器的生命周期多次調用。 NSDateFormatter對象是昂貴的初始化,而是將它們放置在你的init會確保它們是由你的視圖控制器初始化一次
尋找實現代碼如下: numberOfRowsInSection :在WXController.m並更換TODO並以下列返回行
// 1
if (section == 0) {
return MIN([[WXManager sharedManager].hourlyForecast count], 6) + 1;
}
// 2
return MIN([[WXManager sharedManager].dailyForecast count], 6) + 1;
相對短的代碼塊,但這里是它的作用:
1.第一部分是對的逐時預報。使用六小時最新預測,並添加了頁眉多一個單元格
2.接下來的部分是每日預報。使用六種最新的每日預報,並添加了頁眉多一個單元格
Note: You’re using table cells for headers here instead of the built-in section headers which have sticky-scrolling behavior. The table view is set up with paging enabled and sticky-scrolling behavior would look odd in this context.
尋找實現代碼如下:的cellForRowAtIndexPath :在WXController.m ,並以下列取代TODO部分
if (indexPath.section == 0) { 同樣,這段代碼是相當簡單的 1.每個部分的第一行是標題單元格 2.獲取每小時的天氣和使用自定義配置方法配置單元 3.獲取每天的天氣,並使用另一個自定義配置方法配置單元 最后,下面的三種方法添加到WXController.m :
|
Adding Polish to Your App
本頁面為每小時和每天的預測不占用整個屏幕。幸運的是,這原來是一個真正簡單的辦法。在本教程前面您捕獲在
-viewDidLoad
.中屏幕高度。
查找表視圖的委托方法,實現代碼如下:-tableView:heightForRowAtIndexPath:在WXController.m and replace the TODO
and return
lines with the following:
NSInteger cellCount = [self tableView:tableView numberOfRowsInSection:indexPath.section]; 這將屏幕高度由cell中各部分的數量,以便所有單元的總高度等於屏幕的高度. 生成並運行您的應用程序;表視圖現在填滿整個屏幕,如下面的截圖: 最后要做的是把我在本教程的Part 1開頭提到的模糊。模糊應填寫為動態滾動過去預測的第一頁
3.所得到的值賦給模糊圖像的alpha屬性來更改模糊圖像的多,你會看到當你滾動 生成並運行您的應用程序,滾動你的表視圖,並檢查了真棒模糊效果 ![]() Where To Go From Here 你已經完成了很多在本教程中:您使用CocoaPods創建了一個項目,建立一個視圖結構完全代碼,創建的數據模型和管理人員,並利用函數式編程連接到一起! 有很多很酷的地方,你可以把這個應用程序。一個整潔的開始是使用Flickr的API來查找基於設備的位置,背景圖片 還有,你的應用程序只處理溫度和條件;什么其他的天氣信息,你能融入你的應用程序
|