Stel programmatisch een aangepaste subklasse van UINavigationBar in UINavigationController in

Weet iemand hoe ik mijn aangepaste subklasse van UINavigationBarkan gebruiken als ik UINavigationBarprogrammatisch start (zonder IB)?

Sleep een UINavigationBarin IB laat me een onder de navigatiebalk zien en met behulp van Identity Inspectory kan ik het klassetype wijzigen en mijn eigen subklasse van UINavigationBarinstellen, maar programmatisch kan ik dat niet , navigationBareigenschap van Navigation Controller is alleen-lezen…

Wat moet ik doen om de navigatiebalk programmatisch aan te passen? Is IB “krachtiger” dan “code”? Ik geloofde dat alles wat in IB gedaan kan worden, ook programmatisch gedaan kan worden.


Antwoord 1, autoriteit 100%

Je hoeft niet te rotzooien met de XIB, gebruik gewoon KVC.

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

Antwoord 2, autoriteit 75%

Sinds iOS5 biedt Apple een methode om dit rechtstreeks te doen. Referentie

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

Antwoord 3, autoriteit 42%

Vanaf iOS 4 kunt u de klasse UINibgebruiken om dit probleem op te lossen.

  1. Maak uw aangepaste UINavigationBar-subklasse.
  2. Maak een lege xib, voeg een UINavigationBartoe als de enkele
    voorwerp.
  3. Stel de klasse voor de UINavigationBarvan de UINavigationBarin op uw aangepaste subklasse.
  4. Stel je rootview-controller in via een van deze methoden:
    • [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];

Je hebt nu een UINavigationBarmet je aangepaste UINavigationBar.


Antwoord 4, autoriteit 29%

Voor zover ik weet, is het soms inderdaad nodig om UINavigationBar in subklassen te brengen, om een ​​niet-standaard restyling uit te voeren. Het is soms mogelijk om dit te vermijden door gebruik te maken van categorieën, maar niet altijd.

Momenteel is, voor zover ik weet, de enigemanier om een ​​aangepaste UINavigationBar in te stellen binnen een UIViewController via IB (dat wil zeggen, via een archief) – dat zou waarschijnlijk niet zo moeten zijn, maar voor nu moeten we ermee leven.

Dit is vaak prima, maar soms is het gebruik van IB niet echt haalbaar.

Dus ik zag drie opties:

  1. Subklasse UINavigationBar en sluit het allemaal aan in IB, en klets dan over het laden van de penpunt elke keer dat ik een UINavigationController wilde,
  2. Gebruik methodevervangingbinnen een categorie om het gedrag van UINavigationBar te veranderen, in plaats van subclassificatie, of
  3. Subklasse UINavigationBar en wat rommelen met het archiveren/dearchiveren van de UINavigationController.

Optie 1 was in dit geval onhaalbaar (of op zijn minst te vervelend) voor mij, omdat ik de UINavigationController programmatisch moest maken, 2 is een beetje gevaarlijk en naar mijn mening meer een laatste redmiddel, dus ik koos voor optie 3.

Mijn aanpak was om een ​​’sjabloon’-archief van een UINavigationController te maken en dat uit het archief te halen, en terug te sturen in initWithRootViewController.

Zo gaat het:

In IB heb ik een UINavigationController gemaakt met de juiste klassenset voor de UINavigationBar.

Vervolgens nam ik de bestaande controller en bewaarde een gearchiveerde kopie ervan met behulp van +[NSKeyedArchiver archiveRootObject:toFile:]. Ik deed dit net binnen de app-afgevaardigde, in de simulator.

Vervolgens gebruikte ik het hulpprogramma ‘xxd’ met de vlag -i om c-code te genereren uit het opgeslagen bestand, om de gearchiveerde versie in mijn subklasse in te sluiten (xxd -i path/to/file).

Binnen initWithRootViewControllerdearchiver ik die sjabloon en stel mezelf in op het resultaat van het dearchiveren:

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

Dan kan ik gewoon een nieuwe instantie van mijn UIViewController-subklasse pakken die de aangepaste navigatiebalk heeft:

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

Dit geeft me een modale UITableViewController met een navigatiebalk en werkbalk allemaal ingesteld, en met de aangepaste navigatiebalkklasse op zijn plaats. Ik hoefde geen ietwat vervelende methodevervanging uit te voeren, en ik hoef niet te rotzooien met penpunten als ik echt alleen programmatisch wil werken.

Ik zou graag het equivalent van +layerClasszien in UINavigationController – +navigationBarClass– maar voor nu werkt dit.


Antwoord 5, autoriteit 6%

Ik gebruik “optie 1”

Maak een nib-bestand met alleen de UINavigationController erin. En stel de UINavigationBar-klasse in op mijn aangepaste klasse.

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

Antwoord 6, autoriteit 6%

Michael’s oplossing werkt, maar je kunt NSKeyedArchiver en het hulpprogramma ‘xxd’ vermijden. Subclass UINavigationController en overschrijf initWithRootViewController, en laad uw aangepaste NavigationController NIB direct:

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

Antwoord 7, autoriteit 4%

Update:Het gebruik van object_SetClass()werkt niet langer alsof iOS5 GM. Een alternatieve oplossing is hieronder toegevoegd.

Gebruik NSKeyedUnarchiver om handmatig de dearchiveringsklasse voor de navigatiebalk in te stellen.

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


Opmerking: deze originele oplossing werkt alleen vóór iOS5:

Er is een geweldige oplossing, die ik heb gepost hier— injecteer de navBar-subklasse rechtstreeks in uw weergave, de UINavigationBar:

#import <objc/runtime.h>
- (void)viewDidLoad {
    [super viewDidLoad];
    object_setClass(self.navigationController.navigationBar, [MyNavBar class]);
    // the rest of your viewDidLoad code
}

Antwoord 8

Een scenario dat ik heb ontdekt dat we subklasse moeten gebruiken in plaats van categorie, is om de achtergrondkleur van de navigatiebalk in te stellen met patroonafbeelding, omdat in iOS5 het overschrijven van drawRect met behulp van categorie niet meer werkt. Als je ios3.1-5.0 wilt ondersteunen, kun je dat alleen doen door de navigatiebalk te subklassen.


Antwoord 9

Deze categoriemethoden zijn gevaarlijk en niet voor beginners. Ook de complicatie met het feit dat iOS4 en iOS5 anders zijn, maakt dit een gebied dat voor veel mensen bugs kan veroorzaken. Hier is een eenvoudige subklasse die ik gebruik die iOS4.0 ~ iOS6.0 ondersteunt en heel eenvoudig is.

.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

Antwoord 10

Het wordt niet aanbevolenom de klasse UINavigationBarte subclasseren. De voorkeursmanier om de navigatiebalk aan te passen, is door de eigenschappen ervan in te stellen zodat deze wordt weergegeven zoals u dat wilt, en aangepaste weergaven in UIBarButtonItems te gebruiken, samen met een gemachtigde om het gewenste gedrag te krijgen.

Wat probeer je te doen waarvoor subclassificatie nodig is?

Ik denk ook niet dat IB de navigatiebalk daadwerkelijk vervangt. Ik ben er vrij zeker van dat het gewoon niet de standaard weergeeft en je aangepaste navigatiebalk als een subweergave heeft. Als je UINavigationController.navigationBar aanroept, krijg je dan een instantie van je balk?


Antwoord 11

Als je de navigatiebalk wilt onderklassen om de achtergrondafbeelding te wijzigen, is dat niet nodig in iOS 5.
Er zal een methode zijn zoals deze setBackgroundImage


Antwoord 12

Naast de opmerking van obb64, gebruikte ik uiteindelijk zijn truc met setViewControllers:animated:om de controller in te stellen als de rootControllervoor de navigationControllergeladen vanaf de punt. Dit is de code die ik gebruik:

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

Other episodes