web-dev-qa-db-ja.com

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

UINavigationBarをプログラムで(IBなしで)インスタンス化する場合、UINavigationControllerのカスタムサブクラスを使用する方法を知っている人はいますか?

IBのUINavigationControllerをドラッグしてナビゲーションバーの下に表示し、ID検査を使用してクラスタイプを変更し、UINavigationBarの独自のサブクラスを設定できますが、プログラムではnavigationBar Navigation Controllerのプロパティは読み取り専用です...

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

92
Duccio

KIBを使用するだけでXIBをいじる必要はありません。

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

IOS5以降、Appleはこれを直接行う方法を提供します。 リファレンス

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

IOS 4以降、UINibクラスを使用してこの問題を解決できます。

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

コード内:

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


これで、カスタムUINavigationControllerUINavigationBarが得られました。

37
gorbster

私の知る限り、UINavigationBarをサブクラス化して、非標準のスタイル変更を行う必要がある場合があります。 categories を使用することで、そうする必要を回避できる場合がありますが、常にではありません。

現在、私が知る限り、UIViewController内でカスタムUINavigationBarを設定するonly方法は、IB経由(つまり、アーカイブ経由)です。おそらくそのようにするべきではありませんが、今のところ、私たちはそれで生きなければなりません。

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

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

  1. UINavigationBarをサブクラス化し、それをすべてIBにフックしてから、UINavigationControllerが必要になるたびにペン先をロードすることを考えます。
  2. サブクラス化ではなく、カテゴリ内で method replacement を使用してUINavigationBarの動作を変更します。または
  3. UINavigationBarをサブクラス化し、UINavigationControllerのアーカイブ/アーカイブ解除について少しいじります。

プログラム1でUINavigationControllerを作成する必要があるため、この場合、オプション1は実行不可能(または少なくとも面倒)でした。2は少し危険で、私の意見では最後のリゾートオプションなので、オプション3を選択しました。

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

方法は次のとおりです。

IBでは、UINavigationBarに適切なクラスを設定してUINavigationControllerを作成しました。

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

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

initWithRootViewController内で、そのテンプレートをアーカイブ解除し、unarchiveの結果にselfを設定します。

// 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が提供されます。少し厄介なメソッドの置換を行う必要はありませんでしたし、プログラムで作業したいだけなら、ペン先をいじくる必要はありません。

UINavigationController内の+layerClassに相当するもの-+navigationBarClass-を見たいのですが、今のところ、これは動作します。

26
Michael Tyson

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;
}
5
100grams

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

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

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

[navigationController pushViewController:rootViewController animated:YES];
5
obb64

更新: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より前のバージョンでのみ動作します:

私が投稿した素晴らしい解決策があります here -navBarサブクラスをビューに直接挿入しますUINavigationController

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

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

これらのカテゴリメソッドは危険であり、初心者向けではありません。また、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
1
Paul de Lange

IOS5ではcategoryを使用してdrawRectを上書きすることはもはや機能しないため、カテゴリではなくサブクラスを使用する必要があることがわかった1つのシナリオは、パターン画像でナビゲーションバーの背景色を設定することです。 ios3.1-5.0をサポートする場合、できる唯一の方法は、ナビゲーションバーをサブクラス化することです。

1
james

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

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

また、IBが実際にナビゲーションバーを置き換えているとは思いません。私はそれがデフォルトのものを表示していないだけで、サブビューとしてカスタムナビゲーションバーを持っていると確信しています。 UINavigationController.navigationBarを呼び出すと、バーのインスタンスを取得できますか?

0
Ben S

背景画像を変更するためだけにnavBarをサブクラス化する場合-iOS 5では必要ありません。このようなメソッドがありますsetBackgroundImage

0
off

Obb64のコメントに加えて、私は彼のトリックをsetViewControllers:animated:ペン先から読み込まれたrootControllernavigationControllerとしてコントローラーを設定します。私が使用しているコードは次のとおりです。

- (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];
}
0
smtlaissezfaire