UIAlertViewはあちこちに
iOS向けアプリを作っていると必要に応じてアラートを出す場面が多々あると思う。そのためプログラムのあちこちにUIAlertViewの呼び出しが散らばって記述されていることと思う。
そして今日ではCocoaPodsなどで探すと様々なCocoa向けライブラリが公開されており、ちょっとおしゃれなUIのアラートを出してくれるものや、カスタマイズが自由なアラートなども見つかる。
これらのアラートをちょっと試しに導入してみたいこともあるだろうし、自分で設計したアラートを使いたいなどと思うこともあると思うが、きっと手作業で全てのUIAlertViewをMyAlertView(仮名)に置き換えるのは骨が折れるだろうし、だいたい幾つかすり替え忘れたりするものである。
これから作成するアプリならアラートに関するマクロかラッパーでも作っておけば良いと思うが、すでにある程度成熟してしまったプロジェクトや、チームで開発していてアラートを模様替えするたびにあちこちを差分コミットするのが嫌な場合に使えるやり方を説明する。
すり替え(Swizzling)を行う
準備
では、UIAlertViewがあるところに変更を加えずに、全てのアラートを入れ替えるとすればどうすれば良いか、どこに記述すれば良いか。
それはAppDelegate.m(仮名、そのアプリケーションのデリゲートファイル)が最適だと思う。そこだけに少し書き足せば全体でアラートがすり変わる。
自分はSIAlertView( https://github.com/Sumi-Interactive/SIAlertView )というのをCocoapodsで引っ張ってきた。これを例に説明させていただく。
その前に最低限入れて欲しいライブラリが二つある。
どちらもCocoapods対応だ。
platform :ios
pod "BlocksKit"
pod "BlockInjection"
# ↓ 今回のコードを試す際は入れてください。
pod "SIAlertView"
BlocksKit( https://github.com/pandamonia/BlocksKit )とBlockInjection( https://github.com/tokorom/BlockInjection )だ。
どちらもチュートリアルを見れば用途や使い方は理解していただけるだろう。
ちなみに今回例に挙げさせていただくSIAlertViewはボタンが押された時やdismissされるときの処理をブロック記法で書けるところがコーディングの際に都合が良い。
BlocksKitを用いるのはUIAlertViewのほうをブロックで扱えるので好都合であるからだ。
コード
AppDelegateのapplication:didFinishLaunchingWithOptions:メソッド内が最適かと思う。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// その他のコード
// Replace UIAlertView with other AlertView
[BILib replaceImplementationForClassName:@"UIAlertView" methodName:@"show" block:^(UIAlertView *_self) {
SIAlertView *alertView = [[SIAlertView alloc] initWithTitle:_self.title andMessage:_self.message];
// convert buttons
for (NSInteger i = 0; i < _self.numberOfButtons; i++) {
// button title
NSString *title = [_self buttonTitleAtIndex:i];
// add button
[alertView addButtonWithTitle:title type:type handler:^(SIAlertView *alertView) {
void (^handler)() = [_self bk_handlerForButtonAtIndex:i];
if (handler) handler();
}];
}
// convert handlers
alertView.willShowHandler = ^(SIAlertView *alertView) {if (_self.bk_willShowBlock) _self.bk_willShowBlock(_self);};
alertView.didShowHandler = ^(SIAlertView *alertView) {if (_self.bk_didShowBlock) _self.bk_didShowBlock(_self);};
alertView.willDismissHandler = ^(SIAlertView *alertView) {if (_self.bk_willDismissBlock) _self.bk_willDismissBlock(_self, -1);};
alertView.didDismissHandler = ^(SIAlertView *alertView) {if (_self.bk_didDismissBlock) _self.bk_didDismissBlock(_self, -1);};
[alertView show];
}];
// その他のコード
}
といった感じであり、これはSIAlertViewを使った場合という一例である。
上記のコードではUIAlertViewがshowされるとき、設定されている情報を一通り別のアラート(今回の例ではSIAlertView)に移し替えてshowしている。つまりshowの挙動をすり替えている。(Swizzling)
ちなみに上記のようなコードは散らばっているUIAlertViewをBlocksKitでブロック記法で書いていることも条件ではあるが、Delegate記法?で書いていたとしても上記でブロックの挙動を渡してやっている代わりにDelegateを渡してやれば良いと思う。
〆
最近書いた記事はなんかインジェクションやスウィズリングのようなメソッドの呼び出し系に介入するものが多くなってしまった。パフォーマンス的にはどうなんだか知らないけど存続のコードを弄らずに全体を変えられるところが便利すぎてやめられない。。。
追記
集団開発などでブロック記法もデリゲート記法もごちゃ混ぜだという場合は、ブロックがnilでなければブロックを、nilならばデリゲートを渡してやるように分岐すれば良いはず。(ブロック記法できるラリブラリはどうせ中ではAPI本来のデリゲートに噛ませてるはず。ブロックを噛ませれば、その時点でデリゲートもnilではなくなると思うので、ブロックの方のnilチェックで判別したほうが良いと思うため。)