昔話をします。
かつて、iOS7がありました。
iOS7では、次のコードがクラッシュしたといいます。
[[UINavigationBar appearance] setTranslucent:NO];
コンパイル時にエラーを検出できず、実行時にクラッシュして多くの開発者が涙を流しました。
ある開発者は、こんなコードをアプリのあちこちに書き散らしました。
[navigationController.navigationBar setTranslucent:NO];
また、他の開発者はこんなクラスを作りアプリ全体で使うようにしました。
@implementation HRCommonNavigationViewController
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {
self = [super initWithRootViewController:rootViewController];
if (self) {
self.navigationBar.translucent = NO;
}
return self;
}
@end
1年経ち、iOS8が登場しました。
するとどうでしょう、iOS7では動かなかった[[UINavigationBar appearance] setTranslucent:NO];
が動くようになり、UINavigationBar
のtranslucent
をったった一行で変更できるようになりました。
そして、アプリ全体に散らばった微妙なコードを前に再び涙を流したということです。
ただ、UINavigationBar
の透過をオフにしたかっただけなのです。
昔話終わり。
UIAppearance
という訳なので、以下はiOS7にも対応する必要のある開発者にだけ有益です。
また、公式のドキュメントを見つけられていない部分が幾つかあります。
何故クラッシュするか
The property type may be any standard iOS type: id, NSInteger, NSUInteger, CGFloat, CGPoint, CGSize, CGRect, UIEdgeInsets or UIOffset. Axis parameter values must be either NSInteger or NSUInteger. UIKit throws an exception if other types are used in the axes.
iOS7で例外がthrowされるのは、たぶんUIAppearanceはBOOL
に対応していないため。
また、とあるブログによると、
Update: As of iOS 8, BOOL is now supported for UIAppearance.
とのこと。
確かに、iOS8ではUIAppearance
でBOOLをセットしてもクラッシュしません。
コンパイルエラーにならず、iOS7で動かすとクラッシュするアプリが激増しそうな予感がしますがどうなんでしょう。
てっきりUINavigationBar
が対応していないのかと思いましたが、UIAppearance
の制限のようです。
UIAppearance+Category
NSLog(@"[UINavigationBar appearance] = %@", [UINavigationBar appearance]);
実行してみると分かりますが、_UIAppearance
は呼び出されたメソッドをNSInvocation
として保持して必要な時に呼び出しているだけなので、カテゴリが使えます。
カテゴリで追加したメソッドもUIViewがUIWindow以下にaddSubViewされたタイミングで実行されます。
iOS applies appearance changes when a view enters a window,
UIAppearance_Protocol
ただし、(iOS7)で使えるのはパラメーターが、
id
,NSInteger
, NSUInteger
, CGFloat
, CGPoint
, CGSize
, CGRect
, UIEdgeInsets
or UIOffset
の場合だけ。
これ以外では、NSInvalidArgumentException
がthrowされます。
なので、こんなコードを書きました。
// UINavigationBar+Translucent.m
@implementation UINavigationBar (Translucent)
- (void)hr_setTranslucent:(NSInteger)flag {
[self setTranslucent:(BOOL) flag];
}
@end
// AppDelegate.m
[[UINavigationBar appearance] hr_setTranslucent:0];
AppDelegateで呼ぶのはproxyのほうなのでhr_setTranslucent:
の中が実行されるのは、UINavigationBar
がaddSubViewされたタイミングです。
NSIntegerに変換して、UIAppearanceに登録して、実行時にBOOLに直すだけ。
アホかと思いますが、これが正しく動きます。
是非、明日からお使いください。