2012年10月1日 星期一

旋轉,就是那麼簡單 ~ 全新iOS 6旋轉機制

在 iOS 6之前,想要控制App畫面的旋轉,採用的是以下method

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation

此method雖然簡單,但我們還是需要依據參數toInterfaceOrientation來做判斷,依據是否支援此方向而回傳YES或NO,例如以下例子:


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
    if(toInterfaceOrientation == UIInterfaceOrientationPortrait)
    {
        return YES;
    }
    else
    {
        return NO;
    }
}
說明:
只鍾情於一種角度,iPhone為直立方向,且home按鈕在下方(UIInterfaceOrientationPortrait)。將iPhone轉至其它方向時,畫面並不會跟著轉動。


   

                                 

在iOS 6更簡單了,改為利用以下method定義App支援的方向。

- (NSUInteger)supportedInterfaceOrientations
{
        return UIInterfaceOrientationMaskPortrait;
}

說明:
想要支援哪些方向,直接回傳方向的Mask即可。完整的方向mask定義,可參考UIApplication.h。


typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
};

讓我們再來看看另一個例子,如果想要全方向的支援,只要回傳UIInterfaceOrientationMaskAll即可。









SoundCloud SDK介紹 (MacToday 2012.10)

SoundCloud是目前十分火紅的雲端音樂平台,如果說Youtube是諸葛亮,那麼SoundCloud就是周瑜了。Youtube的強項在影片,SoundCloud則在音樂。這些雲端平台讓許多像彼得潘這樣的平凡人實現唱歌,演電影的夢想,而對已經成名的明星來講,這些雲端平台不只有著更快,更國際化的宣傳效果,還能省下大筆的行銷預算。接下來彼得潘將以好朋友,亞洲鋼琴天王V.K克存放於SoundCloud的作品為例,介紹如何結合SoundCloud API,播放V.K克遠在天邊的琴聲陪伴我們度過漫漫長夜。







2012年9月29日 星期六

考慮舊版相容性的App設計

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敘述才會成立。


給我一首歌的時間,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的高度決定顯示的圖片。





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.
 
}

除此之外,為了確定當記憶體不足時,不在畫面上的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的合照了