iOS每年推陳出新,不斷地冒出各式各樣新鮮有趣的新類別,新method,讓我們可以天馬行空地發揮想像力,設計更多令人耳目一新的App。但是,世界並沒有想像中美好,不可能每個人的iPhone都升級到最新的iOS。因此,我們還是要考慮到這些舊版iOS使用者的相容性。
以最新推出的iOS 6為例,MKMapItem是個全新的類別,提供我們更方便的地圖應用可能。但是舊版的iOS就不支援了,因此我們在設計App時,最好能夠聰明地判斷,當使用者機器為iOS 6時才使用MKMapItem, 當機器為舊版的iOS時則採用另外的配套方案。
判斷的方法很簡單,只要以下2行程式碼:
Class itemClass = [MKMapItem class];
if([itemClass respondsToSelector:@selector(openMapsWithItems:launchOptions:)])
{
}
說明:
不管使用者的機器是否為iOS 6,都可以建立MKMapItem類別物件。但只有iOS 6機器所建立的,才真正具有執行MKMapItem所定義method的能力。因此只有在iOS 6,以上的if敘述才會成立。
2012年9月29日 星期六
給我一首歌的時間,3.5和4吋螢幕一網打盡
改頭換面的舊專案 ( 3.5 -> 4 ):
原先針對3.5-inch設計的App,當直接於iPhone 5上運行時,仍然可以運行無阻。此時App運行時將進入相容模式,App的畫面置中,維持原先3.5-inch時的高度,但上下各多出了一塊黑邊,此黑邊即是4-inch畫面所多出的高度。
想要移除破壞美觀的黑邊很簡單,只要設定4-inch的Launch Image,即可表明App在4-inch的環境下可以完全發揮,不用委屈地進入相容模式。
執行App:
重情念舊的新專案 ( 4 -> 3.5 ) :
1. 建立Tabbed Application專案。
2. 將Development Target設為5.0,如此才能至少支援iPhone 5以上的機器。
3. 關閉Autolayout
Autolayout是iOS 6才支援的酷炫功能,為了相容iOS 5,我們只好忍痛割除它。請編輯MainStoryboard.storyboard,切換至File Inspector頁面,將原本勾選的Use Autolayout取消。
4. 於第一個tab的view上加入佔滿畫面的ImageView,Mode設為Aspect Fill,並以彼得潘曾經買過的可愛猴子娃娃為主角。
執行App:
如圖所示,不管是在3.5還是4-inch畫面,tab bar都完美地對齊下方,唯一的差別只在於猴子顯示的範圍。在比較高瘦的4-inch畫面上,最近吃太多需要瘦身的猴子只好犧牲一點,無法露出其完整的腳丫子。
也許有人覺得奇怪,一開始新專案裡預設的Storyboard顯示的是4-inch的畫面,因此我們加入的ImageView其實比較長,為何在3.5-inch iPhone執行時它會聰明地自動縮小呢? 其實這裡頭沒有神奇的魔法,這全是容納ImageView的View的功勞。如下圖所示,我們看到View的Autoresize Subviews被勾選,因此ImageView將跟著View的大小而縮放。
若是我們調皮地將Autoresize Subviews的勾選拿掉,在3.5-inch iPhone執行的畫面將變這樣。
實在是太可憐了,由於ImageView還是維持4-inch時的高度,所以在3.5-inch的畫面上,我們完全看不到猴子的腳丫子了。
2012年9月28日 星期五
黃金比例的iPhone 5全螢幕圖片
iPhone長高了,iPhone 5的螢幕尺寸變為接近16:9的黃金比例,若以pixel為單位,其從原先的640 * 960 成長為 640 * 1136。由於寬度不變,只有高度增長了,因此對於App來講最主要的改變其實在於畫面上一次可以看到的東西變多了,尤其對於像表格這裡元件,顯示範圍甚至可能從五行成長為七行。
對於App開發者,影響最大的莫過於佔滿螢幕的圖片。想要成為最新,最潮,支援iPhone 5的App,關於全螢幕圖片的調整,其實很簡單,只要以下2個步驟:
1. 開頭畫面圖片:
針對4-inch螢幕,使用 640 * 1136的png。值得注意的,for 3.5-inch Retina的Launch Image被命名為Default@2x.png,而for 4-inch Retina則被命名為Default-568h@2x.png。
如圖所示,我們看到Retina的Launch Images,區分為3.5和4兩種。
執行App:
人人平等,不管是4-inch還是3.5-inch,都可以看到Penny又在家裡唱歌演唱會的宣傳圖片。
2. App裡的背景圖片:
App裡的背景圖片並不像Launch Image那麼聰明,可以由檔名判斷顯示的圖片。因此我們必須從程式裡自己做判斷。
CGSize size = [UIScreen mainScreen].bounds.size;
說明:
取得螢幕的尺寸,當為3.5吋的Retina時,size為320*480。當為4吋的Retina時,size則為320*568。因此我可以利用回傳size的高度決定顯示的圖片。
對於App開發者,影響最大的莫過於佔滿螢幕的圖片。想要成為最新,最潮,支援iPhone 5的App,關於全螢幕圖片的調整,其實很簡單,只要以下2個步驟:
1. 開頭畫面圖片:
針對4-inch螢幕,使用 640 * 1136的png。值得注意的,for 3.5-inch Retina的Launch Image被命名為Default@2x.png,而for 4-inch Retina則被命名為Default-568h@2x.png。
如圖所示,我們看到Retina的Launch Images,區分為3.5和4兩種。
執行App:
人人平等,不管是4-inch還是3.5-inch,都可以看到Penny又在家裡唱歌演唱會的宣傳圖片。
2. App裡的背景圖片:
App裡的背景圖片並不像Launch Image那麼聰明,可以由檔名判斷顯示的圖片。因此我們必須從程式裡自己做判斷。
CGSize size = [UIScreen mainScreen].bounds.size;
說明:
取得螢幕的尺寸,當為3.5吋的Retina時,size為320*480。當為4吋的Retina時,size則為320*568。因此我可以利用回傳size的高度決定顯示的圖片。
2012年9月27日 星期四
iOS 6裡被遺忘的viewDidUnload - 由來只見新人笑,有誰聽到舊人哭
在最新的iOS 6裡,有新加入,被大力宣傳和寵愛的新類別和新method。但由來只見新人笑,有誰聽到舊人哭。曾經在UIViewController佔舉足輕重地位的viewDidUnload method不再被呼叫了。過去當記憶體不足時,view controller的didReceiveMemoryWarning將被呼叫,接著為了節省記憶體空間,當其view被移除後,viewDidUnload將被呼叫。
接下來彼得潘實際建立一個簡單的Tab App Test來說明,感謝大畫家朋友張友鷦可愛圖片的友情提供。此App的第一個tab將顯示相愛的甜蜜氣氛,第二個tab則顯示親吻月亮的貓頭鷹。
為了驗證相關method的呼叫,我們將TestFirstViewController.m修改如下:
- (void)viewDidLoad
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewDidUnload
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super viewDidUnload];
}
- (void)didReceiveMemoryWarning
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
最後,最重要的,如果我們想要三天三夜不睡覺寫出的App可以同時支援iOS 5和iOS 6,要怎麼做呢?
很簡單,將didReceiveMemoryWarning method定義成剛剛的範例,即可同時支援iOS 5和iOS 6。因為在iOS 5時,觸發記憶體不足後,印出的Log如下:
2012-09-28 19:15:15.703 TestTab[40899:c07] -[TestTabFirstViewController viewDidLoad]
2012-09-28 19:15:20.817 TestTab[40899:c07] Received memory warning.
2012-09-28 19:15:20.818 TestTab[40899:c07] -[TestTabFirstViewController didReceiveMemoryWarning]
2012-09-28 19:15:20.826 TestTab[40899:c07] -[TestTabFirstViewController viewDidUnload]
由Log我們得知, didReceiveMemoryWarning裡if的判斷沒有成立。這是因為在iOS 5當呼叫[super didReceiveMemoryWarning]後,view即會被移除,接著viewDidUnload將被呼叫。等到viewDidUnload執行完後,才會繼續執行didReceiveMemoryWarning裡剩下的程式碼。因此當isViewLoaded被呼叫時,此時view已經被unload,所以isViewLoaded將回傳No。
接下來彼得潘實際建立一個簡單的Tab App Test來說明,感謝大畫家朋友張友鷦可愛圖片的友情提供。此App的第一個tab將顯示相愛的甜蜜氣氛,第二個tab則顯示親吻月亮的貓頭鷹。
為了驗證相關method的呼叫,我們將TestFirstViewController.m修改如下:
- (void)viewDidLoad
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewDidUnload
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super viewDidUnload];
}
- (void)didReceiveMemoryWarning
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
除此之外,為了確定當記憶體不足時,不在畫面上的UI元件真的被狠心地移除,我們將在TestFirstViewController上的圖片類別設為自訂的MyImageView類別( 繼承UIImageView),並於MyImageView.m裡定義dealloc method。
-(void)dealloc
{
NSLog(@"%s", __PRETTY_FUNCTION__);
}
好了,大功告成,好戲要上演了。先讓我們回到過去,看看iOS 5的表現吧。
iOS 5:
一開始先顯示第1個tab的畫面,此時viewDidLoad被呼叫,log如下。
2012-09-28 00:56:51.808 Test[10727:c07] -[TestFirstViewController viewDidLoad]
切換到第2個tab,並從模擬器觸發記憶體不足情況。(Hardware -> Simulate Memory Warning)
此時log如下:
2012-09-28 00:57:57.826 Test[10727:c07] Received memory warning.
2012-09-28 00:57:57.828 Test[10727:c07] -[TestFirstViewController didReceiveMemoryWarning]
2012-09-28 00:57:57.828 Test[10727:c07] -[MyImageView dealloc]
2012-09-28 00:57:57.829 Test[10727:c07] -[TestFirstViewController viewDidUnload]
根據Log,我們觀察到當didReceiveMemoryWarning呼叫後,不在畫面上的第1個tab的view果然被狠心移除了,因為view上圖片被移除的log -[MyImageView dealloc]鐵證如山般印在log上。而當view整個被移除後,viewDidUnload果然跟著被呼叫了。
要是此時我們再回到第1個tab,由於此tab的view已被移除,它需要重新載入畫面,因此viewDidLoad再度被呼叫。
2012-09-28 01:05:32.063 Test[10727:c07] -[TestFirstViewController viewDidLoad]
但在iOS 6情況就完全不同了,讓我們趕緊接著看下去。
iOS 6:
照著跟剛剛一模一樣的步驟操作,卻發現印出的log精簡許多。
2012-09-28 01:07:35.275 Test[11146:c07] -[TestFirstViewController viewDidLoad]
2012-09-28 01:07:49.983 Test[11146:c07] Received memory warning.
2012-09-28 01:07:49.984 Test[11146:c07] -[TestFirstViewController didReceiveMemoryWarning]
當我們觸發記憶體不足時,didReceiveMemoryWarning如期地被呼叫,當接著呢? 什麼事都沒發生,第一個tab上的view沒被移除,viewDidUnload也不再被呼叫。這是因為在iOS 6系統給我們更高的自主權,讓我們自己決定記憶體不足時要做什麼。如果想要像從前一樣在記憶體不足時移除UI元件節省記憶體,也是可以,只要將TestFirstViewController的 didReceiveMemoryWarning修改如下。
- (void)didReceiveMemoryWarning
{
NSLog(@"%s", __PRETTY_FUNCTION__);
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
if([self isViewLoaded] && self.view.window == nil)
{
NSLog(@"remove view");
self.view = nil;
}
}
說明:
利用isViewLoaded判斷view是否已經load,self.view.window判斷view是否目前不在畫面上,如果這兩個條件都成立,就可以放心大膽地移除view了。(將self.view設為nil)
再次執行:
2012-09-28 01:26:14.369 Test[11924:c07] -[TestFirstViewController viewDidLoad]
2012-09-28 01:26:19.026 Test[11924:c07] Received memory warning.
2012-09-28 01:26:19.028 Test[11924:c07] -[TestFirstViewController didReceiveMemoryWarning]
2012-09-28 01:26:19.029 Test[11924:c07] remove view
2012-09-28 01:26:19.029 Test[11924:c07] -[MyImageView dealloc]
觸發記憶體不足後,由於我們在didReceiveMemoryWarning裡將self.view設為nil,所以第1個tab上的view順利被殺掉了,其上的圖片也一併被解決了。
當我們再次切換回第一個tab時,viewDidLoad也像從前一樣,再一次被呼叫了。
2012-09-28 01:28:14.768 Test[11924:c07] -[TestFirstViewController viewDidLoad]
最後,最重要的,如果我們想要三天三夜不睡覺寫出的App可以同時支援iOS 5和iOS 6,要怎麼做呢?
很簡單,將didReceiveMemoryWarning method定義成剛剛的範例,即可同時支援iOS 5和iOS 6。因為在iOS 5時,觸發記憶體不足後,印出的Log如下:
2012-09-28 19:15:15.703 TestTab[40899:c07] -[TestTabFirstViewController viewDidLoad]
2012-09-28 19:15:20.817 TestTab[40899:c07] Received memory warning.
2012-09-28 19:15:20.818 TestTab[40899:c07] -[TestTabFirstViewController didReceiveMemoryWarning]
2012-09-28 19:15:20.826 TestTab[40899:c07] -[TestTabFirstViewController viewDidUnload]
由Log我們得知, didReceiveMemoryWarning裡if的判斷沒有成立。這是因為在iOS 5當呼叫[super didReceiveMemoryWarning]後,view即會被移除,接著viewDidUnload將被呼叫。等到viewDidUnload執行完後,才會繼續執行didReceiveMemoryWarning裡剩下的程式碼。因此當isViewLoaded被呼叫時,此時view已經被unload,所以isViewLoaded將回傳No。
除此之外,若是想在view被移除時做一些特定的工作,也請記得在iOS 5時要寫在viewDidUnload裡,在iOS 6則寫在didReceiveMemoryWarning裡的self.view = nil;之後。
2012年9月25日 星期二
永不熄滅的iPhone螢幕
天冷了聰明的人們懂的加件外套,同樣的,iPhone當然也懂得自我保護。為了省電,每隔一段時間它會自動地熄燈睡覺休息,熄滅螢幕,進入鎖定模式。從設定App -> 一般 -> 自動鎖定裡,我們可以設定進入自動鎖定的時間,從最短的一分鐘到永不休息的"永不"。
假如說彼得潘中了樂透,當然可以豪氣地設為"永不",讓iPhone一直亮著跟別人炫耀,因為早已事先準備好一百個行動電源。但可惜彼得潘只中過200,所以也許退而求其次,彼得潘只想要顯示與偶像Penny合照的App"彼得潘和Penny"執行時螢幕一直亮著,但執行其它App時遵從自動鎖定的設定,這小小的心願有可能實現嗎?
當然可以,只要在程式裡加入以下短短兩行程式碼即可。
UIApplication *app = [UIApplication sharedApplication];
app.idleTimerDisabled = YES;
說明:
將UIApplication物件的idleTimerDisabled設為YES,即可防止iPhone睡著。終於,彼得潘可以從早到晚,一直盯和偶像Penny的合照了
2012年9月18日 星期二
Android開發環境準備
1. 安裝Eclipse
http://www.eclipse.org/downloads/
下載並安裝Eclipse for Mobile Developers
2. 啟動Eclipse
3. 點選Help -> Install New Software
5. 輸入ADT的名稱和下載位置
Name : ADT Plugin
Location: https://dl-ssl.google.com/android/eclipse/
6. 在安裝的清單裡,勾選Developer Tools,然後再點選Next。
只是要開發App了話,不需要NDK Plugins來湊熱鬧。
勾選Accept All,全部安裝吧。
http://www.eclipse.org/downloads/
下載並安裝Eclipse for Mobile Developers
2. 啟動Eclipse
3. 點選Help -> Install New Software
4. 點選右邊的Add按鈕。
5. 輸入ADT的名稱和下載位置
Name : ADT Plugin
Location: https://dl-ssl.google.com/android/eclipse/
6. 在安裝的清單裡,勾選Developer Tools,然後再點選Next。
只是要開發App了話,不需要NDK Plugins來湊熱鬧。
7. 點選Next。
8. 勾選同意License後,點選Finish,開始安裝。
過程中若出現Security Warning,請不要害怕,勇敢地按下Ok吧。
9. 完成安裝,按下Yes重啟Eclipse。
10. 重啟後將看到SDK安裝的視窗。
請同時勾選Install latest SDK和目前仍然很多人使用的舊版2.2 SDK。
勾選是否同意回傳報告給Google。
勾選Accept All,全部安裝吧。
11. 設定PATH
export PATH=/usr/local/bin:$PATH:/Users/peterpan/android-sdks/tools:/Users/peterpan/android-sdks/platform-tools
2012年9月17日 星期一
只要有iPhone,人人都可以是攝影大師 - iOS 6的全景拍照
iOS 6支援全景拍照,而且不只iPhone 5獨享唷,連iPhone 4S也擁有此非凡能力。接下來,就讓彼得潘手上的iPhone 4S,為各位示範拍攝愛車的全景照片吧。不過可惜彼得潘不是有錢的蝙蝠俠布魯斯韋恩,所以拍攝的不是高級跑車,只是小小的單車啦。
1. 打開相機App,點選"選項"按鈕,再選擇"全景模式"
2. 進入傻瓜全景拍照模式。只要持續移動iPhone,即可完成全景照片。
3. 大功告成 ! 只要有iPhone,人人都可以是攝影大師 。
2012年9月16日 星期日
致命的Autolayout
當我們興高采烈地採用最新的Xcode 4.5,搭配最新的iOS 6 SDK,建立新專案開始開發時,一旦在iOS 5的機器上執行,卻馬上crash,死前遺言如下:
從遺言我們得知問題出在NSLayoutConstraint上,這是因為它是在iOS 6才冒出的類別
NS_CLASS_AVAILABLE_IOS(6_0)
@interface NSLayoutConstraint : NSObject
讓我們回頭看看專案的storyboard,我們可發現在View下多了很多設定排版的Constraint。
切換到storyboard的File Inspector頁面,我們可發現Use Autolayout被勾選。
Autolayout是iOS 6才加入的排版機制,利用剛剛畫面上看到的Constraint,排列畫面上的元件。 為了能相容iOS 5以下的版本,我們只要取消此勾選,移除Autolayout,即可解決App crash的問題。
2012年9月15日 星期六
2012年9月12日 星期三
找不到Xcode的xcrun
在將Storyboard做Localization時,我們需要於Terminal輸入以下指令,將精心翻譯的語系版本文字輸入到Storyboard的畫面上。
ibtool --strings-file MainStoryboard.strings --write MainStoryboard.storyboard ../en.lproj/MainStoryboa.storyboard
ibtool --strings-file MainStoryboard.strings --write MainStoryboard.storyboard ../en.lproj/MainStoryboa.storyboard
此指令將用到xcrun,而xcrun必須找到一個存在的Xcode路徑。當我們進行多次Xcode的安裝,移除動作後,可能發生xcrun找不到Xcode的問題,出現以下錯誤訊息
xcrun: Error: could not stat active Xcode path '/Applications/Xcode45-DP4.app/Contents/Developer'. (No such file or directory)
這是因為xcrum找尋的Xcode45-DP4.app已經不存在,那是舊的路徑。假設目前Xcode位在/Applications/Xcode.app,只要輸入以下指令,即可解決問題
sudo xcode-select -switch /Applications/Xcode.app
2012年9月7日 星期五
不斷進化的Objective-C (MacToday 2012.9)
隨著Xcode 4.4的推出, Objective-C也有了大幅的進步。其實偷懶是人類的天性,科技的進步正是朝著偷懶的目標前進。接下來彼得潘將介紹的Objective-C三大新功能,完全實現了彼得潘不為人知的偷懶欲望,我們可以打愈少的字,犯更少的錯,開發更多的App,賺更多的錢,環遊世界玩更多的國家!
一. 自動補上@synthesize
@property和@synthesize的發明大大減輕了我們手指打字的疼痛程度,讓我們不用再千辛萬苦打下落落長存取物件member的setter和getter。然而所謂懶還要更懶,可以開車就不想走路,可以坐車就不想開車。不斷地打下member相對應的@property和@synthesize,也是很辛苦的。讓我們看看以下這個例子。
假設我們自訂一個蝙蝠俠類別,BatMan。舊時代的寫法如下:
BatMan的宣告 (BatMan.h) :
#import <Foundation/Foundation.h>
@interface BatMan : NSObject
{
NSString *name;
NSString *gender;
}
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *gender;
@end
BatMan的定義 (BatMan.m) :
#import "BatMan.h"
@implementation BatMan
@synthesize name;
@synthesize gender;
@end
今天幸好BatMan只有名字和性別兩種屬性,若是我們連它的眼睛顏色,是否禿頭等各式小細節都想設定,當屬性愈多,我們需要耐下性子輸入的@synthesize也愈多。以剛剛的例子為例,name短短4個英文字組成的單字,仿佛怕我們遺忘它般一連出現三次,一次在member的宣告,一次在@property,一次在@synthesize。如果有一天,我們不用同樣的單字輸入三次,省下三分之二的時間去約會,該有多好! 如果不再是如果,讓我們看看以下這個超簡單Bat Man偷懶版。
BatMan的宣告 (BatMan.h) :
#import <Foundation/Foundation.h>
@interface BatMan : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *gender;
@end
BatMan的定義 (BatMan.m) :
#import "BatMan.h"
@implementation BatMan
@end
不用在類別裡宣告member,也不用再補上@synthesize,只要@property,聰明的Xcode馬上知道我們在想什麼,幫我們將剩餘的部分補上。雖然我們肉眼看不到,但它們確實存在,程式碼也實實在在地編譯成功了! 不過我們還是來解密一下,了解Xcode到底幫我們加上了什麼。
@synthesize name = _name;
@synthesize gender = _gender;
Xcode幫我們加了以上兩行程式碼,如此不只補齊了@synthesize,連member也自動幫我們生成。而member的名稱,正是 = 符號右手邊加了底線的名稱,_name和_gender。假設BatMan有一項飛翔的獨門絕技,有一個fly method,接下來我們讓BatMan一邊飛,一邊取名字。
-(void)fly
{
_name = @"彼得潘";
}
在fly method裡,我們將BatMan的名稱取名彼得潘。利用偷偷產生的_name member來設定。
二. 神奇的@
Objective-C的物件讓人又愛又恨。因為物件,讓我們可以輕易設計許多天馬行空的App。但也因為物件,讓我們在使用某些常用型別,比方int, Array, Dictionary時,需要寫許多令人心煩的程式碼。現在有了神奇的@,一切都簡單多了。
(1) NSNumber
1. 一拍即合型
如果對象是數字,它和@完全是一拍即合,可以直接結合。
沒有@的世界末日
NSNumber * endOfTheWorld = [NSNumber numberWithInt: 2012];
神奇@的世界末日
NSNumber *endOfTheWorld = @2012;
2. 月下老人 ( ) 牽線型
如果不是數字,還是可以結合,只不過需要 ( ) 的幫忙撮合。比方method getEndOfTheWorld回傳數字,讓我們看看它如何結合@。
-(int)getEndOfTheWorld
{
return 2012;
}
沒有@的世界末日
NSNumber * endOfTheWorld = [NSNumber numberWithInt: [self getEndOfTheWorld]];
神奇@的世界末日
NSNumber *endOfTheWorld = @([self getEndOfTheWorld]);
(2) NSArray
結合@ 和 [ ] ,我們可以輕易創造NSArray。接下來我們以彼得潘看了2次的蝙蝠俠三部曲為例說明。
沒有@的蝙蝠俠三部曲
NSArray *batmanMovies = [NSArray arrayWithObjects:@"開戰時刻", @"黑暗騎士", @"黎明升起", nil];
神奇@的蝙蝠俠三部曲
NSArray *batmanMovies = @[@"開戰時刻", @"黑暗騎士", @"黎明升起"];
利用神奇@來創造array,不只程式碼更加精簡,最重要的我們終於甩掉沒路用的nil了。從前利用arrayWithObjects:生成array,必須以nil做訊號表示已經來到array的盡頭,後頭無一物了。現在神奇@聰明多了,只要將array裡的物件一一交給它,它就懂了。
蝙蝠俠是如此地賣座,不管是電影公司或影迷,都願意為了第4集再砸錢吧。不過要拍第4集可沒那麼簡單,利用@建立的array是不可變的NSArray。現在該是向變形金剛學習的時刻了。讓我們將@生成的NSArray變型為NSMutableArray。
NSMutableArray *batmanMovies = [@[@"開戰時刻", @"黑暗騎士",
@"黎明升起"] mutableCopy];
[batmanMovies addObject:@"當蝙蝠俠遇上彼得潘"];
(3) NSDictionary
結合@ 和 { },我們可以輕易創造NSDictionary。接下來彼得潘以最近推出新專輯的蔡淳佳新專輯曲目來說明。
沒有@的蔡淳佳新專輯主打歌
NSDictionary *joiNewAlbum = [NSDictionary dictionaryWithObjectsAndKeys:@"Love You", @"song1", @"不透光", @"song2", nil];
神奇@的蔡淳佳新專輯主打歌
NSDictionary *joiNewAlbum = @{ @"song1" : @"Love You", @"song2": @"不透光" };
三. 更方便的存取集合裡的元素 (Object Subscripting)
透過全新的Object Subscripting語法,我們能夠以更簡潔利落的程式碼存取集合裡的元素。所謂集合,最具代表性的莫過於NSArray和NSDictionary了。讓我們繼續以剛剛的batmanMovies和joiNewAlbum為例,並配合實際的UI畫面demo說明。
(1) 畫面設計
說明:
待會我們將利用movieLabel和songLabel填入電影名和歌曲名。
(2) 程式設計
沒有Object Subscripting的舊時代寫法:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *batmanMovies = [@[@"開戰時刻", @"黑暗騎士", @"黎明
升起"]
mutableCopy];
[batmanMovies addObject:@"當蝙蝠俠遇上彼得潘"];
NSDictionary *joiNewAlbum = @{ @"song1" : @"Love You",
@"song2": @"不透光" };
movieLabel.text = [batmanMovies objectAtIndex:3];
songLabel.text = [joiNewAlbum objectForKey:@"song2"];
}
執行結果:
Object Subscripting的新時代寫法:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *batmanMovies = [@[@"開戰時刻", @"黑暗騎士", @"黎明
升起"]
mutableCopy];
[batmanMovies addObject:@"當蝙蝠俠遇上彼得潘"];
NSDictionary *joiNewAlbum = @{ @"song1" : @"Love You",
@"song2": @"不透光" };
movieLabel.text = batmanMovies[3];
songLabel.text = joiNewAlbum[@"song2"];
}
說明:
利用[ ] 內指定數字存取array裡的物件( 第一個物件的index為0),指定key則可存取dictionary裡的物件。
想要具有Object Subscripting的神奇魔力,其實很簡單,想要像NSArray一樣透過index存取,需要定義以下2兩個method:
- (id)objectAtIndexedSubscript:(NSUInteger);
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
想要像NSDictionary一樣透過key存取,需要定義以下2兩個method:
- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)object forKeyedSubscript:(id)key;
接下來彼得潘就以Peter Pan的自我介紹為例,說明如何讓自訂類別具有Object Subscripting的神奇魔力吧。
(1). PeterPan類別的宣告
#import <Foundation/Foundation.h>
@interface PeterPan : NSObject
{
NSString *lover;
NSString *enemy;
}
- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)object forKeyedSubscript:(id)key;
@end
(2). PeterPan類別的定義,定義實現Object Subscripting的必要method objectForKeyedSubscript:和setObject:forKeyedSubscript:。
- (id)objectForKeyedSubscript:(id)key
{
if([key isEqualToString:@"最愛"])
{
return lover;
}
else if([key isEqualToString:@"死對頭"])
{
return enemy;
}
else
{
return nil;
}
}
- (void)setObject:(id)object forKeyedSubscript:(id)key
{
if([key isEqualToString:@"最愛"])
{
[self setValue:object forKey:@"lover"];
}
else if([key isEqualToString:@"死對頭"])
{
[self setValue:object forKey:@"enemy"];
}
}
測試程式
- (void)viewDidLoad
{
[super viewDidLoad];
PeterPan *peterPan = [[PeterPan alloc] init];
peterPan[@"最愛"] = @"温蒂";
peterPan[@"死對頭"] = @"虎克船長";
self.loverLabel.text = peterPan[@"最愛"];
self.enemyLabel.text = peterPan[@"死對頭"];
}
說明:
由於peterPan物件學會了Object Subscripting,因此我們可以透過[ ] ,傳入@”最愛”和@”死對頭”做為key來存取。q
執行結果:
2012年9月3日 星期一
訂閱:
文章 (Atom)