Setel subkelas khusus UINavigationBar di UINavigationController secara terprogram

92

Adakah yang tahu bagaimana saya bisa menggunakan subclass kustom saya UINavigationBarjika saya instantiate secara UINavigationControllerterprogram (tanpa IB)?

Seret UINavigationControllerdi IB tunjukkan saya di bawah Bilah Navigasi dan menggunakan Identity Inspectory saya dapat mengubah jenis kelas dan mengatur subkelas saya sendiri UINavigationBartetapi secara terprogram saya tidak bisa, navigationBarproperti Pengontrol Navigasi hanya-baca ...

Apa yang harus saya lakukan untuk menyesuaikan bilah navigasi secara terprogram? Apakah IB lebih "kuat" daripada "kode"? Saya percaya semua yang bisa dilakukan di IB bisa dilakukan juga secara terprogram.

Duccio
sumber
apakah Anda beruntung menemukan solusi di tempat lain?
prendio2
apakah kamu mendapat jawaban tentang itu?
Hrushikesh Betai

Jawaban:

89

Anda tidak perlu membuang-buang waktu dengan XIB, cukup gunakan KVC.

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
Fred
sumber
Solusi ini berfungsi seperti pesona (setidaknya di iOS 5.1). Semua solusi lain sepertinya lebih berhasil. Masih mencari sisi negatifnya.
Daniel
6
bagaimana Anda menemukan jalur kunci ini @ "navigationBar" untuk pengontrol navigasi. dapatkah Anda membagikan ini
Iqbal Khan
Apakah Anda yakin ini tidak akan mengakibatkan penolakan Aplikasi? Sebenarnya saya tidak melihat ini didokumentasikan di mana pun.
Bani Uppal
Terima kasih! Bekerja dengan baik di iOS 6 beta 3 juga! @BaniUppal KVC sudah pasti didokumentasikan, kami hanya menggunakannya dengan kunci yang agak sulit ditemukan. Saya pikir itu adalah poin utamanya.
Johannes Lund
9
AF ini tampaknya
hacky
66

Sejak iOS5, apple menyediakan metode untuk melakukan ini secara langsung. Referensi

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
Pascalius
sumber
@nonamelive Sebenarnya tidak, ditambahkan di iOS6: developer.apple.com/library/ios/#releasenotes/General/…
Pascalius
7
Ya, ini ditambahkan di iOS 6, tetapi juga didukung di iOS 5. Seorang insinyur Apple menyebutkannya di WWDC 2012 Session 216.
nonamelive
37

Mulai iOS 4, Anda dapat menggunakan UINibkelas tersebut untuk membantu memecahkan masalah ini.

  1. Buat UINavigationBarsubclass kustom Anda .
  2. Buat xib kosong, tambahkan a UINavigationControllersebagai objek tunggal.
  3. Mengatur kelas untuk UINavigationController's UINavigationBaruntuk subclass kustom Anda.
  4. Setel pengontrol tampilan root Anda melalui salah satu metode berikut:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

Dalam kode:

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


Sekarang Anda sudah memiliki UINavigationControllerkebiasaan Anda UINavigationBar.

gorbster
sumber
Kecuali bagaimana Anda menyetel rootViewController?
memmons
Anda menggunakan [navcontroller setViewControllers: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]]
coneybeare
maaf, Anda menggunakan Anda menggunakan [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> animasi: TIDAK]
coneybeare
3
IMHO ini adalah cara yang paling tidak "meretas" di sini untuk membuat peretasan yang terkadang tidak dapat dihindari.
David Pisoni
2
Sebenarnya, ada satu masalah yang aneh. Ketika saya melakukan ini navigationItem dari navigationController baru tidak disetel ke properti navigationItem yang ditetapkan ke viewController yang saya dorong ke stack (kosong).
David Pisoni
26

Sejauh yang saya tahu, terkadang memang perlu untuk membuat subclass UINavigationBar, untuk melakukan beberapa pengaturan ulang gaya yang tidak standar. Terkadang mungkin untuk menghindari keharusan melakukannya dengan menggunakan kategori , tetapi tidak selalu.

Saat ini, sejauh yang saya tahu, satu - satunya cara untuk menyetel UINavigationBar khusus dalam UIViewController adalah melalui IB (yaitu, melalui arsip) - mungkin seharusnya tidak seperti itu, tetapi untuk saat ini, kita harus menerimanya.

Ini sering kali baik-baik saja, tetapi terkadang menggunakan IB tidak terlalu memungkinkan.

Jadi, saya melihat tiga opsi:

  1. Subclass UINavigationBar dan kaitkan semuanya di IB, lalu buang waktu setiap kali saya menginginkan UINavigationController,
  2. Gunakan penggantian metode dalam kategori untuk mengubah perilaku UINavigationBar, bukan subclassing, atau
  3. Subclass UINavigationBar dan lakukan sedikit penyia-nyiaan dengan pengarsipan / pengarsipan UINavigationController.

Opsi 1 tidak layak (atau setidaknya terlalu menjengkelkan) bagi saya dalam hal ini, karena saya perlu membuat UINavigationController secara terprogram, 2 sedikit berbahaya dan lebih merupakan opsi terakhir menurut saya, jadi saya memilih opsi 3.

Pendekatan saya adalah membuat arsip 'template' dari UINavigationController, dan membatalkan pengarsipannya, mengembalikannya ke dalam initWithRootViewController.

Begini caranya:

Dalam IB, saya membuat UINavigationController dengan kelas yang sesuai untuk UINavigationBar.

Kemudian, saya mengambil pengontrol yang ada, dan menyimpan salinan yang diarsipkan menggunakan +[NSKeyedArchiver archiveRootObject:toFile:]. Saya baru saja melakukan ini di dalam delegasi aplikasi, di simulator.

Saya kemudian menggunakan utilitas 'xxd' dengan flag -i, untuk menghasilkan kode c dari file yang disimpan, untuk menyematkan versi yang diarsipkan di subclass saya ( xxd -i path/to/file).

Di dalam initWithRootViewControllersaya membatalkan pengarsipan template itu, dan mengatur diri ke hasil dari pengarsipan:

// 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];
}

Kemudian, saya bisa mengambil contoh baru dari subkelas UIViewController saya yang memiliki bilah navigasi khusus:

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

Ini memberi saya modal UITableViewController dengan bilah navigasi dan bilah alat yang sudah disiapkan, dan dengan kelas bilah navigasi khusus di tempatnya. Saya tidak perlu melakukan penggantian metode yang sedikit menjengkelkan, dan saya tidak perlu mengotak-atik nib ketika saya benar-benar hanya ingin bekerja secara terprogram.

Saya ingin melihat padanan +layerClassdalam UINavigationController - +navigationBarClass- tetapi untuk saat ini, ini berfungsi.

Michael Tyson
sumber
5

Saya menggunakan "opsi 1"

Buat file nib hanya dengan UINavigationController di dalamnya. Dan atur Kelas UINavigationBar ke Kelas kustom saya.

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

[navigationController pushViewController:rootViewController animated:YES];
obb64.dll
sumber
jadi nama yang sesuai dari file nib adalah loadNibNamed: @ "navigationController? Anda sebenarnya mendapatkan seluruh navigationController dari nib dan bukan hanya dari
bilahnya
ini berfungsi untuk saya, tetapi ketika saya mengklik opsi apa pun di tampilan tabel saya, saya kehilangan tombol kembali.
jfisk
5

Solusi Michael berfungsi, tetapi Anda dapat menghindari NSKeyedArchiver dan utilitas 'xxd'. Cukup Subclass UINavigationController dan timpa initWithRootViewController, memuat NIB NavigationController kustom Anda secara langsung:

- (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;
}
100 gram
sumber
4

Memperbarui: Menggunakan object_SetClass()tidak lagi berfungsi seolah-olah iOS5 GM. Solusi alternatif telah ditambahkan di bawah.

Gunakan NSKeyedUnarchiver untuk secara manual menyetel kelas pembatalan pengarsipan untuk bilah navigasi.

   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"];




Catatan: Solusi asli ini hanya berfungsi sebelum iOS5:

Ada solusi hebat, yang saya posting di sini - menyuntikkan subkelas navBar langsung ke tampilan Anda UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}
memmons
sumber
Seperti yang saya tunjukkan dalam jawaban sebelumnya yang Anda posting ini - master emas iOS5 memecahkan ini. Ada opsi lain yang tersedia, jadi saya akan mengedit dengan metode alternatif.
memmons
1

Satu skenario yang saya temukan bahwa kita perlu menggunakan subclass daripada kategori adalah mengatur warna latar belakang navigationbar dengan gambar pola, karena di iOS5 menimpa drawRect menggunakan kategori tidak berfungsi lagi. Jika Anda ingin mendukung ios3.1-5.0, satu-satunya cara yang dapat Anda lakukan adalah dengan subclass navigationbar.

james
sumber
1

Metode kategori ini berbahaya dan bukan untuk pemula. Selain itu, kerumitan dengan iOS4 dan iOS5 yang berbeda, menjadikan area ini dapat menyebabkan bug bagi banyak orang. Berikut adalah subclass sederhana yang saya gunakan yang mendukung iOS4.0 ~ iOS6.0 dan sangat sederhana.

.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
Paul de Lange
sumber
0

Tidak disarankan untuk membuat subkelas UINavigationBarkelas. Cara yang disukai untuk menyesuaikan bilah navigasi adalah dengan mengatur propertinya agar muncul seperti yang Anda inginkan dan menggunakan tampilan khusus di dalam UIBarButtonItems bersama dengan delegasi untuk mendapatkan perilaku yang diinginkan.

Apa yang Anda coba lakukan yang membutuhkan subclass?

Juga, menurut saya IB tidak benar-benar menggantikan bilah navigasi. Saya cukup yakin itu tidak menampilkan yang default dan memiliki bilah navigasi kustom Anda sebagai subview. Jika Anda memanggil UINavigationController.navigationBar, apakah Anda mendapatkan instance dari bilah Anda?

Ben S
sumber
2
Hai Ben, terima kasih. Saya tahu ini bukan cara yang lebih baik untuk subclass UINavigationBar tapi saya ingin mengatur gambar latar belakang yang menggantikan metode drawRect :. Masalahnya adalah bahwa menggunakan IB saya dapat mengubah kelas Bilah Navigasi di UINavigationController tetapi secara terprogram saya tidak bisa. Dan ya, IB sebenarnya mengganti bilah navigasi: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; bingkai = (0 20; 320 44); clipsToBounds = YA; buram = TIDAK; autoresize = W; layer = <CALayer: 0x1806da0 >>
Duccio
Saya juga menggunakan teknik yang sama, dan terus berfungsi di iOS5 (tidak seperti teknik Kategori UINavigationBar.)
David Pisoni
0

Selanjutnya untuk komentar obb64, saya akhirnya menggunakan triknya dengan setViewControllers:animated:mengatur pengontrol sebagai rootControlleruntuk navigationControllerdimuat dari nib. Inilah kode yang saya gunakan:

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