UIViewController是iOS頂層視圖的載體及控制器,用戶與程序界面的交互都是由UIViewController來控制的,UIViewController管理UIView的生命周期及資源的加載與釋放。
UIView與UIWindow共同展示了應用程序的用戶界面。可以將UIView理解成畫布,UIWindow理解成畫框。這兩個類的繼承關系是這樣的:
NSObject — UIResponder — UIView — UIWindow
iOS中,所有顯示在界面上的對象都是從UIResponder直接或間接繼承的,UIView和UIWindow也不例外。
可以將它們之間的關系想象成這樣一個場景:首先會有一個空的畫框(UIWindow),我們在畫框上放置一塊畫布(UIView),然后可以在這個畫布(UIView)上進行繪畫,畫布上可能會被畫上各種元素,例如UILabel、UIButton等。這些元素其實也是一個又一個UIView,它們會有一個層級關系管理,有點相當於Photoshop圖層的概念,層級高的元素會覆蓋住層級低的元素,從而導致層級低的元素被部分或完全遮擋。
UIWindow
雖然UIWindow繼承自UIView,但是在模型中,它是一個首席View。UIWindow的主要作用是提供一個區域來顯示UIView,然后將事件分發給UIView。一般情況下,應用程序只有一個UIWindow對象,即使有多個UIWindow對象,也只有一個UIWindow可以接受到用戶的觸屏事件。
當新建一個最原始的Empty Application工程后,會發現系統在application:didFinishLaunchingWithOptions:方法里已經為我們建好了一個UIWindow,代碼如下:
- (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; }
1. 創建一個全屏的window:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
2. 給window設置背景色:
self.window.backgroundColor = [UIColor whiteColor];
3. 給將window設置為KeyWindow並顯示:
[self.window makeKeyAndVisible];
假如這個時候通過調試器啟動這個程序,將會收到系統輸出的一個提示:
Application windows are expected to have a root view controller at the end of application launch
這個提示表示沒有指定根view controller,我們可以新建一個帶xib文件的ViewController類作為window的rootViewController:
MyFirstViewController *myVC = [[MyFirstViewController alloc] init];
self.window.rootViewController = myVC;
P.s. 這里有一點需要注意,無論你怎么修改ViewController類實例的尺寸,都會強制鋪滿整個window,文檔里是這樣解釋的:”If a view controller is owned by a window object, it acts as the window’s root view controller. The view controller’s root view is added as a subview of the window and resized to fill the window.”
UIView
UIView有下面這些基礎概念:
- UIViewController的view屬性擁有一個UIView。
- UIView中可以包含多個UIView(subviews)。
- UIView可以通過superview屬性訪問父UIView。
- 如果是UIWindow的子元素,則可以通過Window屬性訪問UIWindow。
UIView中常用的結構體:
CGPoint point = CGPointMake(x, y); //坐標 CGSize size = CGSizeMake(width, height); //大小 CGRect rect = CGRectMake(x, y, width, height); //位置和大小
UIView的常用屬性:
frame — 相對父視圖的位置和大小
bounds — 相對自身的位置和大小,所以bounds的x和y永遠為0
center — 子視圖的中點坐標相對父視圖的位置
transform — 可以通過這個屬性控制視圖的放大縮小和旋轉
superview — 獲取父視圖
subviews — 獲取所有子視圖
alpha — 視圖的透明度(0 - 1)
tag — 視圖的標志,設置了tag后,可以通過viewWithTag方法獲取這個視圖
userInteractionEnabled — 是否相應用戶交互事件
通過transform屬性來對視圖進行縮放、旋轉和平移
//獲取當前transform CGAffineTransform transform = self.lblSeg.transform; //縮放 self.lblSeg.transform = CGAffineTransformMakeScale(.5, .5); //在一個transform的基礎上再縮放 self.lblTest.transform = CGAffineTransformScale(transform, 0.5, 0.5); //旋轉(弧度制) self.lblSeg.transform = CGAffineTransformMakeRotation(M_2_PI); //在一個transform的基礎上再旋轉 self.lblTest.transform = CGAffineTransformRotate(transform,M_2_PI); //平移 self.lblSeg.transform = CGAffineTransformMakeTranslation(100, 100); //在一個transform的基礎上再平移 self.lblTest.transform = CGAffineTransformTranslate(transform, 100, 100);
UIView的常用方法:
初始化視圖
- (id)initWithFrame:(CGRect)aRect
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(10.0, 20.0, 150.0, 100.0)];
將視圖從父視圖中移除
- (void)removeFromSuperview
[self.lblTest removeFromSuperview];
插入一個視圖到指定位置
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
NSInteger myIndex = 0; if(self.myView1.subviews.count>0) { myIndex = self.myView1.subviews.count; } [self.myView1 insertSubview:self.lblTest atIndex:myIndex];
將index1和index2位置的兩個視圖互換位置
- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2
NSInteger index1 = [self.view.subviews indexOfObject:self.lblTest1]; NSInteger index2 = [self.view.subviews indexOfObject:self.lblTest2]; [self.view exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
添加子視圖
- (void)addSubview:(UIView *)view
[self.myView1 addSubview:self.lblTest];
插入視圖到指定位置
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
[self.view insertSubview:self.lblTest atIndex:0];
插入視圖到指定視圖的下面
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview
[self.view insertSubview:self.lblTest1 belowSubview:self.lblTest2];
插入視圖到指定視圖的上面
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview
[self.view insertSubview:self.lblTest1 aboveSubview:self.lblTest2];
將指定子視圖移到最頂層
- (void)bringSubviewToFront:(UIView *)view
[self.view bringSubviewToFront:self.lblTest];
將指定子視圖移到最底層
- (void)sendSubviewToBack:(UIView *)view
[self.view sendSubviewToBack:self.lblTest];
根據視圖的tag查找視圖
- (UIView *)viewWithTag:(NSInteger)tag
[self.lblTest setTag:5]; [self.view viewWithTag:5].alpha = 0;
取得視圖下的所有子視圖
@property(nonatomic, readonly, copy) NSArray *subviews
NSArray *arrView = [self.view subviews]; for (UIView *view1 in arrView) { NSLog(@"%@",view1); }
UIScreen
UIScreen類代表了屏幕,通過這個類我們可以獲取一些關於屏幕的內容:
返回帶有狀態欄的Rect
CGRect bounds = [UIScreen mainScreen].bounds; NSLog(@"%.0f,%.0f,%.0f,%.0f",bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height );
在4英寸設備的屏幕下測試,將打印出0,0,320,568
返回不帶狀態欄的Rect
CGRect bounds = [[UIScreen mainScreen] applicationFrame]; NSLog(@"%.0f,%.0f,%.0f,%.0f",bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height );
在4英寸設備的屏幕下測試,將打印出0,20,320,548
無論是帶狀態欄還是不帶狀態欄獲得的Rect,都是相對於設備屏幕來說的,所以當返回不帶狀態欄的Rect時,y坐標為20(狀態欄的高度為20),而高度減少了20,為568-20=548。
下面這個方法可以獲得狀態欄(StatusBar)的位置和大小:
CGRect rect = [[UIApplication sharedApplication] statusBarFrame]; NSLog(@"%.0f,%.0f,%.0f,%.0f",rect.origin.x, rect.origin.y, rect.size.width, rect.size.height );
在4英寸設備的屏幕下測試,將打印出0,0,320,20
生命周期
我們建立一個簡單的模型來測試生命周期:新建兩個ViewController,一個是主視圖控制器(main ViewController,以下簡稱mainVC),一個是副視圖控制器(sub ViewController,以下簡稱subVC),在mainVC里點擊一個Button,以modal方式切換至subVC,然后在subVC里點擊另一個Button關閉subVC並返回mainVC。我們將這兩個控制器的每個狀態都打印出來,各個階段的執行如下:
case 1. 第一次運行app:
main loadView
main viewDidLoad
main viewWillAppear
main viewDidAppear
case 2. 在mainVC里點擊Button,以modal方式切換至subVC:
sub loadView
sub viewDidLoad
main viewWillDisappear
sub viewWillAppear
sub viewDidAppear
main viewDidDisappear
case 3. 在subVC里點擊Button關閉subVC並返回mainVC
sub viewWillDisappear
main viewWillAppear
main viewDidAppear
sub viewDidDisappear
sub dealloc
當一個視圖控制器被創建,並在屏幕上顯示的時候代碼的執行順序:
step 1:alloc 創建對象,分配空間
step 2:init (initWithNibName) 初始化對象
step 3:loadView 從nib載入視圖 ,通常這一步不需要去干涉。除非你沒有使用xib文件創建視圖
step 4:viewDidLoad 載入完成,可以進行自定義數據以及動態創建其他控件
step 5:viewWillAppear 視圖將出現在屏幕之前,馬上這個視圖就會被展現在屏幕上了
step 6:viewDidAppear 視圖已在屏幕上渲染完成
當一個視圖控制器被移除屏幕並且銷毀的時候的執行順序:
step 1:viewWillDisappear 視圖將被從屏幕上移除之前執行
step 2:viewDidDisappear 視圖已經被從屏幕上移除,用戶看不到這個視圖了
step 3:dealloc 視圖被銷毀
這里需要說一下loadView與viewDidLoad的區別:當loadView時,還沒有view;而viewDidLoad時,view已經創建好了。詳細的加載循環:
step 1:程序請求ViewController的view屬性
step 2:如果view在內存中,則直接加載;如果不存在,則調用loadView方法
step 3:loadView方法執行如下方法:
- 如果重載了這個方法,則必須創建必要的UIView並且將一個非nil值傳給ViewController的view屬性。
- 如果沒有重載這個方法,ViewController會默認使用自己的nibName和nibBundle屬性嘗試從nib文件加載view。如果沒有找到nib文件,它嘗試尋找一個與ViewController類名匹配的nib文件。
- 如果沒有可用的nib文件,那么它創建一個空的UIView作為它的view。
最后還要考慮一個重要的情況:內存不足警告。當程序收到內存警告的時候,會調用每一個ViewController的didReceiveMemoryWarning方法,我們需要做出相應,釋放程序中暫時不需要的資源;通常都會重寫該方法,但記得重寫的時候要調用super的該方法。
iOS3.0 - iOS6.0期間,didReceiveMemoryWarning方法會判斷當前ViewController的view是否顯示在window上,如果沒有顯示在window上,則didReceiveMemoryWarning會自動將ViewController的view以及其所有子view全部銷毀,然后調用View Controller的viewDidUnload方法。但是從iOS6.0開始,viewDidUnload和viewWillUnload這兩個方法已被廢除,收到low-memory時系統不會釋放view,而只是釋放controller的resource。
一種常見處理內存警告的方式:
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; float ver = [[[UIDevice currentDevice] systemVersion] floatValue]; if(ver >= 6.0f) { if(self.isViewLoaded && !self.view.window) { self.view = nil; //確保下次重新加載 } } }
上面的代碼先取得當前iOS系統的版本號,如果是iOS6.0或以上版本,進一步判斷視圖是否被裝載進內存,並且是否為當前視圖,在這兩個條件都滿足(已經裝載進內存&&不是當前視圖)時,將self.view設置為nil,這樣就能保證再調用該ViewController時,loadView和viewDidLoad被再次調用。
我們在xcode調試器里模擬內存警告,監控此時切換的狀態:
case 4. 當已切換至subVC,模擬內存警告,並返回mainVC,不處理didReceiveMemoryWarning。
Received memory warning.
main didReceiveMemoryWarning
sub didReceiveMemoryWarning
sub viewWillDisappear
main viewWillAppear
main viewDidAppear
sub viewDidDisappear
sub dealloc
case 5. 當已切換至subVC,模擬內存警告,並返回mainVC,處理didReceiveMemoryWarning。
Received memory warning.
main didReceiveMemoryWarning
sub didReceiveMemoryWarning
main loadView
main viewDidLoad
sub viewWillDisappear
main viewWillAppear
main viewDidAppear
sub viewDidDisappear
sub dealloc
可以很明顯的看出,當處理了didReceiveMemoryWarning后,重新執行了非當前視圖的loadView和viewDidLoad方法。