yashiganiの英傑になるまで死ねない日記

週末はマスターバイクでハイラルを走り回ります

UIWindowを使ってオレオレアラートを表示する

アラートっぽいのを表示するライブラリを作りたくなって,UIWindowを使ってみた. なんでwindowを使ったかというと,普通にviewにaddSubview:するのだと常に前面に表示されている保証が無いし,ライブラリのくせに自分の管理してないviewの階層をいじくるのはちょっとアレかなあと思ったから.

UIWindowとは

昨今のモダンな環境でiOSアプリを作ってるとUIWindowを自分で作ることはまずないので気にしたこと無いかもしれないけど,UIWindowは特別なUIViewでview階層のルートとなるviewのこと. iOSアプリは例外なく全てのアプリが少なくともひとつのUIWindowを持っている. Xcode 3くらいまではテンプレートでwindowとってきてmakeKeyAndVisibleしていたのでなんとなく覚えている人もいるかと思う. Macと違ってiOSではひとつのアプリしかアクティブにならないから,基本的にwindowを増やしたいこととかほとんどないけど,オリジナルのアラートとかをスクリーン全体に重ねて表示したいってときとかに使える. ちょうど,UIWindowにはwindowLevelってプロパティがあって,オレオレアラートならUIWindowLevelAlertを設定しておくといい具合になる. 基本的にaddSubview:するのと同じ感覚で使えるし,windowを新しく作って表示するとアプリの状態をあまり意識せずにオーバーレイ表示ができて便利.

UIWindowを表示する

UIWindowを表示するときには,通常のviewと違ってmakeKeyAndVisibleを使う. windowにはrootViewControllerプロパティもあるけど,単純なアラート程度ならwindowに直接addSubview:すればいいと思う. UIWindowUIViewのサブクラスなのでだいたいいつもと同じ感覚で使える. 表示はこんな感じ.

- (void)showWindow
{
    UIWindow *w = [UIWindow new];
    w.frame = UIScreen.mainScreen.bounds;
    w.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.2];
    MyAlertView *alert = ...
    [w addSubview:alert];
    [w makeKeyAndVisible];
    self.window = w;
}

windowに対してmakeKeyAndVisibleを呼ぶとそのwindowは key window になる. key windowってはこれまたあ特別なwindowで,現在最前面に表示されているwindowだと解釈すればいい. カレントのkey windowはUIApplicationkeyWindowプロパティで取得できるし,windowの階層はwindowsプロパティで取得できる.

UIWindowを隠す

Macと同じ感覚だとresignKeyWindowを呼ぶけど,iOSでは直接呼んではいけない. resignKeyWindowはkey windowじゃなくなったときの処理をするメソッド(deallocdrawRect:と同じような感じ).

resignKeyWindow Invoked automatically when the window resigns key window status; never invoke this method directly.

- (void)resignKeyWindow Discussion This method sends resignKeyWindow to the receiver’s first responder and posts UIWindowDidResignKeyNotification to the default notification center.

(UIWindow Class Referenceより抜粋)

じゃあどないして隠すかというと,こんな感じで隠したいwindowを破棄すると隠れる. ついでに古いやつをkey windowにしておこう.

- (void)removeWindow
{
    UIApplication *app = UIApplication.sharedApplication;
    NSRange range = NSMakeRange(0, [app.windows indexOfObject:self.window] - 1);
    UIWindow *nextKeyWindow = [[app.windows subarrayWithRange:range] lastObject];
    [nextkeywindow makeKeyAndVisible];
    self.window = nil; // 保持していたやつを破棄
}

なんか釈然としないけどこれで隠れる. この例ではwindowsから次のkey windowを探してきたけど,出すときに覚えておいてもいいと思う.

まとめ

  • なんかオーバーレイで表示したいときはUIWindowを使うと便利
  • 消すときは自分でresignKeyWindowを呼んではいけない

参考