- 問題:從一個控制器push到另一個控制器時,有時會出現卡頓的一種現象,如下
測試代碼里什么數據都沒有加載,所以排除是卡頓原因,view沒有漸變效果,而是瞬間變換的,loadView里創建新的UIView后沒有設置背景色(默認是透明),所以切換時會看到上一頁的內容。
解決:push的下一頁面的self.view設置一個顏色就可以解決
- 說到這里不免提下viewcontroller的生命周期問題
一、ViewController結構
按結構可以對所有ViewController分成兩類:1、主要用于展示內容的ViewController,這種ViewController主要用于為用戶展示內容,并與用戶交互,如UITableViewController,UIViewController。2、用于控制和顯示其他ViewController的ViewController。這種ViewController一般都是一個ViewController的容器。如UINavigationController,UITabbarController。它們都有一個屬性:viewControllers。其中UINavigationController表示一種Stack式結構,push一個ViewController或pop一次,因此后一個ViewController一般會依賴前一個ViewController。而UITabbarController表示一個Array結構,各個ViewController是并列的。第一種ViewController會經常被繼承,用來顯示不同的數據給用戶。而第二種很少被繼承,除非你真的需要自定義它。注:細心的同學應該能發現,在Xcode中新建一個ViewController時,只可以選擇繼承自UIViewController和UITableViewController,而它們都是第一種。
二、Controller和View的生命周期
這里指的View是指Controller的View。它作為Controler的屬性,生命周期在Controller的生命周期內。就是說你的Controller不能在view釋放前就釋放了。
圖2 ViewController生命周期
三、代碼組織(如何設計良好的viewcontroller)
ViewController生命周期中有那么多函數,一個重要問題就是什么代碼該寫在什么地方。
- 1、init里不要出現創建view的代碼。良好的設計,在init里應該只有相關數據的初始化,而且這些數據都是比較關鍵的數據。init里不要掉self.view,否則會導致viewcontroller創建view。(因為view是lazyinit的)。
- 2、loadView中只初始化view,一般用于創建比較關鍵的view如tableViewController的tabView,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前),最好也不要初始化一些非關鍵的view。如果你是從nib文件中創建的viewController在這里一定要首先調用super的loadView方法,但建議不要重載這個方法。
- 3、viewDidLoad 這時候view已經有了,最適合創建一些附加的view和控件了。有一點需要注意的是,viewDidLoad會調用多次(viewcontroller可能多次載入view,參見圖2)。
- 4、viewWillAppear 這個一般在view被添加到superview之前,切換動畫之前調用。在這里可以進行一些顯示前的處理。比如鍵盤彈出,一些特殊的過程動畫(比如狀態條和navigationbar顏色)。
- 5、viewDidAppear 一般用于顯示后,在切換動畫后,如果有需要的操作,可以在這里加入相關代碼。
- 6、viewDidUnload iOS6中,viewDidUnload回調方法被Deprecated掉了
出現的問題
1、 (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 可以查找XIB中有沒有視圖view。如果有,則不會再走loadView。如果這個時候你的VC是沒有xib的,哪么顯然走這個方法后,是找不到任何view的,即self.view 仍為nil.然后,就跑loadview,這個時候會被觸發,如果在loadView中,什么也不做,也不實例化一個View。哪么程序繼續跑到viewDidLoad里,如果這里還是沒有實例化VIEW。哪么這個VC就沒有視窗。在這里很多時侯會出現一個誤區(死循環)。
- 死循環的原因
1、沒有XIB。
2、ViewController中的loadView方法中沒有做任何實例化self.view的操作。如:
-(void)loadView
{
寫了一大堆代碼,但最好并沒有執行以下兩種方式中的其中一種。
//方式一:實例化時使用[supper loadView];
//方式二 : self.view = [UIView alloc]....
}
3、在viewDidLoad中調用了self.view。
只要這三個條件同時滿足,必定死循環。方式一時,調用了[Supper LoadView] 這個時候由父類產生了一個(0,20,Width,height )。這里的寬高根據是IPAD,還是IPHONE不同而不同,但原點坐標一定是(0,20)即去除狀態條。方式二,沒有對self.view作任可賦值,所以使得self.View = nil;
在條件二滿足的情況下,程序運行到步驟三,這個時候,如果在這里調用了self.View。因為self.View在步驟二中為空,所以又回調到了loadView來,但因loadView中沒有對self.View作實例化,于是在跑完loadView后,又繼續跑viewDidLoad,但因ViewDidLoad中又沒有實例化的情況下,使用了self.View.因此就出會現來回調用的現象。
2、initWithCoder不調用
initWithCoder 是一個類在IB中創建但在xocde中被實例化時被調用的.比如,通過IB創建一個controller的nib文件,然后在xcode中通過 initWithNibName來實例化這個controller,那么這個controller的initWithCoder會被調用.或者是一個view的nib文件,類似方法創建時調用initWithCoder
UIView的機制
initWithFrame方法是什么?
initWithFrame方法用來初始化并返回一個新的視圖對象,根據指定的CGRect(尺寸)。
當然,其他UI對象,也有initWithFrame方法,但是,我們以UIView為例,來搞清楚initWithFrame方法。什么時候用initWithFrame方法?
簡單的說,我們用編程方式申明,創建UIView對象時,使用initWithFrame方法。
在此,我們必須搞清楚,兩種方式來進行初始化UIView。
1.使用 Interface Builder 方式。
這種方式,就是使用nib文件。通常我們說的“拖控件” 的方式。
實際編程中,我們如果用Interface Builder 方式創建了UIView對象。(也就是,用拖控件的方式)
那么,initWithFrame方法方法是不會被調用的。因為nib文件已經知道如何初始化該View。(因為,我們在拖該view的時候,就定義好了長、寬、背景等屬性)。
這時候,會調用initWithCoder方法,我們可以用initWithCoder方法來重新定義我們在nib中已經設置的各項屬性。
這就是為什么使用initWithCoder:的原因,因為BIDViewController.xib的view是BIDQuartzFunView類型,而不是UIView類型了,所以其實是從nib中加載對象實例。
- 使用編程方式。
就是我們聲明一個UIView的子類,進行“手工”編寫代碼的方式。
實際編程中,我們使用編程方式下,來創建一個UIView或者創建UIView的子類。這時候,將調用initWithFrame方法,來實例化UIView。
特別注意,如果在子類中重載initWithFrame方法,必須先調用父類的initWithFrame方法。在對自定義的UIView子類進行初始化操作。
比如:
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];// 先調用父類的initWithFrame方法
if (self) {
// 再自定義該類(UIView子類)的初始化操作。
_scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
[_scrollView setFrame:CGRectMake(0, 0, 320, 480)];
_scrollView.contentSize = CGSizeMake(320*3, 480);
[self addSubview:_scrollView];
}
return self;
}
在這里,我想,應該對initWithFrame方法略知一二了。
當我們所寫的程序里沒用用Nib文件(XIB)時,用代碼控制視圖內容,需要調用initWithFrame去初始化
- (id)initWithFrame:(CGRect)frame
{
if (self =[superinitWithFrame:frame]) {
// 初始化代碼
}
return self;
}
用于視圖加載nib文件,從nib中加載對象實例時,使用 initWithCoder初始化這些實例對象
- (id)initWithCoder:(NSCoder*)coder
{
if (self =[superinitWithcoder:coder]) {
// 初始化代碼
}
return self;
}
1.initWithCoder: 對于.xib,當你嵌入一個視圖對象到xib,視圖加載時默認調用的是該方法;例如:假如創建的view來自nib,那么將會調用initWithCoder,由系統來調用,自己不能調用。
2.initWithFrame: 非.xib的手動編碼,視圖加載時默認調用的是該方法。是由自己調用,來初始化對象的
問題:
1、初始化view時不管用什么方式,
//添加lifeview,檢測view生命周期
self.lifeView = [[LifeView alloc] initWithFrame:CGRectMake(10, 500, 200, 20)];
self.lifeView = [[LifeView alloc] init];
[self.view addSubview:self.lifeView];
都會先調用
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor yellowColor];
}
return self;
}
init只有view用init初始化時才會調用
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor redColor];
}
return self;
}