Программно установить пользовательский подкласс UINavigationBar в UINavigationController

Кто-нибудь знает, как я могу использовать свой собственный подкласс UINavigationBar, если я создаю экземпляр UINavigationController программно (без IB)?

Перетащите UINavigationController в IB, покажите мне панель навигации и с помощью Identity Inspectory я могу изменить тип класса и установить собственный подкласс UINavigationBar, но программно я не могу, свойство navigationBar контроллера навигации доступно только для чтения ...

Что мне делать, чтобы программно настроить панель навигации? IB более «мощный», чем «код»? Я считал, что все, что можно сделать в IB, можно сделать и программно.


person Duccio    schedule 08.12.2009    source источник
comment
Вам повезло найти решение где-нибудь в другом месте?   -  person prendio2    schedule 23.04.2010
comment
ты получишь по этому поводу какой-нибудь ответ?   -  person Hrushikesh Betai    schedule 03.09.2012


Ответы (12)


Вам не нужно гадить с XIB, просто используйте KVC.

[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
person Fred    schedule 07.03.2012
comment
Это решение работает как шарм (по крайней мере, на iOS 5.1). Все остальные решения кажутся более трудоемкими. Все еще ищу обратную сторону. - person Daniel; 22.04.2012
comment
как вы нашли этот ключевой путь @navigationBar для контроллера навигации. можешь поделиться этим? - person Iqbal Khan; 23.05.2012
comment
Вы уверены, что это не приведет к отклонению приложения? На самом деле я нигде не видел этого документированного. - person Bani Uppal; 15.07.2012
comment
Спасибо! Отлично работает и в iOS 6 beta 3! @BaniUppal KVC определенно задокументирован, мы просто используем его с ключом, который немного сложно найти. Я думаю, что это главное. - person Johannes Lund; 19.07.2012
comment
@Developer Я думаю, он мог использовать что-то вроде this, но это только предположение. - person Johannes Lund; 19.07.2012
comment
Работает в iOS 7. Какой отличный хак. Интересно, почему они просто не делают метод UINavigationBar для чтения-записи. - person Enrico Susatyo; 04.10.2013
comment
Танки! Для меня это идеальное решение =) - person Nuzhdin Vladimir; 15.05.2015
comment
Также работает в iOS 8.3 :) - person JK ABC; 16.06.2015

Начиная с iOS5, Apple предоставляет способ сделать это напрямую. Справка

UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
person Pascalius    schedule 21.09.2012
comment
@nonamelive На самом деле нет, добавлено в iOS6: developer.apple. ru / library / ios / # releasenotes / General / - person Pascalius; 19.10.2012
comment
Да, он добавлен в iOS 6, но он также поддерживается в iOS 5. Инженер Apple упомянул об этом на WWDC 2012 Session 216. - person nonamelive; 21.10.2012

Начиная с iOS 4, вы можете использовать класс UINib для решения этой проблемы.

  1. Создайте свой собственный подкласс UINavigationBar.
  2. Создайте пустой xib, добавьте UINavigationController как единственный объект.
  3. Установите класс для UINavigationController и UINavigationBar на свой собственный подкласс.
  4. Set your root view controller via one of these methods:
    • [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.

person gorbster    schedule 25.05.2011
comment
Кроме того, как установить rootViewController? - person memmons; 07.10.2011
comment
вы используете [navcontroller setViewControllers: [NSArray arrayWithObject: ‹YOUR_ROOT_CONTROLLER›]] - person coneybeare; 09.10.2011
comment
извините, вы используете вы используете [navcontroller pushViewController: ‹YOUR_ROOT_CONTROLLER› animated: NO] - person coneybeare; 09.10.2011
comment
ИМХО, это наименее хакерский способ сделать этот иногда неизбежный взлом. - person David Pisoni; 24.11.2011
comment
Собственно, одна странная проблема. Когда я это делаю, для navigationItem нового navigationController не устанавливается свойство navigationItem, назначенное для viewController, который я помещаю в (пустой) стек. - person David Pisoni; 24.11.2011

Насколько я могу судить, иногда действительно необходимо создать подкласс UINavigationBar, чтобы провести нестандартный рестайлинг. Иногда этого можно избежать, используя категории, но не всегда.

В настоящее время, насколько мне известно, only способ установить пользовательский 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 я разархивирую этот шаблон и устанавливаю self как результат разархивирования:

// 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, но пока это работает.

person Michael Tyson    schedule 20.06.2010

Я использую "вариант 1"

Создайте файл пера, содержащий только UINavigationController. И установите для класса UINavigationBar мой собственный класс.

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

[navigationController pushViewController:rootViewController animated:YES];
person obb64    schedule 17.12.2010
comment
так что подходящее имя файла пера будет loadNibNamed: @navigationController? Фактически вы получаете весь navigationController из пера, а не только панель. Верно ? - person aneuryzm; 18.06.2011
comment
это работает для меня, но когда я нажимаю на любую опцию в моем табличном представлении, я теряю кнопку возврата. - person jfisk; 02.07.2012

Решение Майкла работает, но вы можете избежать использования 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;
}
person 100grams    schedule 20.10.2011

Обновление: использование 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
}
person memmons    schedule 06.10.2011
comment
Как я указал в предыдущем ответе, вы разместили это - золотой мастер iOS5 сломал это. Однако есть и другие варианты, поэтому я буду редактировать их альтернативным методом. - person memmons; 08.10.2011

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

person james    schedule 30.11.2011

Эти категориальные методы опасны и не для новичков. Кроме того, сложность, связанная с различиями 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
person Paul de Lange    schedule 01.10.2012

Не рекомендуется создавать подкласс класса UINavigationBar. Предпочтительный способ настройки панели навигации - установить ее свойства, чтобы она отображалась так, как вы хотите, и использовать настраиваемые представления внутри UIBarButtonItems вместе с делегатом для получения желаемого поведения.

Что вы пытаетесь сделать, чтобы создать подклассы?

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

person Ben S    schedule 08.12.2009
comment
Привет, Бен, спасибо. Я знаю, что это не лучший способ создать подкласс UINavigationBar, но я хотел бы установить фоновое изображение, переопределяющее метод drawRect :. Проблема в том, что с помощью IB я могу изменить класс панели навигации в UINavigationController, но программно я не могу. И да, IB фактически заменяет панель навигации: NSLog (@% @, self.navigationController.navigationBar); ‹CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; кадр = (0 20; 320 44); clipsToBounds = ДА; непрозрачный = НЕТ; autoresize = W; layer = ‹CALayer: 0x1806da0 ›› - person Duccio; 10.12.2009
comment
Я также использую эту технику, и она продолжает работать в iOS5 (в отличие от техники категорий UINavigationBar). - person David Pisoni; 24.11.2011

Если вы хотите создать подкласс navBar только для изменения фонового изображения - в iOS 5 в этом нет необходимости. Будет такой метод, как этот setBackgroundImage

person off    schedule 09.08.2011
comment
вам необходимо использовать setBackgroundImage:forBarMetrics:as, описанный здесь: разработчик .apple.com / library / IOS / # documentation / UIKit / Reference /. - person Ahti; 01.11.2011

В дополнение к комментарию 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];
}
person smtlaissezfaire    schedule 02.02.2011