天天看點

iOS7最佳實踐:一個天氣App案例

注:本文譯自: raywenderlich ios-7-best-practices-part-1 ,去除了跟主題無關的寒暄部分。 歡迎轉載,保持署名

在這個兩部分的系列教程中,您将探索如何使用以下工具和技術來建立自己的App:

  • Cocoapods
  • Manual layout in code(純代碼布局)
  • ReactiveCocoa
  • OpenWeatherMap

本教程專為熟悉基本知識的、但還沒有接觸到太多進階主題的中級開發者而設計。本教程也是想要去探索Objective-C 函數程式設計 一個很好的開始。

iOS7最佳實踐:一個天氣App案例

開始

打開Xcode并執行 

File\New\Project

 。選擇

Application\Empty Application

 。将項目命名為 

SimpleWeather

 ,單擊下一步,選擇一個目錄去儲存你的項目,然後點選Create。 現在,你的基礎項目已經完成。下一步是內建你的第三方工具。但首先你要 

關閉Xcode

 ,確定他不會影響下一步。

Cocoapods

你将要下載下傳 Cocoapods 的代碼,在Xcode項目中添加檔案來使用,并配置項目需要的設定。

Mantle

Mantle 是由于Github團隊開發的,目的是去除Objective-C把JSON資料轉為NSObject子類的所有樣闆代碼。Mantle也能做資料轉換,通過一種神奇的方式把JSON原始資料(strings, ints, floats)轉換為複雜資料,比如NSDate, NSURL, 甚至是自定義類。

LBBlurredImage

LBBlurredImage 是一個繼承自UIImageView,輕而易舉使圖像模糊的項目。你将僅僅用一行代碼來建立一個神奇的模糊效果。

TSMessages

TSMessages 是另一個非常簡單的庫,用來顯示浮層警告和通知。當出現錯誤資訊而不直接影響使用者的時候,最好使用浮層來代替模态視窗(例如UIAlertView),這樣你将盡可能減少對使用者的影響。

你将隻用TSMessages,在網絡失去連接配接或API錯誤的時候。如果發生錯誤,你将看到類似這樣的一個浮層:

iOS7最佳實踐:一個天氣App案例

ReactiveCocoa

最後,你将使用到 ReactiveCocoa ,他也來自于GitHub團隊。ReactiveCocoa給Objective-C帶來了函數程式設計,類似與.NET的 Reactive Extensions 。你将在第二部分花費大部分時間去實作ReactiveCocoa。

設定你的Cocoapods

設定你的Cocoapods,先要確定你已經安裝了Cocoapods。為此,打開指令行程式,并輸入。

你将會看到類似這樣的輸出:

這決定于你如何管理Ruby gems,例如你使用 rbenv 或 RVM ,路徑可能有所不同。

如果指令行簡單的傳回提示,或顯示 

pod not found

 ,表示Cocoapods未安裝在你的機器上。可以檢視我們的 Cocoapods教程 作為安裝說明。這也是一個很好的資源,如果你想更多得了解Cocoapods的話。

Podfiles 是用來告訴Cocoapods哪些開源項目需要導入。

要建立你的第一個Cocoapod,首先在指令行中用 

cd

 指令導航到你的XCode項目所在的檔案夾,在指令行中啟動編輯器,輸入

1
2
3
4
5
6      
platform :ios, '7.0'
 
pod 'Mantle'
pod 'LBBlurredImage'
pod 'TSMessages'
pod 'ReactiveCocoa'                

這檔案做了兩件事情:

  • 告訴Cocoapods你的目标平台與版本,這裡的你目标是iOS 7.0。
  • 列給Cocoapods一個項目所有需要引入和安裝的三方庫清單。

在指令行中輸入 

pod install

 進行安裝。

這可能需要花一到兩分鐘的時間去安裝各種包。你的指令行應該輸出如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16      
$ pod install
Analyzing dependencies
 
CocoaPods 0.28.0 is available.
 
Downloading dependencies
Installing HexColors (2.2.1)
Installing LBBlurredImage (0.1.0)
Installing Mantle (1.3.1)
 
Installing ReactiveCocoa (2.1.7)
Installing TSMessages (0.9.4)
Generating Pods project
Integrating client project
 
[!] From now on use `SimpleWeather.xcworkspace`.
           

Cocoapods會在你的項目目錄中建立一堆新檔案,但是,隻有一個需要你關心,

SimpleWeather.xcworkspace

 。

用Xcode打開 

SimpleWeather.xcworkspace

 。看看你的項目設定,現在有一個Pods項目在你的項目工作區,以及在Pods檔案夾放着每一個你引入的庫,如下所示:

iOS7最佳實踐:一個天氣App案例

確定你已經選擇SimpleWeather項目,如圖所示:

iOS7最佳實踐:一個天氣App案例

建構并運作您的App,以確定一切工作正常:

iOS7最佳實踐:一個天氣App案例

建立你的主視圖控制器

雖然這個App看起來複雜,但它還會通過一個單一的View Controller完成。現在,你将添加他。

選中 

SimpleWeather

 項目,單擊 

File\New\File

 ,并且選擇

Cocoa Touch\Objective-C class

 . 命名為 

WXController

 ,并設定為

UIViewController

 的子類。

確定 

Targeted for iPad

 和 

With XIB for user interface

 都沒有選中,如下圖所示:

iOS7最佳實踐:一個天氣App案例

打開 

WXController.m

 然後用如下所示替換 

-viewDidLoad

 方法:

1
2
3
4
5
6      
- (void)viewDidLoad {
    [super viewDidLoad];

    // Remove this later
    self.view.backgroundColor = [UIColor redColor];
}
                

現在打開 

AppDelegate.m

 ,并且引入如下兩個class:

1
2      
#import "WXController.h"
#import <TSMessage.h>

           

眼尖的讀者會注意到 

WXController

 使用引号引入, 

TSMessage

 使用單括号引入。

回頭看下當你建立Podfile的時候,你使用Cocoapods引入 

TSMessage

 。Cocoapods建立TSMessage項目,并把它加入到工作空間。既然你從工作區的其他項目導入,可以使用尖括号代替引号。

代替 

-application:didFinishLaunchingWithOptions

 的内容:

1
2
3
4
5
6
7
8
9
10      
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // 1
    self.window.rootViewController = [[WXController alloc] init];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    // 2
    [TSMessage setDefaultViewController: self.window.rootViewController];
    return YES;
}
                

标号注釋的解釋:

  1. 初始化并設定 

    WXController

     執行個體作為App的根視圖控制器。通常這個控制器是一個的 

    UINavigationController

     或 

    UITabBarController

     ,但在目前情況下,你使用 

    WXController

     的單個執行個體。
  2. 設定預設的視圖控制器來顯示你的TSMessages。這樣做,你将不再需要手動指定要使用的控制器來顯示警告。

建構并運作,看看你的新視圖控制器起作用了。

iOS7最佳實踐:一個天氣App案例

在紅色背景下,狀态欄有點不夠清晰。幸運的是,有一個簡單的方法,使狀态欄更清晰易讀。

在iOS7, UIViewController 有一個新的API,用來控制狀态欄的外觀。打開

WXController

 ,直接添加下面的代碼到 

-viewDidLoad:

 方法下:

1
2
3      
- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}

           

再次建構并運作,你将看到狀态欄如下的變化:

iOS7最佳實踐:一個天氣App案例

設定你的App視圖

現在是時候讓你的App接近生活。下載下傳這個項目的 圖檔 ,并解壓縮到一個合适的位置。這個壓縮包的背景圖檔出自Flickr使用者 idleformat 之手,天氣圖檔出自Dribbble使用者 heeyeun 之手。

切換回Xcode,單擊 

File\Add Files to “SimpleWeather”

 ….定位到你剛剛解壓縮的圖檔檔案夾并選擇它。選擇

Copy items into destination group’s folder (if needed)

 ,然後單擊 

Add

 。

打開 

WXController.h

 , 添加如下委托協定:

1      
<UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>

           

現在打開 

WXController.m

 。 小提示:你可以使用 

Control-Command-Up

 的快捷鍵來實作 

.h

 和 

.m

 檔案之間的快速切換。

添加如下代碼到 

WXController.m

 頂部:

1      
#import <LBBlurredImage/UIImageView+LBBlurredImage.h>

           

LBBlurredImage.h

 包含在Cocoapods引入的 

LBBlurredImage

 項目,你會使用這個庫來模糊背景圖檔。

應該有一個空的私有接口樣闆在 

WXController

 imports的下方。它具有以下屬性:

1
2
3
4
5
6
7
8      
@interface WXController ()

@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIImageView *blurredImageView;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign) CGFloat screenHeight;

@end

           

現在,是時候在項目中建立并設定視圖。

下面是你App的分解圖,記住,table view将是透明的:

iOS7最佳實踐:一個天氣App案例

為了實作動态模糊效果,在你的App中,你會根據App的滾動來改變模糊圖像的alpha值。

打開 

WXController.m

 ,使用如下代碼來,替換掉 

-viewDidLoad

 中設定背景色的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25      
// 1
self.screenHeight = [UIScreen mainScreen].bounds.size.height;

UIImage *background = [UIImage imageNamed:@"bg"];

// 2
self.backgroundImageView = [[UIImageView alloc] initWithImage:background];
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:self.backgroundImageView];

// 3
self.blurredImageView = [[UIImageView alloc] init];
self.blurredImageView.contentMode = UIViewContentModeScaleAspectFill;
self.blurredImageView.alpha = 0;
[self.blurredImageView setImageToBlur:background blurRadius:10 completionBlock:nil];
[self.view addSubview:self.blurredImageView];

// 4
self.tableView = [[UITableView alloc] init];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.2];
self.tableView.pagingEnabled = YES;
[self.view addSubview:self.tableView];
                

這是非常簡單的代碼:

  1. 擷取并存儲螢幕高度。之後,你将在用分頁的方式來顯示所有天氣 資料時,使用它。
  2. 建立一個靜态的背景圖,并添加到視圖上。
  3. 使用LBBlurredImage來建立一個模糊的背景圖像,并設定alpha為0,使得開始

    backgroundImageView

     是可見的。
  4. 建立tableview來處理所有的資料呈現。 設定WXController為delegate和dataSource,以及滾動視圖的delegate。請注意,設定 

    pagingEnabled

     為

    YES

     。

添加如下UITableView的delegate和dataSource的代碼到 

WXController.m

 的

@implementation

 塊中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38      
// 1
#pragma mark - UITableViewDataSource

// 2
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // TODO: Return count of forecast
    return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (! cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
    }

    // 3
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
    cell.textLabel.textColor = [UIColor whiteColor];
    cell.detailTextLabel.textColor = [UIColor whiteColor];

    // TODO: Setup the cell

    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // TODO: Determine cell height based on screen
    return 44;
}

           
  1. Pragma mark

     是 組織代碼 的很好的一種方式。
  2. 你的table view有兩個部分,一個是每小時的天氣預報,另一個用于每日播報。table view的section數目,設定為2。
  3. 天氣預報的cell是不可選擇的。給他們一個半透明的黑色背景和白色文字。
1      
注意:使用格式化的注釋 // TODO:可以幫助Xcode找到需要以後完成的代碼。你還可以使用 Show Document Items(Control-6)來檢視TODO項。
                

最後,添加如下代碼到 

WXController.m

 :

1
2
3
4
5
6
7
8
9      
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    CGRect bounds = self.view.bounds;

    self.backgroundImageView.frame = bounds;
    self.blurredImageView.frame = bounds;
    self.tableView.frame = bounds;
}
                

在 

WXController.m

 中,你的視圖控制器調用該方法來編排其子視圖。

建構并運作你的App,看看你的視圖如何堆疊。

iOS7最佳實踐:一個天氣App案例

仔細看,你會看到所有空的table cell的cell分隔線。

仍然在 

-viewDidLoad

 中,添加下面的代碼來設定你的布局架構和邊距:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27      
// 1
CGRect headerFrame = [UIScreen mainScreen].bounds;
// 2
CGFloat inset = 20;
// 3
CGFloat temperatureHeight = 110;
CGFloat hiloHeight = 40;
CGFloat iconHeight = 30;
// 4
CGRect hiloFrame = CGRectMake(inset,
                              headerFrame.size.height - hiloHeight,
                              headerFrame.size.width - (2 * inset),
                              hiloHeight);

CGRect temperatureFrame = CGRectMake(inset,
                                     headerFrame.size.height - (temperatureHeight + hiloHeight),
                                     headerFrame.size.width - (2 * inset),
                                     temperatureHeight);

CGRect iconFrame = CGRectMake(inset,
                              temperatureFrame.origin.y - iconHeight,
                              iconHeight,
                              iconHeight);
// 5
CGRect conditionsFrame = iconFrame;
conditionsFrame.size.width = self.view.bounds.size.width - (((2 * inset) + iconHeight) + 10);
conditionsFrame.origin.x = iconFrame.origin.x + (iconHeight + 10);
                

這是相當正常設定代碼,但這裡是怎麼回事:

  1. 設定table的header大小與螢幕相同。你将利用的UITableView的分頁來分隔頁面頁頭和每日每時的天氣預報部分。
  2. 建立inset(或padding)變量,以便您的所有标簽均勻分布并居中。
  3. 建立并初始化為各種視圖建立的高度變量。設定這些值作為常量,使得可以很容易地在需要的時候,配置和更改您的視圖設定。
  4. 使用常量和inset變量,為label和view建立架構。
  5. 複制圖示框,調整它,使文本具有一定的擴充空間,并将其移動到該圖示的右側。當我們把标簽添加到視圖,你會看到布局的效果。

添加如下代碼到 

-viewDidLoad

 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43      
// 1
UIView *header = [[UIView alloc] initWithFrame:headerFrame];
header.backgroundColor = [UIColor clearColor];
self.tableView.tableHeaderView = header;

// 2
// bottom left
UILabel *temperatureLabel = [[UILabel alloc] initWithFrame:temperatureFrame];
temperatureLabel.backgroundColor = [UIColor clearColor];
temperatureLabel.textColor = [UIColor whiteColor];
temperatureLabel.text = @"0°";
temperatureLabel.font = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:120];
[header addSubview:temperatureLabel];

// bottom left
UILabel *hiloLabel = [[UILabel alloc] initWithFrame:hiloFrame];
hiloLabel.backgroundColor = [UIColor clearColor];
hiloLabel.textColor = [UIColor whiteColor];
hiloLabel.text = @"0° / 0°";
hiloLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:28];
[header addSubview:hiloLabel];

// top
UILabel *cityLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, 30)];
cityLabel.backgroundColor = [UIColor clearColor];
cityLabel.textColor = [UIColor whiteColor];
cityLabel.text = @"Loading...";
cityLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
cityLabel.textAlignment = NSTextAlignmentCenter;
[header addSubview:cityLabel];

UILabel *conditionsLabel = [[UILabel alloc] initWithFrame:conditionsFrame];
conditionsLabel.backgroundColor = [UIColor clearColor];
conditionsLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
conditionsLabel.textColor = [UIColor whiteColor];
[header addSubview:conditionsLabel];

// 3
// bottom left
UIImageView *iconView = [[UIImageView alloc] initWithFrame:iconFrame];
iconView.contentMode = UIViewContentModeScaleAspectFit;
iconView.backgroundColor = [UIColor clearColor];
[header addSubview:iconView];

           

這是相當長的一塊代碼,但它真的隻是在做設定各種控件的繁重工作。簡單的說:

  1. 設定目前view為你的table header。
  2. 建構每一個顯示氣象資料的标簽。
  3. 添加一個天氣圖示的圖像視圖。

建構并運作你的App,你應該可以看到你之前布局的所有所有view。下面的螢幕截圖顯示了使用手工布局的、所有标簽框在視覺上的顯示。

iOS7最佳實踐:一個天氣App案例

用手指輕輕推動table,當你滾動它的時候,應該會反彈。

擷取氣象資料

你會注意到,App顯示“Loading…”,但它不是真正地在工作。是時候擷取一些真正的天氣資料。

你會從 OpenWeatherMap 的API拉取資料。 OpenWeatherMap是一個非常棒的服務,旨在提供實時,準确,免費的天氣資料給任何人。雖然有很多天氣API,但他們大多要麼使用較舊的資料格式,如XML,或是有償服務 – 并且有時還相當昂貴。

你會遵循以下基本步驟,來獲你裝置的位置的氣象資料:

  1. 找到裝置的位置
  2. 從 API端 下載下傳JSON資料
  3. 映射JSON到 

    WXConditions

     和 

    WXDailyForecasts

  4. 告訴UI有新資料了

開始建立你的天氣模型和資料管理類。單擊 

File\New\File…

 并選擇

Cocoa Touch\Objective-C class

 。命名為 

WXClient

 并使其為

NSObject

 的子類。

這樣再做三次建立以下類:

  • WXManager

     作為 

    NSObject

     的子類
  • WXCondition

     作為 

    MTLModel

     的子類
  • WXDailyForecast

     作為 

    WXCondition

     的子類

全部完成?現在,你可以開始下一節,其中涉及映射和轉換您的天氣資料。

建立你的天氣模型

你的模型将使用 Mantle ,這使得資料映射和轉型非常簡單。

打開 

WXCondition.h

 如下列代碼,修改接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22      
// 1
@interface WXCondition : MTLModel <MTLJSONSerializing>

// 2
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSNumber *humidity;
@property (nonatomic, strong) NSNumber *temperature;
@property (nonatomic, strong) NSNumber *tempHigh;
@property (nonatomic, strong) NSNumber *tempLow;
@property (nonatomic, strong) NSString *locationName;
@property (nonatomic, strong) NSDate *sunrise;
@property (nonatomic, strong) NSDate *sunset;
@property (nonatomic, strong) NSString *conditionDescription;
@property (nonatomic, strong) NSString *condition;
@property (nonatomic, strong) NSNumber *windBearing;
@property (nonatomic, strong) NSNumber *windSpeed;
@property (nonatomic, strong) NSString *icon;

// 3
- (NSString *)imageName;

@end
                
  1. MTLJSONSerializing

     協定告訴Mantle序列化該對象如何從JSON映射到Objective-C的屬性。
  2. 這些都是你的天氣資料的屬性。你将會使用這些屬性的get set方法,但是當你要擴充App,這是一種很好的方法來通路資料。
  3. 這是一個簡單的輔助方法,從天氣狀況映射到圖像檔案。

建構并運作App。失敗了……

原因是你沒有從你的Cocoapods項目中引入 

Mantle

 。解決方法是,在

WXCondition.h

 中,你需要把 

MTLModel.h

 替換為 

#import <Mantle.h>

 。

現在建構并運作App。成功了。你會看到一些新的警告,但你可以忽略他們。

首先,你需要處理未實作的 

-imageName

 方法。

打開 

WXCondition.m

 ,添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33      
+ (NSDictionary *)imageMap {
    // 1
    static NSDictionary *_imageMap = nil;
    if (! _imageMap) {
        // 2
        _imageMap = @{
                      @"01d" : @"weather-clear",
                      @"02d" : @"weather-few",
                      @"03d" : @"weather-few",
                      @"04d" : @"weather-broken",
                      @"09d" : @"weather-shower",
                      @"10d" : @"weather-rain",
                      @"11d" : @"weather-tstorm",
                      @"13d" : @"weather-snow",
                      @"50d" : @"weather-mist",
                      @"01n" : @"weather-moon",
                      @"02n" : @"weather-few-night",
                      @"03n" : @"weather-few-night",
                      @"04n" : @"weather-broken",
                      @"09n" : @"weather-shower",
                      @"10n" : @"weather-rain-night",
                      @"11n" : @"weather-tstorm",
                      @"13n" : @"weather-snow",
                      @"50n" : @"weather-mist",
                      };
    }
    return _imageMap;
}

// 3
- (NSString *)imageName {
    return [WXCondition imageMap][self.icon];
}

           
  1. 建立一個靜态的NSDictionary,因為WXCondition的每個執行個體都将使用相同的資料映射。
  2. 天氣狀況與圖像檔案的關系(例如“01d”代表“weather-clear.png”)。
  3. 聲明擷取圖像檔案名的公有方法。

看一看從OpenWeatherMap傳回的JSON響應資料:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20      
{
    "dt": 1384279857,
    "id": 5391959,
    "main": {
        "humidity": 69,
        "pressure": 1025,
        "temp": 62.29,
        "temp_max": 69.01,
        "temp_min": 57.2
    },
    "name": "San Francisco",
    "weather": [
        {
            "description": "haze",
            "icon": "50d",
            "id": 721,
            "main": "Haze"
        }
    ]
}

           

你需要把嵌套的JSON值映射到Objective-C的屬性。嵌套的JSON值是元素,如溫度,即上面看到的 

main

 節點。

要做到這一點,你将利用的Objective-C的 Key-Value Coding 和Mantle 的MTLJSONAdapter 。

還在 

WXCondition.m

 ,通過添加 

+JSONKeyPathsByPropertyKey

 方法,“JSON到模型屬性”的映射,且該方法是 

MTLJSONSerializing

 協定的require 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17      
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"date": @"dt",
             @"locationName": @"name",
             @"humidity": @"main.humidity",
             @"temperature": @"main.temp",
             @"tempHigh": @"main.temp_max",
             @"tempLow": @"main.temp_min",
             @"sunrise": @"sys.sunrise",
             @"sunset": @"sys.sunset",
             @"conditionDescription": @"weather.description",
             @"condition": @"weather.main",
             @"icon": @"weather.icon",
             @"windBearing": @"wind.deg",
             @"windSpeed": @"wind.speed"
             };
}

           

在這個方法裡,dictionary的key是 

WXCondition

 的屬性名稱,而dictionary的value是JSON的路徑。

您可能已經注意到,這裡有一個從JSON資料映射到Objective-C屬性的問題。屬性

date

 是 

NSDate

 類型的,但JSON有一個Unix時間類型(sjpsega注:即從1970年1月1日0時0分0秒起至現在的總秒數)的NSInteger值。你需要完成兩者之間的轉換。

Mantle正好有一個功能來為你解決這個問題: MTLValueTransformer 。這個類允許你聲明一個block,詳細說明值的互相轉換。

Mantle的轉換器文法有點怪。要建立一個為一個特定屬性的轉換器,,您可以添加一個以屬性名開頭和 

JSONTransformer

 結尾的類方法。 可能看實際代碼比試圖解釋它更容易了解,是以在 

WXCondition.m

 中添加以下為NSDate屬性設定的轉換器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17      
+ (NSValueTransformer *)dateJSONTransformer {
    // 1
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [NSDate dateWithTimeIntervalSince1970:str.floatValue];
    } reverseBlock:^(NSDate *date) {
        return [NSString stringWithFormat:@"%f",[date timeIntervalSince1970]];
    }];
}

// 2
+ (NSValueTransformer *)sunriseJSONTransformer {
    return [self dateJSONTransformer];
}

+ (NSValueTransformer *)sunsetJSONTransformer {
    return [self dateJSONTransformer];
}

           
  1. 使用blocks做屬性的轉換的工作,并傳回一個MTLValueTransformer傳回值。
  2. 您隻需要詳細說明Unix時間和NSDate之間進行轉換一次,就可以重用

    -dateJSONTransformer

     方法為sunrise和sunset屬性做轉換。

下一個值轉型有點讨厭,但它隻是使用OpenWeatherMap的API,并自己的格式化JSON響應方式的結果。 

weather

 鍵對應的值是一個JSON數組,但你隻關注單一的天氣狀況。

在 

WXCondition.m

 中,使用 

dateJSONTransformer

 相同的結構,您可以建立一個NSArray和NSString的之間的轉換。該解決方案提供如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15      
+ (NSValueTransformer *)conditionDescriptionJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSArray *values) {
        return [values firstObject];
    } reverseBlock:^(NSString *str) {
        return @[str];
    }];
}

+ (NSValueTransformer *)conditionJSONTransformer {
    return [self conditionDescriptionJSONTransformer];
}

+ (NSValueTransformer *)iconJSONTransformer {
    return [self conditionDescriptionJSONTransformer];
}

           

最後的轉換器隻是為了格式化。 OpenWeatherAPI使用每秒/米的風速。由于您的App使用英制系統,你需要将其轉換為每小時/英裡。

在 

WXCondition.m

 的實作中添加以下轉換器的方法和宏定義。

1
2
3
4
5
6
7
8
9      
#define MPS_TO_MPH 2.23694f

+ (NSValueTransformer *)windSpeedJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSNumber *num) {
        return @(num.floatValue*MPS_TO_MPH);
    } reverseBlock:^(NSNumber *speed) {
        return @(speed.floatValue/MPS_TO_MPH);
    }];
}

           

在OpenWeatherMap的API中有一個小的差異,你必須處理。看一看在位于 目前狀況的響應 和 每日預測反應 之間的溫度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20      
// current
"main": {
    "grnd_level": 1021.87,
    "humidity": 64,
    "pressure": 1021.87,
    "sea_level": 1030.6,
    "temp": 58.09,
    "temp_max": 58.09,
    "temp_min": 58.09
}

// daily forecast
"temp": {
    "day": 58.14,
    "eve": 58.14,
    "max": 58.14,
    "min": 57.18,
    "morn": 58.14,
    "night": 57.18
}

           

current

 的第一個key是 

main

 ,最高溫度存儲在key 

temp_max

 中,而

daily forecast

 的第一個key是 

temp

 ,最高溫度存儲在key 

max

 中。

key Temperature的差異放在一邊,其他都一樣。是以,你真正需要做的是修改daily forecasts的鍵映射。

打開 

WXDailyForecast.m

 重寫 

+JSONKeyPathsByPropertyKey

 方法:

1
2
3
4
5
6
7
8
9      
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    // 1
    NSMutableDictionary *paths = [[super JSONKeyPathsByPropertyKey] mutableCopy];
    // 2
    paths[@"tempHigh"] = @"temp.max";
    paths[@"tempLow"] = @"temp.min";
    // 3
    return paths;
}

           
  1. 擷取 

    WXCondition

     的映射,并建立它的可變副本。
  2. 你需要為daily forecast做的是改變max和min鍵映射。
  3. 傳回新的映射。

建構并運作您的App,看起來和上次運作沒什麼改變,但好的一點是,App編譯和運作沒有任何錯誤。

iOS7最佳實踐:一個天氣App案例

何去何從?

你可以從 這裡 下載下傳完整程式。

在這部分教程中,您使用Cocoapods設定項目,增加視圖到控制器,編排視圖,并建立模型來反映你抓取的氣象資料。該App還沒有充分發揮作用,但是你成功用純代碼建立視圖,并學習了如何使用Mantle映射和轉換JSON資料。

接下來看看教程的第二部分,你将充實你的App,從weather API擷取資料,并在UI上顯示。您将使用新的iOS7 NSURLSession 去下載下傳資料,以及使用

ReactiveCocoa

 把位置查找,天氣資料抓取和UI更新事件綁在一起。