UINavigationController'da programlı olarak UINavigationBar'ın özel bir alt sınıfını ayarlayın


92

Programlı olarak (IB olmadan) UINavigationBarsomutlaştırırsam , özel alt sınıfımı nasıl kullanabileceğimi bilen var mı UINavigationController?

UINavigationControllerIB'de bir sürükleyin bana bir Gezinme Çubuğu altında göster ve Kimlik Denetimi'ni kullanarak sınıf türünü değiştirebilir ve kendi alt sınıfımı ayarlayabilirim UINavigationBarancak program aracılığıyla yapamıyorum, navigationBarGezinme Denetleyicisinin özelliği salt okunur ...

Gezinme çubuğunu programlı olarak özelleştirmek için ne yapmalıyım? IB, "kod" dan daha "güçlü" müdür? IB'de yapılabilecek her şeyin programlı olarak da yapılabileceğine inandım.


başka bir yerde bir çözüm bulma şansınız oldu mu?
prendio2

bununla ilgili herhangi bir cevap alıyor musun?
Hrushikesh Betai

Yanıtlar:


89

XIB ile uğraşmanıza gerek yok, sadece KVC kullanın.

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

Bu çözüm bir cazibe gibi çalışıyor (en azından iOS 5.1'de). Diğer tüm çözümler daha fazla iş gibi görünüyor. Hala olumsuz tarafı arıyorum.
Daniel

6
bu anahtar yolunu nasıl buldunuz @ navigasyon denetleyicisi için "navigationBar". paylaşır mısın
Iqbal Khan

Bunun Uygulama reddiyle sonuçlanmayacağından emin misiniz? Aslında bunun hiçbir yerde belgelendiğini görmedim.
Bani Uppal

Teşekkürler! İOS 6 beta 3'te de harika çalışıyor! @BaniUppal KVC kesinlikle belgelenmiştir, onu sadece bulması biraz zor bir anahtarla kullanıyoruz. Sanırım asıl nokta bu.
Johannes Lund

9
Bu hacky AF gibi görünüyor
mattsven

66

İOS5'ten beri, apple bunu doğrudan yapmak için bir yöntem sağlar. Referans

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

@nonamelive Aslında değil, iOS6'da eklendi: developer.apple.com/library/ios/#releasenotes/General/…
Pascalius

7
Evet, iOS 6'da eklendi, ancak iOS 5'te de destekleniyor. Bir Apple mühendisi WWDC 2012 Oturum
216'da bundan

37

İOS 4 itibariyle, UINibbu sorunu çözmeye yardımcı olması için sınıfı kullanabilirsiniz .

  1. Özel UINavigationBaralt sınıfınızı oluşturun .
  2. Boş bir xib oluşturun UINavigationController, tek nesne olarak a ekleyin .
  3. Özel alt sınıfınız için UINavigationController'ler UINavigationBariçin sınıfı ayarlayın .
  4. Kök görünüm denetleyicinizi şu yöntemlerden biriyle ayarlayın:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

Kodda:

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


Şimdi UINavigationControllergeleneğinizle bir var UINavigationBar.


Bunun dışında rootViewController'ı nasıl ayarlarsınız?
memmons

[navcontroller setViewControllers: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]] kullanıyorsunuz
coneybeare

üzgünüm, [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> animated: NO] kullanıyorsunuz
coneybeare

3
IMHO Bu, bazen kaçınılmaz olan bu hack'i yapmanın buradaki en az "hile" yoludur.
David Pisoni

2
Aslında tuhaf bir sorun. Bunu yaptığımda, yeni navigationController'ın navigationItem'i, (boş) yığına ittiğim viewController'a atanan navigationItem özelliğine ayarlanmıyor.
David Pisoni

26

Anlayabildiğim kadarıyla, bazen bazı standart olmayan yeniden biçimlendirme yapmak için UINavigationBar'ın alt sınıfına girmek gerçekten gerekli. Bazen kategorileri kullanarak bunu yapmaktan kaçınmak mümkündür , ancak her zaman değil.

Şu anda, bildiğim kadarıyla, bir UIViewController içinde özel bir UINavigationBar ayarlamanın tek yolu IB (yani bir arşiv yoluyla) - muhtemelen bu şekilde olmamalı, ama şimdilik bununla yaşamalıyız.

Bu genellikle iyidir, ancak bazen IB kullanmak gerçekten mümkün değildir.

Bu yüzden üç seçenek gördüm:

  1. UINavigationBar alt sınıfını seçin ve hepsini IB'ye bağlayın, ardından her UINavigationController istediğimde ucu yükleme konusunda kafa yorun,
  2. Alt sınıflandırma yerine UINavigationBar davranışını değiştirmek için bir kategori içinde yöntem değiştirmeyi kullanın veya
  3. UINavigationBar alt sınıfını seçin ve UINavigationController'ı arşivleme / arşivden çıkarma ile ilgili biraz uğraşın.

UINavigationController'ı programlı olarak oluşturmam gerektiğinden, 1. Seçenek benim için uygulanabilir değildi (veya en azından çok can sıkıcıydı), 2 biraz tehlikeli ve bence son çare seçeneğiydi, bu yüzden 3. seçeneği seçtim.

Benim yaklaşımım, bir UINavigationController için bir 'şablon' arşivi oluşturmak ve onu arşivden çıkarmak ve onu geri döndürmekti initWithRootViewController.

Bunu nasıl yapacağınız aşağıda açıklanmıştır:

IB'de, UINavigationBar için uygun sınıf kümesiyle bir UINavigationController oluşturdum.

Ardından, mevcut denetleyiciyi aldım ve kullanarak arşivlenmiş bir kopyasını kaydettim +[NSKeyedArchiver archiveRootObject:toFile:]. Bunu simülatörde uygulama temsilcisi içinde yaptım.

Daha sonra arşivlenmiş sürümü alt sınıfıma ( xxd -i path/to/file) gömmek için kaydedilen dosyadan c kodu üretmek için -i bayrağıyla 'xxd' yardımcı programını kullandım .

İçinde initWithRootViewControllerbu şablonu arşivden çıkarıyorum ve kendini arşivden çıkarmanın sonucuna ayarlıyorum:

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

Ardından, özel gezinti çubuğu ayarına sahip olan UIViewController alt sınıfımın yeni bir örneğini alabilirim:

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

Bu bana, bir gezinme çubuğu ve araç çubuğu tümüyle ayarlanmış ve özel gezinme çubuğu sınıfı yerinde olan modal bir UITableViewController veriyor. Biraz çirkin bir yöntem değişimi yapmam gerekmedi ve gerçekten sadece programatik olarak çalışmak istediğimde uçlarla uğraşmak zorunda kalmıyorum.

+layerClassUINavigationController - +navigationBarClass- içindeki eşdeğerini görmek isterdim ama şimdilik bu çalışıyor.


5

"1. seçenek" kullanıyorum

İçinde yalnızca UINavigationController bulunan bir uç dosyası oluşturun. Ve UINavigationBar Sınıfını özel Sınıfıma ayarlayın.

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

[navigationController pushViewController:rootViewController animated:YES];

bu nedenle, uç dosyasının uygun adı loadNibNamed: @ "navigationController olur mu? Aslında tüm navigationController'ı sadece çubuktan değil,
uçtan alıyorsunuz

bu benim için çalışıyor, ancak tablo görünümümde herhangi bir seçeneğe tıkladığımda geri düğmesini kaybediyorum.
jfisk

5

Michael'ın çözümü işe yarıyor, ancak NSKeyedArchiver ve 'xxd' yardımcı programından kaçınabilirsiniz. Simply Subclass UINavigationController ve override initWithRootViewController, özel NavigationController NIB'nizi doğrudan yükleyerek:

- (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

Güncelleme: Kullanmak object_SetClass()artık iOS5 GM gibi çalışmıyor. Aşağıya alternatif bir çözüm eklenmiştir.

Gezinti çubuğunun arşivden çıkarma sınıfını manuel olarak ayarlamak için NSKeyedUnarchiver'ı kullanın.

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




Not: Bu orijinal çözüm yalnızca iOS5 öncesi çalışır:

Burada yayınladığım harika bir çözüm var - navBar alt sınıfını doğrudan görünümünüze enjekte edin UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

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

Önceki cevapta belirttiğim gibi, bunu yayınladınız - iOS5'in altın ustası bunu bozdu. Yine de başka seçenekler de var, bu yüzden alternatif bir yöntemle düzenleyeceğim.
memmons

1

Kategori yerine alt sınıf kullanmamız gerektiğini bulduğum bir senaryo, gezinme çubuğu arka plan rengini desen görüntüsü ile ayarlamaktır, çünkü iOS5'te drawRect'in üzerine kategori kullanarak yazmak artık çalışmıyor. İos3.1-5.0'ı desteklemek istiyorsanız, yapabileceğiniz tek yol alt sınıf gezinme çubuğudur.


1

Bu kategori yöntemleri tehlikelidir ve acemiler için değildir. Ayrıca iOS4 ve iOS5'teki karmaşıklığın farklı olması, bunu birçok insan için hatalara neden olabilecek bir alan haline getiriyor. İşte iOS4.0 ~ iOS6.0'ı destekleyen ve çok basit olan, kullandığım basit bir alt sınıf.

.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

O oluyor önerilmez alt sınıfı için UINavigationBarsınıf. Gezinme çubuğunu özelleştirmenin tercih edilen yolu, özelliklerini istediğiniz gibi görünecek şekilde ayarlamak ve istenen davranışı elde etmek için bir temsilciyle birlikte UIBarButtonItems içinde özel görünümler kullanmaktır.

Alt sınıflandırma gerektiren ne yapmaya çalışıyorsunuz?

Ayrıca, IB'nin aslında gezinme çubuğunun yerini aldığını düşünmüyorum. Eminim sadece varsayılan olanı göstermiyor ve özel gezinme çubuğunuzu bir alt görünüm olarak içeriyor. UINavigationController.navigationBar'ı çağırırsanız, çubuğunuzun bir örneğini alır mısınız?


2
Selam Ben, teşekkürler. UINavigationBar'ı alt sınıflara ayırmanın daha iyi bir yolu olmadığını biliyorum, ancak drawRect: yöntemini geçersiz kılan bir arka plan görüntüsü kurmak istiyorum. Sorun şu ki, IB'yi kullanarak bir UINavigationController'deki Navigasyon Çubuğunun sınıfını değiştirebilirim, ancak program aracılığıyla yapamam. Ve evet, IB aslında gezinti çubuğunun yerini alıyor: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; çerçeve = (0 20; 320 44); clipsToBounds = EVET; opak = HAYIR; otomatik yeniden boyutlandırma = W; layer = <CALayer: 0x1806da0 >>
Duccio

Ben de aynı tekniği kullanıyorum ve iOS5'te çalışmaya devam ediyor (UINavigationBar Kategori tekniğinin aksine)
David Pisoni


0

Obb64 yorumuna Ayrıca, ben de onun numarasını kullanarak sona erdi setViewControllers:animated:olarak kontrolörü ayarlamak rootControlleriçin navigationControlleruç yüklenen. İşte kullandığım kod:

- (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];
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.