這篇文章是專門用來記錄開發中一些常見的BUG以及常用的零碎知識點,我會隔一段時間更新內容
1.重復調用2次loadView和viewDidLoad
最好不要在UIViewController的loadView方法中改變狀態欄的可視性(比如狀態欄由顯示變為隱藏、或者由隱藏變為顯示),因為會導致重復調用2次loadView和viewDidLoad方法
假設狀態欄本來是處於顯示狀態的:
下面的是錯誤代碼:
1 - (void)loadView { 2 NSLog(@"loadView"); 3 // 隱藏狀態欄 4 [UIApplication sharedApplication].statusBarHidden = YES; 5 6 // .... 創建UIView 7 self.view = [[[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds] autorelease]; 8 self.view.backgroundColor = [UIColor grayColor]; 9 } 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 NSLog(@"viewDidLoad"); 14 }
運行效果:
打印信息:
1 2013-02-26 00:51:36.152 weibo[2251:c07] loadView 2 2013-02-26 00:51:36.153 weibo[2251:c07] loadView 3 2013-02-26 00:51:36.153 weibo[2251:c07] viewDidLoad 4 2013-02-26 00:51:36.154 weibo[2251:c07] viewDidLoad
雖然運行效果是對的,但是系統連續調用了2次loadView和viewDidLoad方法,導致創建了2次UIView,造成了不必要的開銷。
原因分析:
狀態欄由顯示變為隱藏,意味着屏幕的可用高度變長了,UIViewController的UIView的高度也要重新調整,因此系統會重新調用loadView方法創建UIView,創建完畢后再次調用viewDidLoad方法。
2.按鈕無法點擊
如果在UIImageView中添加了一個按鈕,你會發現在默認情況下這個按鈕是無法被點擊的,需要設置UIImageView的userInteractionEnabled為YES:
imageView.userInteractionEnabled = YES;
設置為YES后,UIImageView內部的按鈕就可以被點擊了
原因分析:
• 當用戶點擊屏幕時,會產生一個觸摸事件,系統會將該事件加入到一個由UIApplication管理的事件隊列中
• UIApplication會從事件隊列中取出最前面的事件進行分發以便處理,通常,先發送事件給應用程序的主窗口(UIWindow)
• 主窗口會調用hitTest:withEvent:方法在視圖(UIView)層次結構中找到一個最合適的UIView來處理觸摸事件
(hitTest:withEvent:其實是UIView的一個方法,UIWindow繼承自UIView,因此主窗口UIWindow也是屬於視圖的一種)
• hitTest:withEvent:方法大致處理流程是這樣的:
首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內:
▶ 若pointInside:withEvent:方法返回NO,說明觸摸點不在當前視圖內,則當前視圖的hitTest:withEvent:返回nil
▶ 若pointInside:withEvent:方法返回YES,說明觸摸點在當前視圖內,則遍歷當前視圖的所有子視圖(subviews),調用子視圖的hitTest:withEvent:方法重復前面的步驟,子視圖的遍歷順序是從top到bottom,即從subviews數組的末尾向前遍歷,直到有子視圖的hitTest:withEvent:方法返回非空對象或者全部子視圖遍歷完畢:
▷ 若第一次有子視圖的hitTest:withEvent:方法返回非空對象,則當前視圖的hitTest:withEvent:方法就返回此對象,處理結束
▷ 若所有子視圖的hitTest:withEvent:方法都返回nil,則當前視圖的hitTest:withEvent:方法返回當前視圖自身(self)
• 最終,這個觸摸事件交給主窗口的hitTest:withEvent:方法返回的視圖對象去處理
我大致畫了個iOS觸摸事件分發的原理圖:
• hitTest:withEvent:方法會忽略以下視圖:
1> 隱藏(hidden=YES)的視圖
2> 禁止用戶操作(userInteractionEnabled=NO)的視圖
3> alpha<0.01的視圖
4> 如果一個子視圖的區域超過父視圖的區域(如果父視圖的clipsToBounds屬性為NO,超過父視圖區域的子視圖內容也會顯示),那么正常情況下在父 視圖區域外的觸摸操作不會被識別,因為父視圖的pointInside:withEvent:方法會返回NO,這樣就不會繼續向下遍歷子視圖了。當然,也 可以重寫pointInside:withEvent:方法來處理這種
綜上所述可得:如果父視圖的userInteractionEnabled=NO,觸摸事件不會繼續往下傳遞給子視圖,所以子視圖永遠無法處理觸摸事件。而UIImageView在默認情況下的userInteractionEnabled就是NO。
3.@2x和-568h@2x
由於iOS設備的屏幕分辨率不盡相同,有大有小,那么在不同設備中 顯示同一張圖片,可能會造成圖片被拉伸、變形,嚴重影響用戶體驗。
為了讓圖片在不同設備中都能得到很好的顯示效果,同一類圖片我們一般會准備3種版本,比如iOS程序在啟動時會全屏顯示的Default.png圖片:
(Retina即視網膜屏幕)
• Default.png(圖片尺寸為320x480):顯示在非Retina-3.5英寸屏幕上(iPhone3G\iPhone3GS,屏幕分辨率為320x480)
• Default@2x.png(圖片尺寸為640x960):顯示在Retina-3.5英寸屏幕上(iPhone4\iPhone4s,屏幕分辨為640x960)
• Default-568h@2x.png(圖片尺寸為640x1136):顯示在Retina-4.0英寸屏幕上(iPhone5,屏幕分辨率為640x1136)
4.啟動app時全屏顯示Default.png
大部分app在啟動過程中全屏顯示一張背景圖片,比如新浪微博會顯示這張:
要想在iOS中實現這種效果,毫無壓力,非常地簡單,把需要全屏顯示的圖片命名為Default.png即可,在iOS app啟動時默認會去加載並全屏顯示Default.png。
也可以用其他名稱來命名圖片,在Info.plist配置一下即可:
配置過后,app啟動時就會去加載並全屏顯示lufy.png
在默認情況下,app顯示Default.png時並非真正的"全屏顯示",因為頂部的狀態欄並沒有被隱藏,比如下面的效果:
大部分情況下,我們都想隱藏狀態欄,讓Default.png真正全屏顯示。
說到這里,可能有人馬上就想到了一種辦法:在AppDelegate的application:didFinishLaunchingWithOptions:方法中添加如下代碼:
[UIApplication sharedApplication].statusBarHidden = YES;
我只能說你的思路是對的,但實際上達不到想要的效果,你會發現顯示Default.png時狀態欄還是存在的,等Default.png顯示完畢后,狀態欄才被隱藏。
我先解釋下為什么這種方法不可行,其實原因很簡單:
1> Default.png是在app啟動過程中加載的,並不是在app啟動完畢后再加載的
2> AppDelegate的application:didFinishLaunchingWithOptions:方法是在app啟動完畢后才調用的
下面說一下解決方案,在Info.plist中增加一個配置即可:
這里的YES表示在app初始化(啟動)的時候就隱藏狀態欄。
當然,在Default.png顯示完畢后狀態欄還是隱藏的。如果想重新顯示狀態欄,補上下面代碼即可:
[UIApplication sharedApplication].statusBarHidden = NO;