[iOS] 複数のUIWindowの挙動を確認する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

UIWindowとは、特別なUIViewでありビュー階層のルートとなるものです。

通常のアプリ作成では、実装の対象は、このウィンドウに載せられたUIViewControllerが主であり、ウインドウ自体は、テンプレート任せとなっているのであまり意識されていないと思います。

テンプレートから生成されるコードでは、AppDelegateクラスに、次のような1つのUIWindowを保持するプロパティが設定されています。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

しかし、このUIWindowは、アプリに1つではありません。 下記のコードで、アプリのウインドウを全て列挙することができます。上のwindowプロパティは、このうちの1つだったという事です。

NSArray<UIWindow*> *windows = [UIApplication sharedApplication].windows;

今回は、この複数のUIWindowのについて確かめて見ました。 なおこの情報は、iOSのバージョンによって違いますので、本記事はiOS9.3での結果である事を予めご了承ください。

2 試験準備

まずは、UIApplicationが持つUIWindowの数と、それぞれの名前やプロパティを出力するメソッドをAppDelegate.mに作成します。

@implementation AppDelegate
// ログ出力
- (void)log:(NSString *)title{
    NSLog(@"[%@]",title);
    NSArray<UIWindow*> *windows = [UIApplication sharedApplication].windows;
    NSLog(@"Application.windows.count = %lu",(unsigned long)windows.count);
    for( UIWindow *window in windows){
        NSLog(@"%@ keywindow = %d hidden=%d lebel=%f",window.class.description,window.keyWindow, window.hidden,window.windowLevel);
    }
}

上記は、次のような出力を得られます。

[TITLE] <= ログ用のタグ
Application.windows.count = 2 <= 現在windowsに2つのUIWindowがある
UIWindow keywindow = 1 hidden=0 lebel=0.000000 <= 1つ目は、UIWindow レベルは0
UITextEffectsWindow keywindow = 0 hidden=0 lebel=10.000000 <= 2つ目のUITextEffectsWindow レベルは10

そして、UIWindowが表示された時と、非表示に成った時のNotificationでこれをコールするようにします。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(windowDidBecomeVisible:)
                                                 name:UIWindowDidBecomeVisibleNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(windowDidBecomeHidden:)
                                                 name:UIWindowDidBecomeHiddenNotification
                                               object:nil];
    return YES;
}

// UIWindowが表示された時
-(void)windowDidBecomeVisible:(NSNotification*)notification
{
    [self log:@"Visible"];// ログ出力
}

// UIWindowが非表示された時
-(void)windowDidBecomeHidden:(NSNotification*)notification
{
    [self log:@"Hidden"];// ログ出力
}

続いて操作画面です。

002

各UIは、それぞれ次の動作を行います。
①アラートを表示
②TextFieldを置いただけ
③キーボードを非表示にするコード
④ログ出力

// ③ hideKeyboard
- (IBAction)tapHideKeyboard:(id)sender {
    [self.view endEditing:YES];//キーボード非表示
}
// ④ Comfirmのボタンを押すと、ログを出力する
- (IBAction)tapComfirmButton:(id)sender {
    AppDelegate *application = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [application log:@"Confirm"];
}

3 動作確認

(1) 起動時

アプリを起動した時点で、UIWindowが一つ表示されます。レベルは0です。

[Visible]
Application.windows.count = 1
UIWindow keywindow = 0 hidden=0 lebel=0.000000

その後、すぐに確認すると、表示時には、keywindow=FALSEだったのが、すぐにTRUEに変化しています。

[Confirm]
Application.windows.count = 1
UIWindow keywindow = 1 hidden=0 lebel=0.000000

(2) アラート表示

003

アラートを表示すると、UITextEffectsWindowというウインドウが追加されました。レベルが10なので、これが最上位になっているようです。

[Visible]
Application.windows.count = 2
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=10.000000

(3) アラートをクローズ

アラートをクローズしても、UIWindowsNotificationは発生しません。(表示・非表示の変化はない)。 これは、Confirmボタンで確認しても変化がないことが確認できました。

[Confirm]
Application.windows.count = 2
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=10.000000

(4) TextFieldにフォーカスを与える(キーボード表示)

004

キーボードが表示された時、新たにUIRemoteKeyboardWindowというウインドウが追加されました。レベルが10000000なので、今度は、これが最上位になっているのでのしょう。

[Visible]
Application.windows.count = 3
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
UIRemoteKeyboardWindow keywindow = 0 hidden=0 lebel=10000000.000000

(5) キーボードを非表示にする

005

キーボードを非表示にすると、まず、UIRemoteKeyboardWindowhidden属性がFALSEになり、その後、消えました。この時点では、レベルが一番高いUITextEffectsWindowが最上位になっていると考えられます。

[Hidden]
Application.windows.count = 3
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
UIRemoteKeyboardWindow keywindow = 0 hidden=1 lebel=10000000.000000

[Confirm]
Application.windows.count = 2
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000

(6) キーボードの表示状態からアラートを出す

キーボードが表示されている時の状態は、下記の通りですが、

[Visible]
Application.windows.count = 3
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
UIRemoteKeyboardWindow keywindow = 0 hidden=0 lebel=10000000.000000

この状態から、アラート表示すると、

006

UIRemoteKeyboardWindowhiddenがTRUEに設定され、見えなくなります。 上の図を見ても、キーボードが消えているのを確認できます。


[Hidden]
Application.windows.count = 3
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
UIRemoteKeyboardWindow keywindow = 0 hidden=1 lebel=10000000.000000

そして、アラートを閉じると、再びキーボードが出現して、UIRemoteKeyboardWindowhiddenがFALSEに戻っています。

[Visible]
Application.windows.count = 3
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
UIRemoteKeyboardWindow keywindow = 0 hidden=0 lebel=10000000.000000

4 最後に

最初に、書いた通り、この情報はiOSのバージョンによって変化します。試しに、先の動作試験をiOS8.2で実行すると、キーボード表示時に次のような結果となります。

[Confirm]
Application.windows.count = 3
UIWindow keywindow = 1 hidden=0 lebel=0.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=1.000000
UITextEffectsWindow keywindow = 0 hidden=0 lebel=2100.000000

レベルの数値やクラス名などは、iOSによって違うため、そのまま決めうちで使用する事は出来ないことが分かります。

また、予想外だったのは、アラートを一度でも表示すると、追加されたUITextEffectsWindowが、最後まで残り続けることでした。

この結果を踏まえて次は、UIWindowsを自前で追加して、更に詳しく動作を追ってみたいと思います。

参考資料


UIKit Framework Reference UIWindow Class Reference
iOS開発におけるウィンドウ「UIWindow」の知られざる活用方法とは? #iOS
makeKeyWindow vs makeKeyAndVisible