一、簡介
<<UIWindow類定義,管理和協調的Windows應用程序顯示在屏幕上的對象(如Windows)。一個窗口的兩個主要職能是,為顯示其意見面積和分發活動的意見。窗口是在視圖層次的根。一個窗口屬于一個級別;一個級別的窗口出現另一個層面以上。例如,警報出現高于正常的窗口。通常情況下,只有一個在IOS應用程序的窗口
<<UIWindow :UIWindow是一種特殊的UIView,通常在一個app中只會有一個UIWindow
<<繼承關系:UIWindow --> UIView?-->UIResponder-->NSObject
<<它包含了應用中的可見內容;
<<它在視圖和應用對象之間傳遞觸摸事件中起很重要的作用;
<<它和視圖控制器配合完成方向轉變
? ? ? 在iOS系統中,windows沒有標題欄、關閉框以及其他可視控件。一個window就是一個或多個視圖的空白容器,而且應用不能通過window來改變自己的顯示內容。當你想要改變現顯示容的時候,改變最上層的視圖就可以了。
?? ?大多數iOS應用在其生命周期內只使用一個window,這個window從應用的主nib文件中加載,鋪滿整個主屏幕。當然,如果你的應用支持外部顯示,它會額外創建一個window,用于外部顯示。系統會創建其他典型的window,一般是在響應特殊事件時創建,例如來電顯示。
格式為
1--> 設置根視圖(屬性的作用)
[self.window.rootviewcontroller=vc];; ? (這是具體的例子)
@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0); // 設置根視圖,?默認是空的 ??(這是屬性的說明)
二、UIWindow的創建和配置
可以代碼或者InterfaceBuilder來創建并設置window。您在啟動時創建了窗口,并應保留它,并將其保存到應用程序代理對象中的引用。如果需要額外的window,則在需要用的時候創建即可。
??? 創建window不需要考慮應用實在前臺啟動還是后臺啟動。創建和設置一個window不需要花太多資源。如果程序直接在后臺啟動,那么你就不能讓window可見,直到window到前臺之后再讓其可見
1、InterfaceBuilder創建UIWindow
用IB創建window非常簡單,因為Xcode的工程模板可以替你你創建。每個應用都會包含一個主XIB文件,這個XIB就包含了一個主window。另外,這些模板也為window在應用的代理對象中定義了outlet,你可以在代碼中通過outlet取到window對象。
注意:在使用IB創建window的時候,應該在屬性設置欄中設置為全屏。如果沒有設置為全屏,且window小于手機的屏幕尺寸,那么一些視圖的觸摸時間肯接收不到。這是因為window接受不到自己區域以外的觸摸事件。如果視圖的觸摸點沒在window的區域范圍內,則響應不到觸摸事件。所以要確保window是全屏。
??? 如果你重構代碼時用到 IB添加window,也很簡單,像XIB文件中拖一個window對象,然后進行如下操作:
要在運行時訪問window,應該把window和outlet相連。outlet一般情況下是在application delegate中,也可以是這個NIB文件對應的代碼文件。
重構過程中如果需要新建一個主NIB,那就得在info.plist文件中設置NSMainNibFil鍵。通過設置NSMainNibFil的值來確保這個window在代理方法application:didFinishLaunchingWithOptions:被調用是得到加載。
2、純代碼創建window
上面代碼中的self.window,是在application delegate中已經聲明過得屬性,用來保存window對象。如果你創建的是用于外部顯示的window,應該給它重新命名,并且需要設置bounds。
?創建window的時候,要把bounds設置為屏幕大小,不能有任何縮減
三、將view添加到UIWindow
1、直接將控制器的view添加到UIWindow中,并不理會它對應的控制器
[self.window ?addsubview:vc.view];
直接將view通過addSubview方式添加到window中,程序負責維護view的生命周期以及刷新,但是并不會為去理會view對應的ViewController,因此采用這種方法將view添加到window以后,我們還要保持view對應的ViewController的有效性,不能過早釋放。
2、設置uiwindow的根控制器,自動將rootviewcontroller的view添加到window中,負責管理rootviewcontroller的生命周期
[self.window.rootviewcontroller=vc];
rootViewController時UIWindow的一個遍歷方法,通過設置該屬性為要添加view對應的ViewController,UIWindow將會自動將其view添加到當前window中,同時負責ViewController和view的生命周期的維護,防止其過早釋放
<注意>建議使用(2).因為方法(1)存在一些問題,比如說控制器上面可能由按鈕,需要監聽按鈕的點擊事件,如果是1,那么按鈕的事件應該由控制器來進行管理。但控制器是一個局部變量,控制器此時已經不存在了,但是控制器的view還在,此時有可能會報錯。注意:方法執行完,這個控制器就已經不存在了。
問題描述1:當view發生一些事件的時候,通知控制器,但是控制器已經銷毀了,所以可能出現未知的錯誤。
問題描述2:添加一個開關按鈕,讓屏幕360度旋轉(兩者的效果不一樣)。當發生屏幕旋轉事件的時候,UIapplication對象會將旋轉事件傳遞給uiwindow,uiwindow又會將旋轉事件傳遞給它的根控制器,由根控制器決定是否需要旋轉
UIapplication->uiwindow->根控制器(第一種方式沒有根控制器,所以不能跟著旋轉)。
提示:不通過控制器的view也可以做開發,但是在實際開發中,不要這么做,不要直接把view添加到UIWindow上面去。因為,難以管理。
四、獲取window
1、主窗口和次窗口
【self.window makekeyandvisible】讓窗口成為主窗口,并且顯示出來。有這個方法,才能把信息顯示到屏幕上。
? ?因為Window有makekeyandvisible這個方法,可以讓這個Window憑空的顯示出來,而其他的view沒有這個方法,所以它只能依賴于Window,Window顯示出來后,view才依附在Window上顯示出來。
【self.window make keywindow】//讓uiwindow成為主窗口,但不顯示。
2.獲取UIwindow
1)[UIApplication?sharedApplication].windows ?在本應用中打開的UIWindow列表,這樣就可以接觸應用中的任何一個UIView對象(平時輸入文字彈出的鍵盤,就處在一個新的UIWindow中)
(2)[UIApplication sharedApplication].keyWindow(獲取應用程序的主窗口)用來接收鍵盤以及非觸摸類的消息事件的UIWindow,而且程序中每個時刻只能有一個UIWindow是keyWindow。
提示:如果某個UIWindow內部的文本框不能輸入文字,可能是因為這個UIWindow不是keyWindow
(3)view.window獲得某個UIView所在的UIWindow
五、UIWindow的視圖屬性(屬性的順序與蘋果API一致)
1-->設置Screen
????????self.window.screen?=?self.externalScreen;??
@property(nonatomic,strong) UIScreen *screen NS_AVAILABLE_IOS(3_2);//默認是[UIScreen mainScreen]。改變屏幕可能是一個昂貴的操作,不應該在性能敏感的代碼中完成
2-->設置視圖層級
self.window.windowLevel = UIWindowLevelAlert+1;
@property(nonatomic) UIWindowLevel windowLevel; // 默認為0
<注意>UIWindow在顯示的時候會根據UIWindowLevel進行排序的,即Level高的將排在所有Level比他低的層級的前面。下面我們來看UIWindowLevel的定義:
? ? ? ? ? ? ? ? const ? UIWindowLevel UIWindowLevelNormal; 默認為0????
const UIWindowLevel UIWindowLevelAlert;默認為2000
const UIWindowLevel UIWindowLevelStatusBar;默認為1000
typedef CGFloat UIWindowLevel;
Normal ,StatusBar,Alert.輸出他們三個層級的值,我們發現從左到右依次是0,1000,2000,也就是說Normal級別是最低的,StatusBar處于中級,Alert級別最高。而通常我們的程序的界面都是處于Normal這個級別的,系統頂部的狀態欄應該是處于StatusBar級別,提醒用戶等操作位于Alert級別。根據window顯示級別優先原則,級別高的會顯示在最上層,級別低的在下面,我們程序正常顯示的view在最底層;
3-->是否為根視圖(只讀屬性)
BOOL ?isKeyWindow=self.window.keyWindow;
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;
4-->becomeKeyWindow
- (void)becomeKeyWindow; //該方法不應該被手動調用,當window變為keyWindow時會被自動調用來通知window。可以繼承UIWindow重寫此方法來實現功能
5-->resignKeyWindow
- (void)resignKeyWindow;?// 該方法不應該被手動調用,當window不再是keyWindow時(例如其他window實例調用了- makeKeyWindow或- makeKeyAndVisible方法)會被自動調用來通知window??梢岳^承UIWindow重寫此方法來實現功能。
6-->讓當前UIWindow變成keyWindow,默認不顯示
??[self.window?makeKeyWindow];
- (void)makeKeyWindow;?//?讓window成為keyWindow(主窗口),默認不可見
7-->讓當前UIWindow變成keyWindow,并顯示出來
?[self.window?makeKeyAndVisible];
- (void)makeKeyAndVisible;// ?讓當前UIWindow變成keyWindow,并顯示出來
8-->設置uiwindow的根控制器
[self.window.rootviewcontroller=vc];
@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0);// ?根控制器
9-->分發自定義事件
UIApplication 和 UIWindow 有方法 - sendEvent: ,用于把事件分發到 hitTest View
UIApplication == sendEvent: ==> UIWindow == sendEvent: ==> hitTest View
- (void)sendEvent:(UIEvent *)event;//UIApplication調用window的該方法給window分發事件,window再將事件分發到合適的目標,比如將觸摸事件分發到真正觸摸的view上??梢灾苯诱{用該方法分發自定義事件。
10-->把該window中的一個坐標轉換成在目標window中時的坐標值
CGPoint p = [self.window1 convertPoint:CGPointMake(0, 0) toWindow:self.window0];
- (CGPoint)convertPoint:(CGPoint)point toWindow:(nullable UIWindow *)window;
11-->把目標window中的一個坐標轉換成在該window中時的坐標值
CGPoint p = [self.window1 convertPoint:CGPointMake(0, 0) fromWindow:self.window0];
- (CGPoint)convertPoint:(CGPoint)point fromWindow:(UIWindow *)window;
12-->把該window中的一個矩陣轉換成在目標window中時的矩陣值
CGRect rect=[self.window convertRect:CGRectMake(0, 0, 0, 0) toView:self.window0];
- (CGRect)convertRect:(CGRect)rect toWindow:(UIWindow *)window; ?
13--> 把目標window中的一個矩陣轉換成在該window中時的矩陣值
?CGRect rect=[self.window convertRect:CGRectMake(0, 0, 0, 0) toView:self.window0];
- (CGRect)convertRect:(CGRect)rect fromWindow:(UIWindow *)window;
六、UIWindow的常量屬性
1-->UIWindowLevel的枚舉
UIWindowLevelNormal;// 0.000000
UIWindowLevelStatusBar;// 1000.000000
UIWindowLevelAlert;// 2000.000000
2-->監測window的通知名稱:
UIKIT_EXTERN NSString *const UIWindowDidBecomeVisibleNotification; // 當window激活時并展示在界面的時候觸發,返回空
UIKIT_EXTERN NSString *const UIWindowDidBecomeHiddenNotification;? // 當window隱藏的時候觸發,暫時沒有實際測,返回空
UIKIT_EXTERN NSString *const UIWindowDidBecomeKeyNotification;? ? // 當window被設置為keyWindow時觸發,返回空
UIKIT_EXTERN NSString *const UIWindowDidResignKeyNotification;? ? // 當window的key位置被取代時觸發,返回空
3-->監測鍵盤的通知名稱:
UIKIT_EXTERN NSString *const UIKeyboardWillShowNotification;//?顯示鍵盤的時候立即發出該通知
UIKIT_EXTERN NSString *const UIKeyboardDidShowNotification;//顯示鍵盤后才發出該通知
UIKIT_EXTERN NSString *const UIKeyboardWillHideNotification;//鍵盤即將消失的時候立即發出該通知
UIKIT_EXTERN NSString *const UIKeyboardDidHideNotification;//鍵盤消失后才發出該通知
UIKIT_EXTERN NSString *const UIKeyboardWillChangeFrameNotification? NS_AVAILABLE_IOS(5_0);//鍵盤的frame值發生變化的時候立即發出該通知
UIKIT_EXTERN NSString *const UIKeyboardDidChangeFrameNotification? NS_AVAILABLE_IOS(5_0);//鍵盤的frame值發生變化后才發出該通知
4-->userInfo字典中key為:
NSString *const UIKeyboardFrameBeginUserInfoKey;//userInfo字典里該key對應一個NSValue,存儲一個包含鍵盤初始frame值的CGRect結構(即鍵盤剛出現時的frame值)
NSString *const UIKeyboardFrameEndUserInfoKey;//userInfo字典里該key對應一個NSValue,存儲一個包含鍵盤結束frame值的CGRect結構(即鍵盤動畫結束后的frame值)
NSString *const UIKeyboardAnimationDurationUserInfoKey;//userInfo字典里該key對應一個NSNumber,存儲一個包含鍵盤進入或離開屏幕的UIViewAnimationCurve結構
NSString *const UIKeyboardAnimationCurveUserInfoKey;//userInfo字典里該key對應一個NSNumber,存儲一個包含鍵盤動畫時間的double值,時間以秒為單位。
例如,在UIKeyboardWillShowNotification,UIKeyboardDidShowNotification通知中的userInfo內容為
userInfo = {
? ? UIKeyboardAnimationCurveUserInfoKey = 0;
? ? UIKeyboardAnimationDurationUserInfoKey = "0.25";
? ? UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
? ? UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 588}";
? ? UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
? ? UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
? ? UIKeyboardFrameChangedByUserInteraction = 0;
? ? UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
參考: