LoginSignup
26
26

More than 5 years have passed since last update.

UIAppearanceを使って[UINavigationBar setTranslucent:NO];

Last updated at Posted at 2014-08-18

昔話をします。  
  

かつて、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];が動くようになり、UINavigationBartranslucentをったった一行で変更できるようになりました。
そして、アプリ全体に散らばった微妙なコードを前に再び涙を流したということです。

ただ、UINavigationBarの透過をオフにしたかっただけなのです。

昔話終わり。

UIAppearance

という訳なので、以下はiOS7にも対応する必要のある開発者にだけ有益です。

また、公式のドキュメントを見つけられていない部分が幾つかあります。

何故クラッシュするか

UI_APPEARANCE_SELECTOR

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に直すだけ。
アホかと思いますが、これが正しく動きます。

是非、明日からお使いください。

26
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
26