UIWindow 詳解及使用場景


首先來看一下UIWindow 繼承關系

方法和屬性

NS_CLASS_AVAILABLE_IOS(2_0) @interface UIWindow : UIView
//window的屏幕,默認是 [UIScreen mainScreen] ,不能更改,否則沒有界面
@property(nonatomic,strong) UIScreen *screen NS_AVAILABLE_IOS(3_2);  
//window的視圖層級,默認是0.0
@property(nonatomic) UIWindowLevel windowLevel;           
//是否是keyWindow
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;
//該方法不應該被手動調用,當window變為keyWindow時會被自動調用來通知window。可以繼承UIWindow重寫此方法來實現功能
- (void)becomeKeyWindow;   
// 該方法不應該被手動調用,當window不再是keyWindow時(例如其他window實例調用了- makeKeyWindow或- makeKeyAndVisible方法)會被自動調用來通知window。可以繼承UIWindow重寫此方法來實現功能。                          
- (void)resignKeyWindow;                              
//該方法使window變為keyWindow,但不影響顯示狀態
- (void)makeKeyWindow;

//顯示主window並將window設為key並顯示,該方法讓window顯示並變為keyWindow。調用該方法會讓window排在其level組中的最上面。如果只想改變window的顯示而不影響keyWindow狀態,可以直接設置window的hidden屬性為NO。
- (void)makeKeyAndVisible;                            
//根控制器
@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0); 
//UIApplication調用window的該方法給window分發事件,window再將事件分發到合適的目標,比如將觸摸事件分發到真正觸摸的view上。可以直接調用該方法分發自定義事件。
- (void)sendEvent:(UIEvent *)event;                   
// 把該window中的一個坐標轉換成在目標window中時的坐標值
- (CGPoint)convertPoint:(CGPoint)point toWindow:(nullable UIWindow *)window;   
// 把目標window中的一個坐標轉換成在該window中時的坐標值
- (CGPoint)convertPoint:(CGPoint)point fromWindow:(nullable UIWindow *)window;  
// 把該window中的一個矩陣轉換成在目標window中時的矩陣值
- (CGRect)convertRect:(CGRect)rect toWindow:(nullable UIWindow *)window;
// 把目標window中的一個矩陣轉換成在該window中時的矩陣值
- (CGRect)convertRect:(CGRect)rect fromWindow:(nullable UIWindow *)window;

 

 

UIView的功能 

負責渲染區域的內容,並且響應該區域內發生的觸摸事件

UIWindow

在iOS App中,UIWindow是最頂層的界面內容,我們使用UIWindow和UIView來呈現界面。UIWindow並不包含任何默認的內容,但是它被當作UIView的容器,用於放置應用中所有的UIView。

從繼承關系來看,UIWindow繼承自UIView,所以UIWindow除了具有UIView的所有功能之外,還增加了一些特有的屬性和方法,而我們最常用的方法,就是在App剛啟動時,調用UIWindow的rootViewController(必須指定根控制器) 和 makeKeyAndVisible方法

狀態欄和鍵盤都是特殊的UIWindow。

 

UIWindow的主要作用有

1.作為UIView的最頂層容器,包含應用顯示所有的UIView;

2.傳遞觸摸消息和鍵盤事件給UIView;

 

UIWindow的層級:

UIWindow的層級由一個UIWindowLevel類型屬性windowLevel,該屬性指示了UIWindow的層級,windowLevel有三種可取值。

並且層級是可以做加減的self.window.windowLevel = UIWindowLevelAlert+1;

 

UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal; //默認,值為0
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert; //值為2000 
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar ; // 值為1000

 

 

 

Normal ,StatusBar,Alert.輸出他們三個層級的值,我們發現從左到右依次是0,1000,2000,也就是說Normal級別是最低的,StatusBar處於中級,Alert級別最高。而通常我們的程序的界面都是處於Normal這個級別的,系統頂部的狀態欄應該是處於StatusBar級別,提醒用戶等操作位於Alert級別。根據window顯示級別優先原則,級別高的會顯示在最上層,級別低的在下面,我們程序正常顯示的view在最底層;

 

四個關於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位置被取代時觸發,返回空

 

 程序啟動然后點擊彈框出來 keywindow的變化

這四個通知對象中的object都代表當前已顯示(隱藏),已變成keyWindow(非keyWindow)的window對象,其中的userInfo則是空的。於是我們可以注冊這個四個消息,再打印信息來觀察keyWindow的變化以及window的顯示,隱藏的變動 . 變成keywindow 的流程是這樣的(默認的window -->點擊彈出AlertView)

1.程序默認的window先顯示出來

2.默認的window再變成keywindow 

3.AlertView 的window顯示出來

4.默認的window變成keywindow

5.最終AlertView的window變成keywindow 

 

根據測試我們同時可以知道默認的window的level是0,即normal級別;AlertView的window的level是1996,比Alert級別稍微低了一點兒。同時我們可以看出ActionSheet的window的level是2001; 鍵盤window 的level是最高的在一切之上(我測試的是不管level 設置為多少都在鍵盤window 的下面)

 

當我們點擊ActionSheet cancel的時候,我們會看到流程

1.首先ActionSheet 的window變成非keyWindow

2.程序默認的window變成keywindow 

3.ActionSheet 的window隱藏掉

 

彈出AlertView和ActionSheet的時候系統會幫你改變keyWindow  但是當彈出鍵盤的時候keyWindow是不變的!

下面有說keyWindow是用來接收鍵盤以及非觸摸類的消息(文檔有誤 是指點擊事件等 不是keywindow 也是可以接受事件的消息的)

 

keyWindow 

當前app可以打開的多個window 如系統狀態欄其實就是一個window ,程序啟動的時候創建的默認的window ,彈出鍵盤也是一個window ,alterView 彈框也是window 。但是keyWindow只有一個 ,一般情況下就是我們程序啟動時設置的默認的window

官方文檔中是這樣解釋的 “The key window is the one that is designated to receive keyboard and other non-touch related events. Only one window at a time may be the key window." 翻譯過來就是說,keyWindow是指定的用來接收鍵盤以及非觸摸類的消息,而且程序中每一個時刻只能有一個window是keyWindow。

文檔有誤:app可以打開的多個window 每個里面加入都加入UITextField  和點擊事件 發現 都可以處理事件和接受鍵盤消息

 

問題一:一個應用程序只能有一個主窗口,如果程序中創建了兩個Window,那么誰是主窗口?

 

①iOS 7 以后,主窗口和次窗口是沒有區別的
②iOS 7 之前,如果后面的窗口設置為主窗口,會把之前設置的主窗口覆蓋掉

 

問題二:只有主窗口才能響應鍵盤的輸入事件?

在ios9.3的模擬器中,主窗口和非主窗口中的輸入框都能輸入文字,但是在ios6.1的模擬器中,
非主窗口的輸入框不能輸入文字。

 

 

獲取keyWindow的方式

UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
UIViewController *rootViewController = keyWindow.rootViewController;

 

注意:keyWindow不是一成不變的,當你創建alertView或者ActionSheet的時候,它們所在的window會變成keyWindow。也就是說系統默認創建的window首先變成keywindow,而當彈框的時候,alertView所在的window變成keywindow,默認的keywindow變成非keywindow。

 

@property(nonatomic,readonly) NSArray  *windows;

windows數組里面,window是根據windowLevel來排列的,最后一個覆蓋在最上面。這里的windows數組不包括系統提供的window,比如說狀態欄就是在一個系統創建的window里面。

 

測試代碼如下

#import "AppDelegate.h"

@interface AppDelegate ()
@property(strong, nonatomic) UIWindow *normalWindow;
@property(strong, nonatomic) UIWindow *coverStatusBarWindow;
@property(strong, nonatomic) UIWindow *alertLevelWindow;



@end

@implementation AppDelegate

- (void)coverWindowOnClicked{
    NSLog(@"tap tap 11111");
    [[NSNotificationCenter defaultCenter]postNotificationName:@"kOnClickedStatusBarNotification" object:self userInfo:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"touchesBegan touchesBegan55555555555");

}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //1.
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor yellowColor];
    self.window.rootViewController = [[UIViewController alloc]init];
    [self.window makeKeyAndVisible];
    
    NSLog(@"1hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel);
    
    
    //2.
    UIWindow *normalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    normalWindow.backgroundColor = [UIColor grayColor];
    normalWindow.windowLevel = UIWindowLevelNormal;
    normalWindow.rootViewController = [[UIViewController alloc]init];
    [normalWindow makeKeyAndVisible];
    self.normalWindow = normalWindow;
    
    UITextField *tf = [[UITextField alloc] init];
    tf.frame = CGRectMake(10, 64, 100, 20);
    tf.borderStyle = UITextBorderStyleRoundedRect;
    [self.normalWindow addSubview:tf];
    UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)];
    [self.normalWindow addGestureRecognizer:tap1];

    
    NSLog(@"2hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel);
    

    
    //2. 創建覆蓋着狀態欄的window
    UIWindow * coverStatusBarWindow =[[UIWindow alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 20)];
    coverStatusBarWindow.rootViewController = [[UIViewController alloc]init];
    coverStatusBarWindow.backgroundColor = [UIColor redColor];
    //級別要比 狀態欄的級別高
    coverStatusBarWindow.windowLevel = UIWindowLevelStatusBar+1;
    [coverStatusBarWindow makeKeyAndVisible];
    self.coverStatusBarWindow = coverStatusBarWindow;
    
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)];
    [self.coverStatusBarWindow addGestureRecognizer:tap];
    //想移除coverStatusBarWindow 將其賦值為空
    //     self.coverStatusBarWindow = nil;
    
    
    
    // 3.創建UIwindow1
    self.alertLevelWindow = [[UIWindow alloc] initWithFrame:CGRectMake(50, 150, 200, 250)];
    self.alertLevelWindow.backgroundColor = [UIColor blueColor];
    UIViewController *vc1 = [[UIViewController alloc] init];
    self.alertLevelWindow.rootViewController = vc1;
    self.alertLevelWindow.windowLevel = UIWindowLevelAlert;
    [self.alertLevelWindow makeKeyAndVisible];
    // 給UIwindow1添加一個輸入框
    UITextField *tf1 = [[UITextField alloc] init];
    tf1.frame = CGRectMake(10, 64, 100, 20);
    tf1.borderStyle = UITextBorderStyleRoundedRect;
    [self.alertLevelWindow addSubview:tf1];
    UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)];
    [self.alertLevelWindow addGestureRecognizer:tap2];
    
    
    NSLog(@"1hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel);
    
    NSLog(@"windows 剛啟動 ---%@",[UIApplication sharedApplication].windows);


    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyBoardShow:) name:UIKeyboardDidShowNotification object:nil];
    

    return YES;

    
}

- (void)keyBoardShow:(NSNotification *)notif{
    
    NSLog(@"windows 鍵盤彈出 ---%@",[UIApplication sharedApplication].windows);

    NSLog(@"2hahah%f",[UIApplication sharedApplication].keyWindow.windowLevel);
}

 

 

 

運行效果如下

 

 

可以得出以下結論: (實踐是檢驗真理的唯一標准 網上有很多是錯誤的還是自己實踐最好)

1) 同一層級的 最后一個顯示出來,上一個被覆蓋

2)UIWindow在顯示的時候是不管KeyWindow是誰,都是Level優先的,即Level最高的始終顯示在最前面。

3)誰最后設置的 makeKeyAndVisible 誰就是keyWindow 其他的也會顯示出來 所有的window都可以監聽鍵盤 和點擊的事件 

 

看打印

windows 剛啟動 ---(
    "<UIWindow: 0x7ff636e01b80; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005c6e0>; layer = <UIWindowLayer: 0x600000027640>>",
    "<UIWindow: 0x7ff636d07580; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005fa40>; layer = <UIWindowLayer: 0x6000000276a0>>",
    "<UIWindow: 0x7ff636d0bdd0; frame = (0 0; 320 20); gestureRecognizers = <NSArray: 0x6080002418c0>; layer = <UIWindowLayer: 0x600000037bc0>>",
    "<UIWindow: 0x7ff636d0d500; frame = (50 150; 200 250); gestureRecognizers = <NSArray: 0x600000241ec0>; layer = <UIWindowLayer: 0x600000037e80>>"
)

windows 鍵盤彈出 ---(
    "<UIWindow: 0x7ff636e01b80; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005c6e0>; layer = <UIWindowLayer: 0x600000027640>>",
    "<UIWindow: 0x7ff636d07580; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x60000005fa40>; layer = <UIWindowLayer: 0x6000000276a0>>",
    "<UIWindow: 0x7ff636d0bdd0; frame = (0 0; 320 20); gestureRecognizers = <NSArray: 0x6080002418c0>; layer = <UIWindowLayer: 0x600000037bc0>>",
    "<UIWindow: 0x7ff636d0d500; frame = (50 150; 200 250); gestureRecognizers = <NSArray: 0x600000241ec0>; layer = <UIWindowLayer: 0x600000037e80>>",
    "<UITextEffectsWindow: 0x7ff636c110d0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x60800003ae80>>",
    "<UIRemoteKeyboardWindow: 0x7ff636f06f90; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x60800003f860>>"
)

 

發現 

(1)UITextEffectsWindow
這是iOS8引入的一個新window,是鍵盤所在的window。它的windowLevel是最高的。
(2)UIRemoteKeyboardWindow
iOS9之后,新增了一個類型為 UIRemoteKeyboardWindow 的窗口用來顯示鍵盤按鈕。

如何銷毀一個UIWindow

self.testWindow.hidden = YES;
self.testWindow = nil;

 

 
window應用場景

在應用開發中,將某些界面覆蓋在所有界面的最上層。這個時候,我們就可以手工創建一個新的UIWindow。需要注意的是,和創建UIView不同,UIWindow一旦被創建(並設置rootViewController 和 makeKeyAndVisible),它就自動地被添加到整個界面上了(當然,其windowLevel要足夠高)

場景一:

支付寶錢包等App的密碼保護頁面是基於UIWindow實現的,當用戶從應用的任何界面按Home鍵退出,過一段時間再從后台切換回來時,顯示一個密碼輸入界面。只有用戶輸入了正確的密碼,才能進入退出前的界面。因為這個密碼輸入界面可能從任何應用界面彈出,並且需要蓋住所有界面的最上層,所以很合適做一個UIWindow來實現

場景二:

自定義statusBar 解決 點擊statusBar滑動到頂部 

http://www.cnblogs.com/junhuawang/p/6003191.html

 

場景三:

一個手勢解鎖 0.設置手勢界面 1.app進入后台跳轉前台是進入手勢解鎖界面 2.點擊某個按鈕進入手勢界面

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM