プログラムでUINavigationControllerにUINavigationBarのカスタムサブクラスを設定する


92

プログラムで(IBなしで)UINavigationBarインスタンス化する場合、カスタムサブクラスをどのように使用できるかを誰かが知っていますUINavigationControllerか?

UINavigationControllerIBでドラッグすると、ナビゲーションバーの下が表示され、Identity Inspectoryを使用してクラスタイプを変更したり、独自のサブクラスを設定したりできますUINavigationBarが、プログラムではできません。navigationBarナビゲーションコントローラーのプロパティは読み取り専用です...

プログラムでナビゲーションバーをカスタマイズするにはどうすればよいですか?IBは「コード」より「強力」ですか?IBで実行できることはすべてプログラムでも実行できると思いました。


他の場所で解決策を見つける運はありましたか?
prendio2 2010

それについて何か答えはありますか?
Hrushikesh Betai

回答:


89

XIBをいじくる必要はなく、単にKVCを使用します。

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];

このソリューションは魅力的です(少なくともiOS 5.1では)。他のすべてのソリューションは、より多くの作業のように見えます。まだマイナス面を探しています。
Daniel

6
ナビゲーションコントローラのこのキーパス@ "navigationBar"をどのように見つけましたか。これを共有できますか
Iqbal Khan

これでアプリが拒否されないことを確認しますか?私はこれが実際にどこにも文書化されているのを見ませんでした。
Bani Uppal 2012

ありがとう!iOS 6ベータ3でもうまく動作します!@BaniUppal KVCは確かに文書化されており、見つけるのが少し難しいキーで使用しています。それがポイントだと思います。
Johannes Lund、

9
これはハッキーなAFのようです
mattsven 2014

66

iOS5以降、アップルはこれを直接行う方法を提供しています。参照

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];

iOS6で追加され、実際に@nonameliveない:developer.apple.com/library/ios/#releasenotes/General/...
Pascalius

7
はい、それはiOSの6で追加されますが、それはまた、前述のiOS 5アンアップルのエンジニアでサポートされているということWWDC 2012のセッション216で
nonamelive

37

iOS 4以降、UINibこの問題を解決するためにクラスを使用できます。

  1. カスタムUINavigationBarサブクラスを作成します。
  2. 空のxibを作成し、a UINavigationControllerを単一オブジェクトとして追加します。
  3. クラスを設定してUINavigationControllerUINavigationBarカスタムサブクラスへ。
  4. 次のいずれかの方法でルートビューコントローラーを設定します。
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

コードで:

UINib *nib = [UINib nibWithNibName:@"YourCustomXib" bundle:nil];
UINavigationController *navController = 
             [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];


今、あなたはUINavigationControllerあなたのカスタムを持っていますUINavigationBar


それ以外はどのようにrootViewControllerを設定しますか?
11

あなたは[navcontroller setViewControllers:[NSArray arrayWithObject:<YOUR_ROOT_CONTROLLER>]]を使用します
corybeare

申し訳ありませんが、あなたが使用使用[からNavController pushViewController:<YOUR_ROOT_CONTROLLER>アニメーション:NO]
coneybeare

3
私見これは、時々避けられないハックを作成する、ここで最も「ハッキー」な方法です。
David Pisoni

2
実際、奇妙な問題が1つあります。これを行うと、新しいnavigationControllerのnavigationItemが、(空の)スタックにプッシュしているviewControllerに割り当てられたnavigationItemプロパティに設定されません。
David Pisoni

26

私の知る限りでは、非標準のスタイル変更を行うために、UINavigationBarをサブクラス化する必要がある場合があります。カテゴリを使用することで回避する必要がない場合もありますが、常にそうであるとは限りません。

現在、私が知る限り、唯一の UIViewController内でカスタムUINavigationBarを設定する方法は、IBを介して(つまり、アーカイブを介して)です-おそらくそのようにすべきではありませんが、今のところ、それを使用します。

これは多くの場合問題ありませんが、IBを使用することは実際には現実的ではありません。

だから、私は3つのオプションを見ました:

  1. UINavigationBarをサブクラス化し、それをすべてIBに接続します。次に、UINavigationControllerが必要になるたびにnibをロードします。
  2. メソッド置換を使用サブクラス化ではなく、カテゴリ内で UINavigationBarの動作を変更する、または
  3. UINavigationBarをサブクラス化し、UINavigationControllerのアーカイブ/アーカイブ解除を少し変更します。

私はUINavigationControllerをプログラムで作成する必要があったので、この場合、オプション1は実現不可能でした(または、少なくとも煩わしくなりました)。2は少し危険で、私の意見では最後の手段であるので、オプション3を選択しました。

私のアプローチは、UINavigationControllerの「テンプレート」アーカイブを作成し、それをアーカイブ解除してに返すことinitWithRootViewControllerでした。

方法は次のとおりです。

IBでは、UINavigationBarに適切なクラスセットを使用してUINavigationControllerを作成しました。

次に、既存のコントローラーを取得し、を使用してそのアーカイブコピーを保存しました+[NSKeyedArchiver archiveRootObject:toFile:]。これは、アプリデリゲート内のシミュレータで行いました。

次に、-iフラグを指定して 'xxd'ユーティリティを使用し、保存したファイルからCコードを生成して、アーカイブバージョンをサブクラスに埋め込みました(xxd -i path/to/file)。

中でinitWithRootViewController私は未アーカイブの結果にそのテンプレート、および設定した自己のアーカイブ解凍します:

// This is the data from [NSKeyedArchiver archivedDataWithRootObject:controller], where
// controller is a CTNavigationController with navigation bar class set to CTNavigationBar,
// from IB.  This c code was created using 'xxd -i'
static unsigned char archived_controller[] = {
    0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd4, 0x01, 0x02, 0x03,
    ...
};
static unsigned int archived_controller_len = 682;

...

- (id)initWithRootViewController:(UIViewController *)rootViewController {
     // Replace with unarchived view controller, necessary for the custom navigation bar
     [self release];
     self = (CTNavigationController*)[NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithBytes:archived_controller length:archived_controller_len]];
     [self setViewControllers:[NSArray arrayWithObject:rootViewController]];
     return [self retain];
}

次に、カスタムナビゲーションバーが設定されているUIViewControllerサブクラスの新しいインスタンスを取得します。

UIViewController *modalViewController = [[[CTNavigationController alloc] initWithRootViewController:myTableViewController] autorelease];
[self.navigationController presentModalViewController:modalViewController animated:YES];

これにより、ナビゲーションバーとツールバーがすべて設定され、カスタムナビゲーションバークラスが配置されたモーダルUITableViewControllerが得られます。少し厄介なメソッドの置換を行う必要はありませんでした。プログラムで作業したいだけの場合は、ペン先をいじくる必要はありません。

+layerClassUINavigationController内で同等のものを見たいのです+navigationBarClassが、今のところ、これでうまくいきます。


5

「オプション1」を使用します

UINavigationControllerのみを含むnibファイルを作成します。そして、UINavigationBarクラスをカスタムクラスに設定します。

self.navigationController = [[[NSBundle mainBundle] loadNibNamed:@"navigationbar" owner:self options:nil] lastObject];

[navigationController pushViewController:rootViewController animated:YES];

したがって、nibファイルの適切な名前はloadNibNamed:@ "navigationControllerになります。実際には、バーだけでなく、nibから全体のnavigationControllerを取得しています。そうですか?
aneuryzm

これは私にとっては機能しますが、テーブルビューのオプションをクリックすると、戻るボタンが失われます。
jfisk 2012

5

Michaelのソリューションは機能しますが、NSKeyedArchiverおよび 'xxd'ユーティリティを回避できます。UINavigationControllerをサブクラス化してオーバーライドしinitWithRootViewController、カスタムNavigationController NIBを直接ロードします。

- (id) initWithRootViewController:(UIViewController *)rootViewController
{
    [self release];
    self = [[[[NSBundle mainBundle] loadNibNamed:@"CTNavigationController" owner:nil options:nil] objectAtIndex:0] retain];  
    [self setViewControllers:[NSArray arrayWithObject:rootViewController]];
    return self;
}

4

更新:object_SetClass() iOS5 GMのように使用することはできなくなりました。以下に代替ソリューションが追加されました。

NSKeyedUnarchiverを使用して、ナビゲーションバーのアーカイブ解除クラスを手動で設定します。

   MyViewController *controller = [[[MyViewController alloc] init] autorelease];
   NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:[NSKeyedArchiver archivedDataWithRootObject:controller]] autorelease];
   [unarchiver setClass:[MyNavigationBar class] forClassName:@"UINavigationBar"];
   controller = [unarchiver decodeObjectForKey:@"root"];




注:この元のソリューションはiOS5以前でのみ機能します。

私がここに投稿した素晴らしい解決策があります-navBarサブクラスをあなたのビューに直接注入してくださいUINavigationController

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}

以前の回答で示したように、あなたはこれを投稿しました-iOS5のゴールドマスターがこれを壊しました。ただし、他にも利用可能なオプションがあるため、別の方法で編集します。
11

1

iOS5ではカテゴリを使用したdrawRectの上書きが機能しなくなったため、カテゴリではなくサブクラスを使用する必要があることがわかりました。ios3.1-5.0をサポートしたい場合、あなたができる唯一の方法は、navigationbarをサブクラス化することです。


1

これらのカテゴリメソッドは危険であり、初心者には適していません。また、iOS4とiOS5の複雑さが異なるため、これは多くの人にバグを引き起こす可能性のある領域になります。これは私が使用しているシンプルなサブクラスで、iOS4.0〜iOS6.0をサポートし、非常にシンプルです。

.h

@interface XXXNavigatioNBar : UINavigationBar
@end

.m

#import "XXXNavigationBar.h"

#import <objc/runtime.h>

@implementation XXXNavigationBar

- (void) didMoveToSuperview {
    if( [self respondsToSelector: @selector(setBackgroundImage:forBarMetrics:)]) {
        //iOS5.0 and above has a system defined method -> use it
        [self setBackgroundImage: [UIImage imageNamed: @"nav-bar"]
                   forBarMetrics: UIBarMetricsDefault];
    }
    else {
        //iOS4.0 requires us to override drawRect:. BUT!!
        //If you override drawRect: on iOS5.0 the system default will break,
        //so we dynamically add this method if required
        IMP implementation = class_getMethodImplementation([self class], @selector(iOS4drawRect:));
        class_addMethod([self class], @selector(drawRect:), implementation, "v@:{name=CGRect}");
    }
}

- (void)iOS4drawRect: (CGRect) rect {
    UIImage* bg = [UIImage imageNamed:@"nav-bar-blue"];
    [bg drawInRect: rect];
}

@end

0

サブクラス化することはお勧めませんUINavigationBarクラス。ナビゲーションバーをカスタマイズする好ましい方法は、プロパティを設定して希望どおりに表示し、UIBarButtonItems内のカスタムビューとデリゲートを使用して目的の動作を実現することです。

サブクラス化が必要なことを何をしようとしていますか?

また、IBが実際にナビゲーションバーに取って代わるとは思いません。私はそれが単にデフォルトのものを表示しておらず、あなたのカスタムナビゲーションバーをサブビューとして持っていると確信しています。UINavigationController.navigationBarを呼び出す場合、バーのインスタンスを取得しますか?


2
こんにちは、ベン、ありがとう。私はUINavigationBarをサブクラス化するためのより良い方法ではないことを知っていますが、drawRect:メソッドをオーバーライドして背景画像をセットアップしたいと思います。問題は、IBを使用すると、UINavigationControllerでナビゲーションバーのクラスを変更できることですが、プログラムでは変更できません。そして、はい、IBは実際にナビゲーションバーを置き換えています:NSLog(@ "%@"、self.navigationController.navigationBar); <CustomNavigationBar:0x1806160; baseClass = UINavigationBar; フレーム=(0 20; 320 44); clipsToBounds = YES; 不透明=いいえ; autoresize = W; レイヤー= <CALayer:0x1806da0 >>
Duccio

(UINavigationBarカテゴリ技術とは異なり。)私も、これと同じ手法を使っていて、それはiOS5をして仕事に続く
デヴィッドPisoni


0

さらにobb64さんのコメントに、私は彼のトリックを使用して終了setViewControllers:animated:とコントローラを設定するrootControllerためのnavigationControllerペン先からロードされました。これが私が使っているコードです:

- (void) presentModalViewControllerForClass: (Class) a_class {
  UINavigationController *navController = [[[NSBundle mainBundle] loadNibNamed: @"CustomNavBar" owner:self options:nil] lastObject];

  LoginSignupBaseViewController *controller = [[a_class alloc] initWithNibName: nil bundle: nil];
  controller.navigationController = navController;
  [navController setViewControllers: A(controller) animated: NO];

  [self presentModalViewController: navController animated: YES];

  [controller release];
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.