2011年10月29日 星期六

Storyboard 第三部曲:Segue & Delegate

我們再來對 MyFirst5 這個 Project 新增新的功能。還記得之前的文章嗎?
About Storyboard
View Controller (NSObject) 之間交換資料 by Segue
最後我們停留的 Storyboard 畫面如下。
右邊的 Done Button 還沒有功效,除此之外再加上一個新的功能,當使用者在 Second 輸入文字後,按下 Done 就會把 Text Field 的文字傳到左邊的 View Controller 的 Label 上,也就是改變了目前看到 Hello Story 的顯示。
不過首先要加上一個很自然的動作,就是按下 Done 按鈕之後會離開 Second 畫面回到 Hello Story 這個畫面,因為Second 這個畫面是按下 Show 之後利用 Segue而呈現出來的,有呈現就有退回,我們就寫在 Done 這個按下的動作裡。
所以我們要加上一個 IBAction 來相對應 Done 按下這個事件,也就是要在 SecondController.m 新加一個 IBAction。名為 doneEditing。如下所示。
在這裡用到新的 method 在 Line 49 名為 dismissViewControllerAnimated:completion: 這個是用來取代之前的 dismissModalViewControllerAnimated: 而且 Apple 的文章也建議之後都用這個不要用 dismissModalViewControllerAnimated: 相對的,呈現的時候也有一個名為
presentViewController:animated:completion: 用來取代之前的 presentModalViewController:animated: 請大家注意。好。執行之。就可以看到按下 Show 和 Done 是個相對應的動作,在 Hello Story 和 Second 畫面之間切換。如下所示。


接著我們要從 Text Field 傳資料到 Hello Story。不過在此時,我們想一想,資料傳遞方向和 Segue 相反,所以在資料的傳送端建立 Protocol 而 資料的接受端就是 delegate 的對像,就要實作傳送端的 Protocol 提到的 method 才可以收到資料
好,我們在 SecondController 建立為了把 Text Field 的資料傳送出去的 Protocol 如下表示。在 SecondController.h。
這個Protocol名為 SecondDelegate 寫在  Line 10。再準備一個 delegate property,這個變數所指的對像就是要來收到 Text Field 上面資料的。型別就是 id<SecondDelegate> 意思就是任意的 Class 但要實作 SecondDelegate 所宣告的 method。那 SecondDelegate 這個 Protocol 的主體就在 Line 17 ~ Line 21 。唯一的一個 method 名為 second:inputString: 也就是說,要收到 Text Field 的資料的物件就要實作這個 method。那在什麼時候傳過去 delegate 的物件呢?就在 Done 按下去的時候,所以要在 doneEditing 這個 method 要再新增傳資料給  delegate 所指的物件。於是如下更改。

從 line 49 到 line 52 是新增部分,就是確定 self.delegate 有實作 second:inputString: 然後就直接呼叫了。Good,到目前為止就是傳送端準備好了,定義好 Protocol 準備好 Delegate 來使用,接下來看接收端,左邊的 HelloController 的實體,也就是 HelloController.m 要實作 second:inputString: 但我們首先在 HelloController.h 提示一下 Compiler 要實作 SecondDelegate 這個 Protocol 如下所示。
比較要注意的是在 Line 10 要 import SecondController.h 因為 Protocol 在這邊宣告,然後在 Line 11 多了 <SecondDelegate> 這個就是和 Compiler 說 .m 要實作其 method 哦。這樣 Compiler 才不會給警告。ok 在 HelloController.m 要加上 second:inputString: 的實作。如下。
很簡單就是收到 inputString 之後去更改  helloLaebel 的 text 。到此,無論是傳送端或是接收端,行為都在 Class 定義好了,執行的時候物件就會依這些定義行為。但是還有一件事情還沒做,就是 HelloController 的物件,和 SecondController 的物件還沒有關係?怎麼說?剛剛做的事情就是定義 method,忽略了,SecondController.h 不是有新增一個 delgate 變數嗎?什麼時候給值,這個變數要指定到正確的物件,值才會傳過去,那要寫在那?指定給誰?指定給 HelloController 的物件,這個比較明確,問題比較大的是寫在那?
這個問題有了 Segue 會比較明確,還記得之前一篇,從 HelloController 實體傳資料給 SecondController 實體的程式碼寫在那呢?寫在 HelloController.m 的 -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {} 嘛,所以要把這兩個物件實體的關係綁在一起的程式碼就寫在這,因為在這可以同時存取到兩個物件實體。如下所示。
就只多了 Line 64 把 self 也就是 HelloController 的實體指定給 secondCon 的 delegate property 了。直接執行就會看到如下。
好了。三部曲告一段落,希望大家多懂一點 Storyboard,對大家的工作和興趣有幫助。

2011年10月28日 星期五

View Controller (NSObject) 之間交換資料 by Segue

延續上一篇有關 Storyboard 的簡介,現在我們要來介紹在 Storyboard 中看到的新角色 Segue。
首先我們先來秀一下 Segue 的威力。在 MyFirst5 的專案中,再新增一個 View Controller 這樣總共有兩個 View Controller 然後各在其上拉一個 UILabel 做為識別,並在 Initial View Controller 的 View 上方,放置一個 UIButton 如下圖所示。
接下來我們要做一件很直覺的事情,就是按著 Ctrl 然後從 Hello Story 的 Button Click Drag 到右邊的 Second 畫面。放開之後你會看到如下的選擇畫面。選 Modal
接著會看到新的角色出現。Segue 。如下圖
 Segue 查字典是指動詞,流暢地轉換。也可以想像它是一個過場效果。OK 就這樣。直接執行。然後按下 Show Button 你將會看到 Magic 產生。從下方的畫面。
 產生如下的畫面,其過場動畫就是 Modal View Controller 呈現的效果。
產生這個 Modal View 我們沒有寫任何的程式碼,這個就是 Segue 迷人的地方。接著要更進一步來利用 Segue. 在 MyStory.storyboard 在 Second 新增一個 Text Field 和 Button 如下畫面。
接著我想要把左邊的 Label : Hello Story 這個字樣,傳到右邊的 Text Field 上面,時間點就是按下 Show 這個按鈕的時候。不過,這個時候有一個大問題,產生這兩個 View Controller 的物件是由 Storyboard 產生的而且他們的 Class 是 UIViewController,換句話說,我們沒辦法去更動 UIViewController 內容,因為我們沒有 UIViewController.m 的原始碼。但我們可以繼承它,然後把上面兩個 View Controller 的 Class 都改成我們新增的 Class。新先增兩個 Class 都繼承自 UIViewController 各命名為 HelloController 和 SecondController。首先新增 HelloController 如下的設定

不需要產生 Xib 檔,因為 Storyboard 就提供了和 Xib 檔相當的功能。其實 Stroyboard 就是很多個 Xib 檔的組合。成功產生之後會在 Navigator 看到。如下的畫面。
然後點選 Storyboard 的 Initial View Controller 的 View Controller 圖形,把它的 Class 改成 HelloController。如下圖
相同的做法產生 SecondController 如下圖。
接著也改掉 Storyboard 中 Second 的 View Controller 圖示的 Class 成 SecondController. 如下圖。
OK 材料都準備齊了。再來思考剛剛的問題。我們要從左邊 View Controller 傳資料給右邊的 View Controller。也就是由 HelloController 的實體,傳給 SecondController 的實體,傳遞的方向和 Segue 一樣,我們要在 SecondController (接受端) 新增 propery 來接 HelloController (傳送端) 傳來的資料。於是我們在 SecondController.h 新增 property dataString 而且要新增一個 IBOutlet 用來指向 Storyboard 上 SecondController 畫面上的 Text Field 實體。如下所示。
 記得各都要有 @Synthesize 在 .m 。然後上方的小圈圈中間有個黑實圈表示和 Storyboard 的圖示連在一起了。接著我們要思考一件事,在這我們用  dataString 來接受資料,要在什麼時候放到 inputField 上面呢?在 viewDidLoad。所以在 SecondController.m 的 viewDidLoad 如下表示。

現在接受端準備好了,傳送端要如何傳送呢?直覺的想就是程式碼要寫在 HelloController 裡。的哪?介紹一個新的 method。
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
這個method 會在 segue 在呼叫起 View Controller 之前被呼叫,我們來看看要寫什麼?
在 line 60,我們可以用 segue 的  destinationViewController 就可以拿到執行中 SecondController 的實體。然後把 @"From Hello" 傳給 SecondController 的 property dataString 。執行之後,按下按鈕,你會看到如下畫面。
From Hello 傳到 Text Field 上面了。Good。但好像不是我們要的。我們想把 Hello Story 這個 UILabel 傳到 Text Field 上,於是在 HelloController.h 要新增 IBOutlet UILabel * helloLabel。然後 prepareForSegue:sender: 改成如下。
在 Line 63 我們看到了 helloLabel.text 傳到 SecondController 實體的 Text Field 。執行之後就是如下。

恭喜大家。到此還沒介紹如何用到 Done 這個按鈕,還有我們接下來要反過來從 SecondController 實體傳資料給  HelloController 的實體。請期待下一篇。

2011年10月27日 星期四

About Storyboard

這篇的主題主要是來討論在 Xcode 4.2 中新的功能或是概念。有一個新的檔名,Storyboard。看名字就知道這個檔要表達的,就是一個故事,就是 App 要說的事情,換句話說,看了這個檔的內容大概就可以掌握整個 App 想要做的事。OK。
由於我個人喜歡從簡單到難,所以接下來的例子,就從一個最簡單的 Template 開始:Empty Application.
如下畫面,在開啟新專案的時候選擇 Empty Application。

就命名為 MyFirst5,記得勾選 Use Automatic Reference Counting 如下所示.
存完檔之後呢,會發現。竟然沒有 Storyboard 檔,果然是 Empty。
如果這個時候比較心急的朋友,直接 Run。當然是看到如下的畫面
好吧,我們來新增自己的 Storyboard。新增 File,然後選擇 User Interface -> Storyboard

命名為 MyStory 好啦。在專案裡就會出現這個 Storyboard 檔。
而且在右側的編輯區當然也是什麼東西都沒有。第一步就是加入一個 View Controller。從 Inspector Library 拉一個 View Controller 到 MyStory.storyboard。
首先你會看到一個白色的畫面和一個向右的箭號。
 
因為在 Storyboard 上面可以放置很多個 View Controller。要有這個箭號表示,Storyboard 第一個要執行的 View Controller。接著我們要在這個 View Controller 白色的 View 上面放上一個 Label。從 Library 拉一個 Label放到上面。如下圖所示。
所以我們預期執行的時候可以呈現上面那張圖。但是在此時執行的結果,如下圖。還是白白一張。
最目前為止?還少了什麼呢?想想。剛剛新增了 Storyboard,但是還沒有和 Xcode 說,也就是說沒有設定在這個專案的設定檔裡。專案的設定檔都是  info.plist。在這個專案就是。MyFirst5-info.plist 如下圖可以找到。
在其內容設定裡新增一個設定,Main storyboard file base name 寫上 MyStory 如下圖所示。
寫上Storyboard 名稱,再執行。發現。還是白白的一張。
 好吧,該設定的都設定了,還少了什麼嗎?其實還是少了去檢查,執行的時候會去做的事。也就是說到目前為止,都是靜態的設定,執行的時候會先去執行什麼 method 呢?答案就是 xxxAppDelegate.m 裡的application:didFinishLaunchingOptions:。看看裡面的內容。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
     self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
     self.window.backgroundColor = [UIColor whiteColor];
     [self.window makeKeyAndVisible];
return YES;
}
原在這個 Method 裡 self.window 又被改掉,當然就無法顯示出 Storyboard 加的 Label。建議就直接 Mark / Comment 如下所示:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
再執行就可以看到如下的畫面了。

2011年6月27日 星期一

iOS App 開發者的自我修練,幾個問題。

Facebook Page : Developer's Note
Objectice-C Language.

obj.name = @"Michael"; 這行程式碼會等同於那個 method 呼叫?是不是一定要定義 property ?

多個 autorelease pool 產生時,把任一個物件加入其中一個 pool,再把別的 pool drain 時會有什麼情況發生?

什麼是 Retain Cycle ? 遇到這個情形要如何應對?

Handler<NetMessage> * delegate; 請解釋 delegate 這個變數所指向的實體的 Class 要有什麼樣的限制?

自定的 Class 要支援 copy 這個 method 需要實作那個 method ? 以 Engine 這個 Class 為例,寫出其支援 copy method 的 實作內容。

請解釋 dispatch_queue_t 是什麼意思?會在什麼情況下被使用?

iOS App

解釋從 main() 到 application:didFinishLaunchingWithOptions: 的流程。

請解釋當使用者按下某個 UIView 時,Touch Event 會如何被處理 ? 請考慮被按下的 UIView 有實作 touchesBegan:withEvent: 和沒有實作 touchesBegan:withEvent: 兩種情況。

解釋  viewDidLoad,  viewWillAppear:,  viewDidUnload 各是在什麼情況下會被自動呼叫?

利用 UIScrollView 去呈現大量畫面的時候,應該要注意什麼事情?假設有 100 圖要被加入 UIScrollView 而一次只呈現一張圖,讓使用者用 scroll 方式換圖。

利用 UITableView 呈現資料時,其中要 UITableViewCell 的部分要加上自己的 UIView (UIButton, UILabel, etc...) ,應該要寫在那一個 method 裡的那一段?

請解釋 Managed Object, 和 Database 的關係。

假設在 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 裡要利用遠端的 url 更新 cell.imageView.image 有如下的程式碼


[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[imagePrefix stringByAppendingPathComponent:[icons objectAtIndex:indexPath.row]]]]];

如此一來會發生什麼問題?請用 GCD 的寫法改善。

2011年5月3日 星期二

NSURLConnection over HTTPS

這篇文章是要來教大家,怎麼和 HTTPS 的 Server 溝通。
在寫文章之前要先提醒一下大家,以下的教學目的是不論如何都要去連 HTTPS Server
正常的情況下是會先輸入帳號密碼,而且檢查 Server 的憑證是不是合法之類的
即使不用輸入帳號密碼,如果憑證有可疑,通常 Browser 也會跳出一個視窗來提醒使用者,如下
Firefox 會看到
Safari 會看到


以下的文章是在不用輸入帳號密碼而且不檢查 Server 的憑證的清況下
簡單利用 NSURLConnection 就可以了


- (void)viewDidLoad
{
    [super viewDidLoad];
    NSString * httpsAddress = @"https://allseeing-i.com";
    NSURLConnection * urlCon = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:httpsAddress]] delegate:self];
}

// 一般的 connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"got data %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"error");
}

// 無論如何回傳 YES
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
    return YES;
}

// 不管那一種 challenge 都相信
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
    NSLog(@"received authen challenge");
    [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

如果是用上面的例子 https://allseeing-i.com ,應該會在 Console 看到網頁的 HTML 源始碼。太長了就不貼上,有空的朋友可以試試不同的 https server


2011年4月30日 星期六

Build Static Library in Xcode 4

這篇文章是要來教大家用 Xcode 4 產生 .a 檔,所謂的 Static Library。
用途就是把自己寫好的程式不想要給對方看到 source code 包成 .a 再加上 .h 這樣一樣對方可以正常使用自己寫好的程式,而且看不到自己寫的 .m 檔。
在這個文章要建立兩個專案,一個是用來產生 .a ,另一個用來測試 .a 是不是成功產生。
LibTest - 這個專案用來產生 .a ( Xcode 自動會產生一個名為 libLibTest.a 的檔案)
UsageTest - 這個專案用來測試 LibTest 產生的 .a
讓我們一步一步來進行
首先開啟一個 Static Library 的專案,如下
然後命名為 LibTest 會在導覽列看到如下

LibTest 這個 Group 裡什麼都沒有
接著我們就要新增一個 Class 打算把這個 Class 的 .m 包成 .a
如下方式新增一個 File
然後新增一個 NSObject 的子類別命名為 LibTest,之後會在導覽列看到。
LibTest.h 和 LibTest.m。之後呢,新增一個 method 在 .h 和其實作在 .m
#import <Foundation/Foundation.h> 
@interface LibTest : NSObject { 
-(void) greeting:(NSString *) message; 
@end
上面是 LibTest.h,下方是 LibTest.m


#import "LibTest.h" 
@implementation LibTest 

-(void) greeting:(NSString *) message{ 
    NSLog(@"Hello %@", message); 
@end
打完之後存檔,接下來要產生 .a 檔了。在產生之前先確定一下 configuration
目的就是要確定這個 scheme 跑的是 release 而不是 debug。確定之後。在 Product -> Build 按下之後就會 Build 這個 Target 然後會產生 .a 如下圖來 Build
重點來了,.a 檔會放在那?Xcode 4 會放在 ~/Library/Developer/Xocde/DerivedData/ 的再接下圖
也就是  ~/Library/Developer/Xocde/DerivedData/<Product Name>/Build/Products/Release-iphonesimulator/ 底下會有一個 .a 如上圖,要記得哦,是 Release 如果是 Debug 開頭,我測過是不能被用的。好記得就好。然後要把這個檔案和 .h 一起放到等一下要開啟的 UsageTest 專案裡。
簡單開始一個 Window-based Application ,Product Name 命名為 UsageTest 就可以了。如下圖
接者把上一個專案產生的 libLibTest.a 和 LibTest.h 一起 copy 到這個專案。如下圖。
然後在 UsageTestAppDelegate.m 的 application:didFinishLaunchingWithOptions: 填上如下的程式碼。


#import "UsageTestAppDelegate.h" 
#import "LibTest.h" 
@implementation UsageTestAppDelegate 
@synthesize window=_window; 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    LibTest * libObj = [LibTest new]; 
    [libObj greeting:@"Michael"]; 
[self.window makeKeyAndVisible]; 
return YES; 
// 省略 

}
沒意外的話,Run 就會看到如下成功的畫面。只有 .a 和 .h 就可以成功執行了。