Legen Sie programmgesteuert eine benutzerdefinierte Unterklasse von UINavigationBar in UINavigationController fest

92

Weiß jemand, wie ich meine benutzerdefinierte Unterklasse verwenden kann, UINavigationBarwenn ich UINavigationControllerprogrammgesteuert (ohne IB) instanziiere ?

Ziehen Sie eine UINavigationControllerin IB, zeigen Sie mir eine unter Navigationsleiste und verwenden Sie Identity Inspectory. Ich kann den Klassentyp ändern und meine eigene Unterklasse festlegen, UINavigationBaraber programmgesteuert kann ich nicht, die navigationBarEigenschaft von Navigation Controller ist schreibgeschützt ...

Was kann ich tun, um die Navigationsleiste programmgesteuert anzupassen? Ist IB "leistungsfähiger" als "Code"? Ich glaubte, dass alles, was in IB gemacht werden kann, auch programmatisch gemacht werden kann.

Duccio
quelle
Hattest du Glück, woanders eine Lösung zu finden?
prendio2
Hast du eine Antwort darauf?
Hrushikesh Betai

Antworten:

89

Sie müssen sich nicht mit dem XIB herumschlagen, sondern verwenden einfach KVC.

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
Fred
quelle
Diese Lösung funktioniert wie ein Zauber (zumindest unter iOS 5.1). Alle anderen Lösungen scheinen viel mehr Arbeit zu sein. Immer noch auf der Suche nach dem Nachteil.
Daniel
6
Wie haben Sie diesen Schlüsselpfad @ "navigationBar" für den Navigationscontroller gefunden? können Sie dies teilen
Iqbal Khan
Sind Sie sicher, dass dies nicht zur Ablehnung der App führt? Ich habe das eigentlich nirgendwo dokumentiert gesehen.
Bani Uppal
Vielen Dank! Funktioniert auch hervorragend in iOS 6 Beta 3! @BaniUppal KVC ist sicherlich dokumentiert, wir verwenden es nur mit einem Schlüssel, der etwas schwer zu finden ist. Ich denke, das ist der Hauptpunkt.
Johannes Lund
9
Dies scheint hacky AF
Mattsven
66

Seit iOS5 bietet Apple eine Methode, um dies direkt zu tun. Referenz

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
Pascalius
quelle
@nonamelive Eigentlich nicht, hinzugefügt in iOS6: developer.apple.com/library/ios/#releasenotes/General/…
Pascalius
7
Ja, es wurde in iOS 6 hinzugefügt, aber es wird auch in iOS 5 unterstützt. Ein Apple-Ingenieur erwähnte dies in der WWDC 2012-Sitzung 216.
Nonamelive
37

Ab iOS 4 können Sie die UINibKlasse verwenden, um dieses Problem zu lösen.

  1. Erstellen Sie Ihre benutzerdefinierte UINavigationBarUnterklasse.
  2. Erstellen Sie eine leere xib und fügen Sie a UINavigationControllerals einzelnes Objekt hinzu.
  3. Stellen Sie die Klasse für die UINavigationController‚s UINavigationBarzu Ihrer benutzerdefinierten Unterklasse.
  4. Stellen Sie Ihren Root View Controller über eine der folgenden Methoden ein:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

In Code:

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


Jetzt haben Sie eine UINavigationControllermit Ihrem Brauch UINavigationBar.

Gorbster
quelle
Außer wie stellen Sie den rootViewController ein?
Memmons
Sie verwenden [navcontroller setViewControllers: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]]
coneybeare
Entschuldigung, Sie verwenden Sie verwenden [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> animiert: NEIN]
coneybeare
3
IMHO ist dies der am wenigsten "hackische" Weg, um diesen manchmal unvermeidlichen Hack zu machen.
David Pisoni
2
Eigentlich ein merkwürdiges Problem. Wenn ich dies tue, wird das Navigationselement des neuen Navigationscontrollers nicht auf die Navigationseigenschaftseigenschaft festgelegt, die dem Ansichtscontroller zugewiesen ist, den ich auf den (leeren) Stapel schiebe.
David Pisoni
26

Soweit ich das beurteilen kann, ist es manchmal tatsächlich notwendig, UINavigationBar zu unterordnen, um ein nicht standardmäßiges Restyling durchzuführen. Es ist manchmal möglich, dies zu vermeiden, indem Kategorien verwendet werden , aber nicht immer.

Derzeit, soweit ich weiß, die einzige Weg , um eine benutzerdefinierte UINavigationBar innerhalb eines UIViewController gesetzt ist über IB (das heißt, über ein Archiv) - es sollte wohl nicht so sein, aber für jetzt, wir müssen damit leben.

Dies ist oft in Ordnung, aber manchmal ist die Verwendung von IB nicht wirklich machbar.

Also sah ich drei Möglichkeiten:

  1. Unterklasse UINavigationBar und alles in IB einbinden, dann jedes Mal, wenn ich einen UINavigationController wollte, die Feder laden.
  2. Verwenden Sie den Methodenersatz innerhalb einer Kategorie, um das Verhalten von UINavigationBar anstelle von Unterklassen zu ändern, oder
  3. Unterklasse UINavigationBar und ein wenig herumspielen mit dem Archivieren / Aufheben der Archivierung des UINavigationControllers.

Option 1 war in diesem Fall für mich nicht durchführbar (oder zumindest zu ärgerlich), da ich den UINavigationController programmgesteuert erstellen musste. 2 ist meiner Meinung nach ein wenig gefährlich und eher ein letzter Ausweg, daher habe ich Option 3 gewählt.

Mein Ansatz war es, ein "Vorlagen" -Archiv eines UINavigationControllers zu erstellen und dieses zu archivieren und es zurückzugeben initWithRootViewController.

Hier ist wie:

In IB habe ich einen UINavigationController mit der entsprechenden Klasse für die UINavigationBar erstellt.

Dann nahm ich den vorhandenen Controller und speicherte eine archivierte Kopie davon mit +[NSKeyedArchiver archiveRootObject:toFile:] . Ich habe dies gerade innerhalb des App-Delegaten im Simulator getan.

Ich habe dann das Dienstprogramm 'xxd' mit dem Flag -i verwendet, um C-Code aus der gespeicherten Datei zu generieren und die archivierte Version in meine Unterklasse einzubetten (xxd -i path/to/file ) .

Innerhalb initWithRootViewControllerentarchiviere ich diese Vorlage und setze mich auf das Ergebnis des Unarchivs:

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

Dann kann ich einfach eine neue Instanz meiner UIViewController-Unterklasse abrufen, für die die benutzerdefinierte Navigationsleiste festgelegt ist:

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

Dies gibt mir einen modalen UITableViewController mit einer Navigationsleiste und einer Symbolleiste, die alle eingerichtet sind, und mit der benutzerdefinierten Navigationsleistenklasse. Ich musste keinen leicht unangenehmen Methodenersatz durchführen, und ich muss mich nicht mit Schreibfedern herumschlagen, wenn ich wirklich nur programmgesteuert arbeiten möchte.

Ich würde gerne das Äquivalent von +layerClassUINavigationController sehen - +navigationBarClass- aber im Moment funktioniert dies.

Michael Tyson
quelle
5

Ich benutze "Option 1"

Erstellen Sie eine NIB-Datei, in der nur der UINavigationController enthalten ist. Und setzen Sie die UINavigationBar-Klasse auf meine benutzerdefinierte Klasse.

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

[navigationController pushViewController:rootViewController animated:YES];
obb64
quelle
Der entsprechende Name der NIB-Datei wäre also loadNibNamed: @ "navigationController? Sie erhalten tatsächlich den gesamten Navigationscontroller von der Feder und nicht nur von der Leiste. Richtig?
aneuryzm
Das funktioniert bei mir, aber wenn ich in meiner Tabellenansicht auf eine Option klicke, verliere ich die Schaltfläche "Zurück".
jfisk
5

Michaels Lösung funktioniert, aber Sie können NSKeyedArchiver und das Dienstprogramm 'xxd' vermeiden. Unterklassifizieren Sie einfach UINavigationController und überschreiben Sie es initWithRootViewController, indem Sie Ihre benutzerdefinierte NavigationController-NIB direkt laden:

- (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 Gramm
quelle
4

Update: Die Verwendung object_SetClass()funktioniert nicht mehr so, als ob iOS5 GM. Eine alternative Lösung wurde unten hinzugefügt.

Verwenden Sie NSKeyedUnarchiver, um die nicht archivierte Klasse für die Navigationsleiste manuell festzulegen.

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




Hinweis: Diese ursprüngliche Lösung funktioniert nur vor iOS5:

Es gibt eine großartige Lösung, die ich hier gepostet habe : Fügen Sie die Unterklasse navBar direkt in Ihre Ansicht ein UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}
memmons
quelle
Wie ich in der vorherigen Antwort angegeben habe, haben Sie dies gepostet - der Goldmaster von iOS5 hat dies gebrochen. Es stehen jedoch noch andere Optionen zur Verfügung, sodass ich sie mit einer alternativen Methode bearbeiten werde.
Memmons
1

Ein Szenario, bei dem ich festgestellt habe, dass wir eine Unterklasse anstelle einer Kategorie verwenden müssen, ist das Festlegen der Hintergrundfarbe der Navigationsleiste mit dem Musterbild, da in iOS5 das Überschreiben von drawRect mithilfe der Kategorie nicht mehr funktioniert. Wenn Sie ios3.1-5.0 unterstützen möchten, können Sie nur die Navigationsleiste der Unterklasse verwenden.

James
quelle
1

Diese Kategoriemethoden sind gefährlich und nicht für Anfänger. Auch die Komplikation mit iOS4 und iOS5 macht dies zu einem Bereich, der für viele Menschen Fehler verursachen kann. Hier ist eine einfache Unterklasse, die ich verwende und die iOS4.0 ~ iOS6.0 unterstützt und sehr einfach ist.

.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
quelle
0

Es wird nicht empfohlen , die UnterklasseUINavigationBar Klasse . Die bevorzugte Methode zum Anpassen der Navigationsleiste besteht darin, die Eigenschaften so festzulegen, dass sie wie gewünscht angezeigt werden, und benutzerdefinierte Ansichten in UIBarButtonItems zusammen mit einem Delegaten zu verwenden, um das gewünschte Verhalten zu erzielen.

Was versuchst du zu tun, das Unterklassen benötigt?

Ich glaube auch nicht, dass IB tatsächlich die Navigationsleiste ersetzt. Ich bin mir ziemlich sicher, dass es einfach nicht die Standardanzeige anzeigt und Ihre benutzerdefinierte Navigationsleiste als Unteransicht hat. Wenn Sie UINavigationController.navigationBar aufrufen, erhalten Sie eine Instanz Ihrer Leiste?

Ben S.
quelle
2
Hallo Ben, danke. Ich weiß, dass dies nicht der bessere Weg ist, um UINavigationBar zu unterordnen, aber ich möchte ein Hintergrundbild einrichten, das die drawRect: -Methode überschreibt. Das Problem ist, dass ich mit IB die Klasse der Navigationsleiste in einem UINavigationController ändern kann, programmgesteuert jedoch nicht. Und ja, IB ersetzt tatsächlich die Navigationsleiste: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; Rahmen = (0 20; 320 44); clipsToBounds = YES; undurchsichtig = NEIN; Autoresize = W; layer = <CALayer: 0x1806da0 >>
Duccio
Ich verwende auch diese Technik und sie funktioniert weiterhin in iOS5 (im Gegensatz zur UINavigationBar-Kategorietechnik)
David Pisoni
0

Nach dem Kommentar von obb64 habe ich schließlich seinen Trick verwendet setViewControllers:animated:, um den Controller als den rootControllerfür die navigationControllervon der Feder geladenen festzulegen. Hier ist der Code, den ich verwende:

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