Ответ 1
Вам не нужно гадать с XIB, просто используйте KVC.
[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
Кто-нибудь знает, как я могу использовать свой пользовательский подкласс UINavigationBar
, если я создаю программный код UINavigationController
(без IB)?
Перетащите a UINavigationController
в IB, покажите мне панель навигации и с помощью Identity Inspectory. Я могу изменить тип класса и установить свой собственный подкласс UINavigationBar
, но программным способом я не могу, свойство navigationBar
контроллера навигации доступно только для чтения...
Что делать, чтобы программно настроить панель навигации? Является ли IB более "мощным", чем "код"? Я полагал, что все, что можно сделать в IB, можно сделать также программно.
Вам не нужно гадать с XIB, просто используйте KVC.
[self.navigationController setValue:[[[CustomNavBar alloc]init] autorelease] forKeyPath:@"navigationBar"];
Так как iOS5, apple предоставляет метод, чтобы сделать это напрямую. Reference
UINavigationController *navigationController= [[UINavigationController alloc]initWithNavigationBarClass:[CustomNavBar class] toolbarClass:nil];
[navigationController setViewControllers:[NSArray arrayWithObject:yourRootViewController]];
Как и в iOS 4, вы можете использовать класс UINib
, чтобы помочь решить эту проблему.
UINavigationBar
.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
.
Насколько я могу судить, иногда действительно необходимо подклассифицировать UINavigationBar, чтобы выполнить нестандартный рестайлинг. Иногда бывает невозможно избежать этого, используя категории, но не всегда.
В настоящее время, насколько мне известно, единственный способ установить пользовательский UINavigationBar в UIViewController через IB (то есть через архив) - это, вероятно, не должно быть так, но пока мы должны жить с ним.
Это часто замечательно, но иногда использование IB не реально выполнимо.
Итак, я видел три варианта:
Вариант 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
- но пока это работает.
Я использую "вариант 1"
Создайте nib файл с только UINavigationController в нем. И установите класс UINavigationBar в мой пользовательский класс.
self.navigationController = [[[NSBundle mainBundle] loadNibNamed:@"navigationbar" owner:self options:nil] lastObject];
[navigationController pushViewController:rootViewController animated:YES];
Решение 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;
}
Обновление: Использование 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
}
Один из сценариев, которые я обнаружил, что нам нужно использовать подкласс, а не категорию, - это установить навигационную панель backgroundcolor с образцом изображения, потому что в iOS5 переписывание drawRect с использованием категории больше не работает. Если вы хотите поддерживать ios3.1-5.0, единственный способ сделать это - подклассифицировать навигационную панель.
Эти методы категории опасны, а не для новичков. Также осложнение с 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
Он не рекомендуется для подкласса класса UINavigationBar
. Предпочтительный способ настройки панели навигации - установить его свойства, чтобы они отображались по своему усмотрению, и использовать пользовательские представления внутри UIBarButtonItems вместе с делегатом для получения желаемого поведения.
Что вы пытаетесь сделать, это требует подкласса?
Кроме того, я не думаю, что IB фактически заменяет панель навигации. Я уверен, что он просто не отображает значение по умолчанию и имеет вашу настраиваемую панель навигации в качестве подзаголовка. Если вы вызываете UINavigationController.navigationBar, вы получаете экземпляр своего бара?
В дополнение к комментарию 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];
}
Если вы хотите подклассифицировать navBar только для изменения фонового изображения - в iOS 5 нет необходимости. Будет такой метод, как этот setBackgroundImage