Đặt một lớp con tùy chỉnh của UINavigationBar trong UINavigationController theo lập trình


92

Có ai biết cách tôi có thể sử dụng lớp con tùy chỉnh của mình UINavigationBarnếu tôi khởi tạo theo UINavigationControllerchương trình (không có IB) không?

Kéo một UINavigationControllertrong IB hiển thị cho tôi một bên dưới Thanh điều hướng và sử dụng Kiểm tra danh tính Tôi có thể thay đổi loại lớp và đặt lớp con của riêng mình UINavigationBarnhưng tôi không thể lập trình, navigationBarthuộc tính của Bộ điều khiển điều hướng là chỉ đọc ...

Tôi nên làm gì để tùy chỉnh thanh điều hướng theo chương trình? IB có "quyền lực" hơn "mã"? Tôi tin rằng tất cả những gì có thể làm trong IB cũng có thể được thực hiện theo chương trình.


bạn có may mắn tìm thấy một giải pháp ở nơi khác không?
prendio2

bạn có nhận được câu trả lời nào về nó không?
Hrushikesh Betai

Câu trả lời:


89

Bạn không cần kết hợp với XIB mà chỉ cần sử dụng KVC.

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

Giải pháp này hoạt động giống như một sự quyến rũ (ít nhất là trên iOS 5.1). Tất cả các giải pháp khác dường như hoạt động hiệu quả hơn. Vẫn đang tìm kiếm nhược điểm.
Daniel

6
Làm thế nào bạn tìm thấy đường dẫn chính này @ "navigationBar" cho bộ điều khiển điều hướng. bạn có thể chia sẻ điều này không
Iqbal Khan

Bạn có chắc điều này sẽ không dẫn đến việc Ứng dụng bị từ chối không? Tôi không thấy điều này được ghi lại ở bất cứ đâu.
Bani Uppal,

Cảm ơn! Hoạt động tuyệt vời trong iOS 6 beta 3! @BaniUppal KVC chắc chắn đã được ghi lại, chúng tôi chỉ đang sử dụng nó với một khóa hơi khó tìm. Tôi nghĩ đó là điểm chính.
Johannes Lund,

9
Điều này có vẻ hacky AF
mattsven

66

Kể từ iOS5, apple cung cấp một phương pháp để thực hiện việc này trực tiếp. Tài liệu tham khảo

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

@nonamelive Thực ra thì không, được thêm vào iOS6: developer.apple.com/library/ios/#releasenotes/General/…
Pascalius 19/10/12

7
Vâng, nó được thêm vào trong iOS 6, nhưng nó cũng được hỗ trợ trong kỹ sư iOS 5. An Apple nói rằng trong WWDC 2012 phiên 216.
nonamelive

37

Kể từ iOS 4, bạn có thể sử dụng UINiblớp để giúp giải quyết vấn đề này.

  1. Tạo UINavigationBarlớp con tùy chỉnh của bạn .
  2. Tạo một xib trống, thêm a UINavigationControllerlàm đối tượng duy nhất.
  3. Đặt lớp cho UINavigationController's UINavigationBarthành lớp con tùy chỉnh của bạn.
  4. Đặt bộ điều khiển chế độ xem gốc của bạn thông qua một trong các phương pháp sau:
    • [navController setViewcontrollers[NSArray arrayWithObject:myRootVC]];
    • [navController pushViewController:myRootVC];

Trong mã:

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


Bây giờ bạn đã có một UINavigationControllertùy chỉnh của mình UINavigationBar.


Ngoại trừ sau đó, làm thế nào để bạn đặt rootViewController?
memmons

bạn sử dụng [setViewControllers navcontroller: [NSArray arrayWithObject: <YOUR_ROOT_CONTROLLER>]]
coneybeare

xin lỗi, bạn sử dụng bạn sử dụng [navcontroller pushViewController: <YOUR_ROOT_CONTROLLER> hoạt hình: KHÔNG]
coneybeare

3
IMHO đây là cách ít "hackish" nhất ở đây để thực hiện việc hack đôi khi không thể tránh khỏi này.
David Pisoni

2
Trên thực tế, một vấn đề kỳ lạ. Khi tôi làm điều này, navigationItem của navigationController mới không được đặt thành thuộc tính navigationItem được gán cho viewController mà tôi đang đẩy vào ngăn xếp (trống).
David Pisoni

26

Theo như tôi có thể nói, đôi khi thực sự cần thiết phải phân lớp UINavigationBar, để thực hiện một số cấu trúc lại không theo tiêu chuẩn. Đôi khi bạn có thể tránh phải làm như vậy bằng cách sử dụng các danh mục , nhưng không phải lúc nào cũng vậy.

Hiện tại, theo như tôi biết, cách duy nhất để đặt UINavigationBar tùy chỉnh trong UIViewController là thông qua IB (tức là thông qua một kho lưu trữ) - có lẽ không nên như vậy, nhưng hiện tại, chúng ta phải sống với nó.

Điều này thường ổn, nhưng đôi khi sử dụng IB không thực sự khả thi.

Vì vậy, tôi đã thấy ba tùy chọn:

  1. Subclass UINavigationBar và kết nối tất cả trong IB, sau đó bắt đầu tải lên nib mỗi khi tôi muốn có UINavigationController,
  2. Sử dụng phương thức thay thế trong một danh mục để thay đổi hành vi của UINavigationBar, thay vì phân lớp, hoặc
  3. Subclass UINavigationBar và thực hiện một chút thao tác với việc lưu trữ / hủy lưu trữ UINavigationController.

Tùy chọn 1 là không khả thi (hoặc ít nhất là quá khó chịu) đối với tôi trong trường hợp này, vì tôi cần tạo UINavigationController theo lập trình, 2 hơi nguy hiểm và theo ý kiến ​​của tôi là tùy chọn cuối cùng, vì vậy tôi đã chọn tùy chọn 3.

Cách tiếp cận của tôi là tạo một kho lưu trữ 'mẫu' của một UINavigationController và hủy lưu trữ nó, trả lại nó initWithRootViewController.

Đây là cách thực hiện:

Trong IB, tôi đã tạo một UINavigationController với lớp thích hợp được đặt cho UINavigationBar.

Sau đó, tôi lấy bộ điều khiển hiện có và lưu bản sao lưu trữ của nó bằng cách sử dụng +[NSKeyedArchiver archiveRootObject:toFile:]. Tôi vừa thực hiện việc này trong ứng dụng ủy quyền, trong trình mô phỏng.

Sau đó, tôi đã sử dụng tiện ích 'xxd' với cờ -i, để tạo mã c từ tệp đã lưu, để nhúng phiên bản đã lưu trữ vào lớp con ( xxd -i path/to/file) của tôi .

Trong initWithRootViewControllerTôi hủy lưu trữ mẫu đó và tự đặt thành kết quả của quá trình hủy lưu trữ:

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

Sau đó, tôi chỉ có thể lấy một phiên bản mới của lớp con UIViewController của tôi có bộ thanh điều hướng tùy chỉnh:

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

Điều này mang lại cho tôi một UITableViewController phương thức với thanh điều hướng và thanh công cụ đều được thiết lập và với lớp thanh điều hướng tùy chỉnh tại chỗ. Tôi không cần phải thực hiện bất kỳ thay thế phương pháp hơi khó chịu nào và tôi không phải bận tâm về ngòi khi tôi thực sự chỉ muốn làm việc theo chương trình.

Tôi muốn thấy tương đương với +layerClassbên trong UINavigationController - +navigationBarClass- nhưng hiện tại, điều này hoạt động.


5

Tôi sử dụng "tùy chọn 1"

Tạo một tệp nib chỉ với UINavigationController trong đó. Và đặt Lớp UINavigationBar thành Lớp tùy chỉnh của tôi.

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

[navigationController pushViewController:rootViewController animated:YES];

vì vậy tên thích hợp của nib-file sẽ được loadNibNamed: @ "navigationController Bạn đang thực sự nhận được toàn bộ navigationController từ ngòi và không chỉ thanh Phải không?.?
aneuryzm

điều này có hiệu quả với tôi, nhưng khi tôi nhấp vào bất kỳ tùy chọn nào trong chế độ xem bảng của mình, tôi sẽ mất nút quay lại.
jfisk

5

Giải pháp của Michael hoạt động, nhưng bạn có thể tránh được tiện ích NSKeyedArchiver và 'xxd'. Đơn giản chỉ cần Subclass UINavigationController và ghi đè initWithRootViewController, tải trực tiếp NavigationController NIB tùy chỉnh của bạn:

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

Cập nhật: Sử dụng object_SetClass()không còn hoạt động như iOS5 GM. Một giải pháp thay thế đã được thêm vào bên dưới.

Sử dụng NSKeyedUnarchiver để đặt thủ công lớp hủy lưu trữ cho thanh điều hướng.

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




Lưu ý: Giải pháp gốc này chỉ hoạt động trước iOS5:

Có một giải pháp tuyệt vời mà tôi đã đăng ở đây - đưa trực tiếp lớp con navBar vào chế độ xem của bạn UINavigationController:

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];

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

Như tôi đã chỉ ra trong câu trả lời trước mà bạn đã đăng điều này - bậc thầy vàng của iOS5 đã phá vỡ điều này. Tuy nhiên, có các tùy chọn khác, vì vậy tôi sẽ chỉnh sửa bằng một phương pháp thay thế.
memmons

1

Một tình huống mà tôi thấy rằng chúng ta cần sử dụng lớp con hơn là danh mục là đặt màu nền của thanh điều hướng với hình ảnh mẫu, bởi vì trong iOS5, việc ghi đè drawRect bằng danh mục không hoạt động nữa. Nếu bạn muốn hỗ trợ ios3.1-5.0, cách duy nhất bạn có thể làm là truy cập thanh điều hướng lớp con.


1

Các phương pháp danh mục này nguy hiểm và không dành cho người mới. Ngoài ra, sự phức tạp giữa iOS4 và iOS5 là khác nhau, khiến đây là một khu vực có thể gây ra lỗi cho nhiều người. Đây là một lớp con đơn giản mà tôi sử dụng hỗ trợ iOS4.0 ~ iOS6.0 và rất đơn giản.

.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

Không nên phân loại lớp con UINavigationBar. Cách ưa thích để tùy chỉnh thanh điều hướng là đặt các thuộc tính của nó để làm cho nó xuất hiện như bạn muốn và sử dụng các dạng xem tùy chỉnh bên trong UIBarButtonItems cùng với một đại biểu để có được hành vi mong muốn.

Bạn đang cố gắng làm gì mà cần phân lớp?

Ngoài ra, tôi không nghĩ rằng IB thực sự đang thay thế thanh điều hướng. Tôi khá chắc chắn rằng nó chỉ đơn giản là không hiển thị cái mặc định và có thanh điều hướng tùy chỉnh của bạn làm chế độ xem phụ. Nếu bạn gọi UINavigationController.navigationBar, bạn có nhận được một phiên bản của thanh của bạn không?


2
Xin chào Ben, cảm ơn. Tôi biết không phải là cách tốt hơn để phân lớp UINavigationBar nhưng tôi muốn thiết lập một hình nền ghi đè phương thức drawRect:. Vấn đề là bằng cách sử dụng IB, tôi có thể thay đổi lớp của Thanh điều hướng trong UINavigationController nhưng theo chương trình thì tôi không thể. Và vâng, IB thực sự đang thay thế thanh điều hướng: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; khung = (0 20; 320 44); clipsToBounds = CÓ; đục = KHÔNG; autoresize = W; layer = <CALayer: 0x1806da0 >>
Duccio

Tôi cũng đang sử dụng kỹ thuật này giống nhau, và nó tiếp tục làm việc trong iOS5 (không giống như các loại kỹ thuật UINavigationBar.)
David Pisoni


0

Ngoài nhận xét của obb64, tôi đã sử dụng thủ thuật của anh ấy setViewControllers:animated:để đặt bộ điều khiển làm bộ điều khiển rootControllerđược navigationControllertải từ ngòi. Đây là mã tôi đang sử dụng:

- (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];
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.