Programowo ustaw niestandardową podklasę UINavigationBar w UINavigationController

92

Czy ktoś wie, jak mogę użyć mojej niestandardowej podklasy, UINavigationBarjeśli utworzę wystąpienie UINavigationControllerprogramowo (bez IB)?

Przeciągnij UINavigationControllerw IB pokaż mi pod Paskiem Nawigacyjnym i używając Inspekcji tożsamości Mogę zmienić typ klasy i ustawić własną podklasę, UINavigationBarale programowo nie mogę, navigationBarwłaściwość kontrolera nawigacji jest tylko do odczytu ...

Co mam zrobić, aby programowo dostosować pasek nawigacji? Czy IB jest bardziej „potężny” niż „kod”? Wierzyłem, że wszystko, co można zrobić w IB, można również zrobić programowo.

Duccio
źródło
czy udało Ci się znaleźć rozwiązanie gdzie indziej?
prendio2
czy masz jakąś odpowiedź na ten temat?
Hrushikesh Betai

Odpowiedzi:

89

Nie musisz grzebać w XIB, po prostu użyj KVC.

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
Fred
źródło
To rozwiązanie działa jak urok (przynajmniej na iOS 5.1). Wszystkie inne rozwiązania wydają się o wiele bardziej skuteczne. Wciąż szukam wad.
Daniel,
6
jak znalazłeś tę ścieżkę klucza @ „navigationBar” dla kontrolera nawigacji. czy możesz to udostępnić
Iqbal Khan
Czy na pewno nie spowoduje to odrzucenia aplikacji? Właściwie nie widziałem tego nigdzie udokumentowanego.
Bani Uppal
Dzięki! Działa również świetnie w iOS 6 beta 3! @BaniUppal KVC jest z pewnością udokumentowane, używamy go tylko z kluczem, który jest nieco trudny do znalezienia. Myślę, że to jest główny punkt.
Johannes Lund,
9
To wygląda na hacky AF
mattsven
66

Od iOS5 firma Apple udostępnia metodę bezpośredniej obsługi. Odniesienie

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
Pascalius
źródło
@nonamelive Właściwie nie, dodano w iOS6: developer.apple.com/library/ios/#releasenotes/General/…
Pascalius
7
Tak, jest dodany w iOS 6, ale jest również obsługiwany w iOS 5. Inżynier Apple wspomniał, że w WWDC 2012 Session 216.
nonamelive
37

Od wersji iOS 4 możesz użyć UINibklasy, aby rozwiązać ten problem.

  1. Utwórz własną UINavigationBarpodklasę.
  2. Utwórz pusty xib, dodaj UINavigationControllerjako pojedynczy obiekt.
  3. Ustaw klasę dla UINavigationController„s UINavigationBardo swojej niestandardowej podklasy.
  4. Ustaw główny kontroler widoku za pomocą jednej z następujących metod:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

W kodzie:

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


Teraz masz UINavigationControllerze swoim niestandardowym UINavigationBar.

gorbster
źródło
Z wyjątkiem tego, jak ustawić rootViewController?
memmons
używasz [navcontroller setViewControllers: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]]
coneybeare
przepraszam, używasz [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> animowany: NIE]
coneybeare
3
IMHO to najmniej "hakerski" sposób na zrobienie tego czasami nieuniknionego włamania.
David Pisoni
2
Właściwie jeden dziwny problem. Kiedy to zrobię, navigationItem nowego navigationController nie jest ustawiane na właściwość navigationItem przypisaną do viewController, którą wypycham na (pusty) stos.
David Pisoni
26

O ile wiem, czasami rzeczywiście konieczne jest utworzenie podklasy UINavigationBar w celu wykonania niestandardowej zmiany stylizacji. Czasami można tego uniknąć, używając kategorii , ale nie zawsze.

Obecnie, o ile wiem, jedynym sposobem ustawienia niestandardowego UINavigationBar w kontrolerze UIViewController jest IB (czyli archiwum) - prawdopodobnie nie powinno tak być, ale na razie musimy z tym żyć.

Często jest to w porządku, ale czasami użycie IB nie jest naprawdę wykonalne.

Widziałem więc trzy opcje:

  1. Podklasa UINavigationBar i podłącz to wszystko do IB, a następnie zajmij się ładowaniem stalówki za każdym razem, gdy chcę UINavigationController,
  2. Użyj zamiany metody w kategorii, aby zmienić zachowanie UINavigationBar zamiast podklasy lub
  3. Podklasa UINavigationBar i trochę pomieszaj przy archiwizowaniu / cofaniu archiwizacji UINavigationController.

Opcja 1 była dla mnie niewykonalna (lub przynajmniej zbyt irytująca) w tym przypadku, ponieważ musiałem programowo utworzyć UINavigationController, 2 jest trochę niebezpieczna i moim zdaniem jest bardziej ostateczną opcją, więc wybrałem opcję 3.

Moje podejście polegało na utworzeniu archiwum „szablonu” kontrolera UINavigationController i przywróceniu go z archiwum, zwracając go w formacie initWithRootViewController.

Oto jak:

W IB stworzyłem UINavigationController z odpowiednim zestawem klas dla UINavigationBar.

Następnie wziąłem istniejący kontroler i zapisałem jego zarchiwizowaną kopię za pomocą +[NSKeyedArchiver archiveRootObject:toFile:]. Właśnie to zrobiłem w ramach delegata aplikacji, w symulatorze.

Następnie użyłem narzędzia „xxd” z flagą -i, aby wygenerować kod c z zapisanego pliku, aby osadzić zarchiwizowaną wersję w mojej podklasie ( xxd -i path/to/file).

Wewnątrz initWithRootViewControllerrozpakowuję ten szablon i ustawiam się na wynik przywrócenia z archiwum:

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

Następnie mogę po prostu pobrać nowe wystąpienie mojej podklasy UIViewController, która ma ustawiony niestandardowy pasek nawigacji:

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

Daje mi to modalny UITableViewController ze skonfigurowanym paskiem nawigacji i paskiem narzędzi oraz z niestandardową klasą paska nawigacji. Nie musiałem wykonywać żadnej nieco nieprzyjemnej zamiany metody i nie muszę majstrować przy końcówkach, gdy naprawdę chcę pracować programowo.

Chciałbym zobaczyć odpowiednik +layerClassw UINavigationController - +navigationBarClass- ale na razie to działa.

Michael Tyson
źródło
5

Używam „opcji 1”

Utwórz plik nib zawierający tylko element UINavigationController. I ustaw klasę UINavigationBar na moją klasę niestandardową.

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

[navigationController pushViewController:rootViewController animated:YES];
obb64
źródło
więc właściwa nazwa pliku nib byłaby loadNibNamed: @ "navigationController? W rzeczywistości otrzymujesz cały navigationController ze stalówki, a nie tylko pasek. Dobrze?
aneuryzm
to działa dla mnie, ale kiedy klikam dowolną opcję w moim widoku tabeli, tracę przycisk Wstecz.
jfisk,
5

Rozwiązanie Michaela działa, ale możesz uniknąć NSKeyedArchiver i narzędzia „xxd”. Po prostu podklasę UINavigationController i zastąpienie initWithRootViewController, ładując bezpośrednio niestandardowy plik NIB NavigationController:

- (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ów
źródło
4

Aktualizacja: Używanie object_SetClass()nie działa już tak, jakby iOS5 GM. Alternatywne rozwiązanie zostało dodane poniżej.

Użyj NSKeyedUnarchiver, aby ręcznie ustawić klasę przywracania z archiwum dla paska nawigacji.

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




Uwaga: to oryginalne rozwiązanie działa tylko przed iOS5:

Jest świetne rozwiązanie, które zamieściłem tutaj - wstrzyknij podklasę navBar bezpośrednio do swojego widoku UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}
memmons
źródło
Jak wskazałem w poprzedniej odpowiedzi, opublikowałeś to na - złoty mistrz iOS5 zepsuł to. Są jednak dostępne inne opcje, więc edytuję inną metodą.
memmons
1

Jednym ze scenariuszy, które odkryłem, że musimy użyć podklasy zamiast kategorii, jest ustawienie koloru tła paska nawigacji z obrazem wzoru, ponieważ w iOS5 nadpisywanie drawRect za pomocą kategorii już nie działa. Jeśli chcesz obsługiwać ios3.1-5.0, jedynym sposobem, w jaki możesz to zrobić, jest podklasa paska nawigacji.

James
źródło
1

Te metody kategorii są niebezpieczne i nie są dla nowicjuszy. Również komplikacje związane z różnymi systemami iOS4 i iOS5 sprawiają, że jest to obszar, który może powodować błędy dla wielu osób. Oto prosta podklasa, której używam, która obsługuje iOS4.0 ~ iOS6.0 i jest bardzo prosta.

.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
źródło
0

Nie zaleca się tworzenia podklasy tej UINavigationBarklasy. Preferowanym sposobem dostosowywania paska nawigacji jest ustawienie jego właściwości tak, aby wyglądał tak, jak chcesz, i używanie niestandardowych widoków w UIBarButtonItems wraz z delegatem w celu uzyskania żądanego zachowania.

Co próbujesz zrobić, co wymaga podklasy?

Nie sądzę też, że IB faktycznie zastępuje pasek nawigacyjny. Jestem prawie pewien, że po prostu nie wyświetla domyślnego i ma niestandardowy pasek nawigacyjny jako widok podrzędny. Jeśli wywołasz UINavigationController.navigationBar, czy otrzymasz wystąpienie swojego paska?

Ben S.
źródło
2
Cześć Ben, dzięki. Wiem, że nie jest lepszym sposobem na podklasę UINavigationBar, ale chciałbym ustawić obraz tła zastępujący metodę drawRect:. Problem polega na tym, że używając IB mogę zmienić klasę paska nawigacji w UINavigationController, ale programowo nie mogę. I tak, IB faktycznie zastępuje pasek nawigacji: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; ramka = (0 20; 320 44); clipsToBounds = YES; nieprzezroczysty = NIE; autoresize = W; layer = <CALayer: 0x1806da0 >>
Duccio,
Używam również tej samej techniki i nadal działa w iOS5 (w przeciwieństwie do techniki UINavigationBar Category).
David Pisoni
0

W nawiązaniu do obb64 w komentarzu, skończyło się używając swojego triku z setViewControllers:animated:ustaw jako kontroler rootControllerdla navigationControllerładowane z nib. Oto kod, którego używam:

- (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
źródło