Автоматически настраивать пользовательский подкласс UINavigationBar в UINavigationController

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

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

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

Ответ 1

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

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

Ответ 2

Так как iOS5, apple предоставляет метод, чтобы сделать это напрямую. Reference

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

Ответ 3

Как и в iOS 4, вы можете использовать класс UINib, чтобы помочь решить эту проблему.

  • Создайте свой пользовательский подкласс UINavigationBar.
  • Создайте пустой xib, добавьте UINavigationController в качестве единственного объект.
  • Задайте класс для UINavigationController UINavigationBar для вашего пользовательского подкласса.
  • Задайте свой контроллер корневого представления одним из следующих способов:
    • [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.

Ответ 4

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

В настоящее время, насколько мне известно, единственный способ установить пользовательский UINavigationBar в UIViewController через IB (то есть через архив) - это, вероятно, не должно быть так, но пока мы должны жить с ним.

Это часто замечательно, но иногда использование IB не реально выполнимо.

Итак, я видел три варианта:

  • Подкласс UINavigationBar и зацепите его все в IB, а затем забудьте о загрузке иглы каждый раз, когда мне нужен UINavigationController,
  • Используйте замену метода в категории, чтобы изменить поведение UINavigationBar, а не подклассифицировать, или
  • Подкласс UINavigationBar и немного обманываем архивирование/разблокирование UINavigationController.

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

Мой подход состоял в том, чтобы создать "шаблонный" архив UINavigationController и разблокировать его, возвращая его в initWithRootViewController.

Вот как:

В IB я создал UINavigationController с соответствующим набором классов для UINavigationBar.

Затем я взял существующий контроллер и сохранил его архивированную копию с помощью +[NSKeyedArchiver archiveRootObject:toFile:]. Я просто сделал это в делегате приложения, в симуляторе.

Затем я использовал утилиту "xxd" с флагом -i, чтобы сгенерировать c-код из сохраненного файла, для встраивания архивной версии в мой подкласс (xxd -i path/to/file).

Внутри initWithRootViewController я отформатирую этот шаблон и присваиваю значение self для результата unarchive:

// 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 в мой пользовательский класс.

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

[navigationController pushViewController:rootViewController animated:YES];

Ответ 6

Решение Michael работает, но вы можете избежать использования NSKeyedArchiver и утилиты xxd. Просто подклассифицируйте UINavigationController и переопределите initWithRootViewController, загрузите собственный пользовательский навигационный контроллер NIB напрямую:

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

Ответ 7

Обновление: Использование object_SetClass() больше не работает, как iOS5 GM. Альтернативное решение было добавлено ниже.

Используйте NSKeyedUnarchiver для ручной установки класса unarchive для панели навигации.

   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
}

Ответ 8

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

Ответ 9

Эти методы категории опасны, а не для новичков. Также осложнение с iOS4 и iOS5 отличается от других, делает область, которая может вызвать ошибки для многих людей. Вот простой подкласс, который я использую, который поддерживает iOS4.0 ~ iOS6.0 и очень просто.

.h

@interface XXXNavigatioNBar : UINavigationBar
@end

ого

#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, "[email protected]:{name=CGRect}");
    }
}

- (void)iOS4drawRect: (CGRect) rect {
    UIImage* bg = [UIImage imageNamed:@"nav-bar-blue"];
    [bg drawInRect: rect];
}

@end

Ответ 10

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

Что вы пытаетесь сделать, это требует подкласса?

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

Ответ 11

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

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

Ответ 12

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