Встановіть власний підклас UINavigationBar в UINavigationController програмно


92

Хто-небудь знає, як я можу використовувати свій власний підклас, UINavigationBarякщо створювати екземпляри UINavigationControllerпрограмно (без IB)?

Перетягніть UINavigationControllerв IB, покажіть мені під панеллю навігації, і за допомогою Identity Inspectory я можу змінити тип класу та встановити власний підклас, UINavigationBarале програмно не можу, navigationBarвластивість Navigation Controller читається лише ...

Що робити, щоб програмно налаштувати панель навігації? Чи є IB "потужнішим", ніж "код"? Я вважав, що все, що можна зробити в IB, може бути зроблено також програмно.


Вам пощастило знайти рішення в іншому місці?
prendio2

Ви отримуєте будь-яку відповідь щодо цього?
Грушікеш Бетай

Відповіді:


89

Вам не потрібно гадити з XIB, просто використовуйте KVC.

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

Це рішення працює як шарм (принаймні на iOS 5.1). Всі інші рішення здаються набагато більше роботи. Все ще шукає мінус.
Даніель

6
як ви знайшли цей ключовий шлях @ "navigationBar" для контролера навігації. Ви можете поділитися цим
Ікбал Хан,

Ви впевнені, що це не призведе до відхилення програми? Я цього фактично ніде не бачив.
Бані Уппал,

Дякую! Чудово працює і в iOS 6 beta 3! @BaniUppal KVC, безумовно, задокументований, ми просто використовуємо його з ключем, який трохи важко знайти. Я думаю, що це головне.
Йоганнес Лунд,

9
Це здається хакі AF
mattsven

66

Починаючи з iOS5, apple пропонує спосіб зробити це безпосередньо. Довідково

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

@nonamelive Насправді ні, додано в iOS6: developer.apple.com/library/ios/#releasenotes/General/…
Паскалій

7
Так, це додано в iOS 6, але воно також підтримується в iOS 5. Інженер Apple згадав, що у сесії 216 WWDC 2012
nonamelive

37

Починаючи з iOS 4, ви можете використовувати UINibклас, щоб допомогти вирішити цю проблему.

  1. Створіть власний UINavigationBarпідклас.
  2. Створіть порожній xib, додайте a UINavigationControllerяк єдиний об'єкт.
  3. Встановіть для класу UINavigationController'' UINavigationBarсвій власний підклас.
  4. Встановіть свій кореневий контролер подання одним із таких методів:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

У коді:

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


Тепер у вас є UINavigationControllerз вашим нестандартним UINavigationBar.


Окрім того, як ви встановлюєте rootViewController?
memmons

ви використовуєте [navcontroller setViewControllers: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]]
coneybeare

вибачте, ви використовуєте ви використовуєте [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> анімований: NO]
coneybeare

3
ІМХО - це найменш "хакерський" спосіб зробити цей часом неминучий хак.
Девід Пізоні,

2
Насправді одна дивна проблема. Коли я це роблю, NaviItem нового навігаційного контролера не встановлюється для властивості navigationItem, призначеного viewController, який я натискаю на (порожній) стек.
Девід Пізоні,

26

Наскільки я можу зрозуміти, іноді дійсно потрібно підклас UinavigationBar виконати якийсь нестандартний рестайлінг. Іноді можна уникнути необхідності робити це за допомогою категорій , але не завжди.

На даний момент, наскільки мені відомо, єдиний спосіб встановити користувацький UINavigationBar в UIViewController - це через IB (тобто через архів) - це, мабуть, не повинно бути таким чином, але наразі ми повинні жити з цим.

Це часто нормально, але іноді використання IB насправді неможливо.

Отже, я побачив три варіанти:

  1. Підклас UINavigationBar і підключіть все це до IB, а потім роздумуйте про завантаження перо кожного разу, коли я хочу UINavigationController,
  2. Використовуйте заміну методу в категорії, щоб змінити поведінку UINavigationBar, а не підкласифікацію, або
  3. Підклас UINavigationBar та трохи поглинайте архівування / деархівування UINavigationController.

Варіант 1 у цьому випадку був для мене нездійсненним (або, принаймні, надто надокучливим), оскільки мені потрібно було програмно створити UINavigationController, 2, на мій погляд, є трохи небезпечним і більш крайнім варіантом, тому я обрав варіант 3.

Мій підхід полягав у створенні "шаблону" архіву UINavigationController та його архівуванні, повертаючи його в initWithRootViewController.

Ось як:

У IB я створив UINavigationController з відповідним класом, встановленим для UINavigationBar.

Потім я взяв існуючий контролер і зберіг заархівовану копію його за допомогою +[NSKeyedArchiver archiveRootObject:toFile:]. Я щойно зробив це в рамках делегата програми, в симуляторі.

Потім я використав утиліту 'xxd' з прапором -i, щоб згенерувати c-код із збереженого файлу, щоб вставити заархівовану версію в мій підклас ( xxd -i path/to/file).

Усередині initWithRootViewControllerя розархівую цей шаблон і встановлюю себе як результат розпакування:

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

Тоді я можу просто захопити новий екземпляр мого підкласу UIViewController, у якому встановлена ​​спеціальна панель навігації:

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

Це дає мені модальний UITableViewController із налаштованою панеллю навігації та панеллю інструментів, а також із спеціальним класом панелі навігації. Мені не потрібно було робити будь-який злегка неприємний метод заміни, і мені не доводиться базікати перо, коли я справді просто хочу працювати програмно.

Я хотів би бачити еквівалент +layerClassусередині UINavigationController - +navigationBarClass- але наразі це працює.


5

Я використовую "варіант 1"

Створіть nib-файл, у якому є лише UINavigationController. І встановіть для UINavigationBar Class мій власний клас.

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

[navigationController pushViewController:rootViewController animated:YES];

тож відповідна назва файлу nib буде loadNibNamed: @ "navigationController? Ви фактично отримуєте весь navigationController з перо, а не тільки панелі. Так?
aneuryzm

це працює для мене, але коли я натискаю будь-який параметр у своєму перегляді таблиці, я втрачаю кнопку "Назад".
jfisk 02

5

Рішення Майкла працює, але ви можете уникнути NSKeyedArchiver та утиліти 'xxd'. Просто підкласуйте UINavigationController і перевизначте initWithRootViewController, завантажуючи власний 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;
}

4

Оновлення: використання object_SetClass()більше не працює, як якщо б iOS5 GM. Нижче додано альтернативне рішення.

Використовуйте NSKeyedUnarchiver, щоб вручну встановити клас розпакування для навігаційної панелі.

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




Примітка: Це оригінальне рішення працює лише до iOS5:

Є чудове рішення, яке я розмістив тут - вкладіть підклас navBar безпосередньо на ваш погляд UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

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

Як я вже зазначав у попередній відповіді, яку ви розмістили на цьому - золотий майстер iOS5 зламав це. Хоча є й інші варіанти, тому я відредагую альтернативним методом.
memmons

1

Один сценарій, який я виявив, що нам потрібно використовувати підклас, а не категорію, - це встановити кольори фону навігаційної панелі із зображенням шаблону, оскільки в iOS5 перезапис drawRect за допомогою категорії більше не працює. Якщо ви хочете підтримувати ios3.1-5.0, єдиний спосіб, який ви можете зробити, - це підклас панелі навігації.


1

Ці методи категорії є небезпечними та не для початківців. Крім того, ускладнення в тому, що iOS4 та iOS5 відрізняються, робить цю сферу причиною багатьох помилок. Ось простий підклас, який я використовую, який підтримує iOS4.0 ~ iOS6.0 і дуже простий.

.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

Це не рекомендується , щоб підклас UINavigationBarкласу. Найкращим способом налаштування навігаційної панелі є встановлення її властивостей, щоб вона відображалася, як вам заманеться, та використання власних подань всередині UIBarButtonItems разом із делегатом, щоб отримати бажану поведінку.

Що ви намагаєтесь зробити, для чого потрібна підкласифікація?

Крім того, я не думаю, що IB насправді замінює панель навігації. Я майже впевнений, що він просто не відображається за замовчуванням, і ваш власний панель навігації є підпрограмою. Якщо ви зателефонуєте UINavigationController.navigationBar, чи отримаєте ви екземпляр своєї панелі?


2
Привіт, Бен, дякую. Я знаю, що це не найкращий спосіб підкласу UINavigationBar, але я хотів би встановити фонове зображення, яке перевизначає метод drawRect:. Проблема в тому, що за допомогою IB я можу змінити клас панелі навігації в UINavigationController, але програмно не можу. І так, IB насправді замінює панель навігації: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; кадр = (0 20; 320 44); clipsToBounds = ТАК; непрозорий = НІ; авторозмір = W; layer = <CALayer: 0x1806da0 >>
Duccio

Я також використовую цю саму техніку, і вона продовжує працювати в iOS5 (на відміну від техніки UINavigationBar Category.)
Девід Пізоні,


0

В доповнення до obb64 в коментарі, я в кінцевому підсумку , використовуючи свій трюк з , setViewControllers:animated:щоб встановити контролер в якості rootControllerдля navigationControllerзавантажується з кінчика пера. Ось код, який я використовую:

- (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];
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.