ตั้งค่าคลาสย่อยที่กำหนดเองของ UINavigationBar ใน UINavigationController โดยทางโปรแกรม


92

มีใครทราบบ้างว่าฉันจะใช้คลาสย่อยที่กำหนดเองได้UINavigationBarอย่างไรหากฉันสร้างอินสแตนซ์แบบUINavigationControllerโปรแกรม (โดยไม่มี IB)

ลากUINavigationControllerใน IB แสดงให้ฉันเห็นภายใต้แถบนำทางและการใช้ Identity Inspectory ฉันสามารถเปลี่ยนประเภทคลาสและตั้งค่าคลาสย่อยของฉันเองได้UINavigationBarแต่โดยทางโปรแกรมฉันทำไม่ได้navigationBarคุณสมบัติของ Navigation Controller เป็นแบบอ่านอย่างเดียว ...

ฉันควรทำอย่างไรเพื่อปรับแต่งแถบนำทางโดยใช้โปรแกรม IB "มีประสิทธิภาพ" มากกว่า "รหัส" หรือไม่? ฉันเชื่อว่าทุกสิ่งที่ทำได้ใน IB สามารถทำได้โดยใช้โปรแกรม


คุณมีโชคในการหาทางออกจากที่อื่นหรือไม่?
prendio2

คุณได้รับคำตอบเกี่ยวกับเรื่องนี้หรือไม่?
Hrushikesh Betai

คำตอบ:


89

คุณไม่จำเป็นต้องยุ่งกับ XIB เพียงแค่ใช้ KVC

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

โซลูชันนี้ใช้งานได้อย่างมีเสน่ห์ (อย่างน้อยบน iOS 5.1) วิธีแก้ปัญหาอื่น ๆ ทั้งหมดดูเหมือนจะได้ผลมากกว่า ยังคงมองหาข้อเสีย
Daniel

6
คุณพบคีย์พา ธ @ "navigationBar" สำหรับตัวควบคุมการนำทางได้อย่างไร คุณสามารถแบ่งปันสิ่งนี้
Iqbal Khan

คุณแน่ใจหรือไม่ว่าสิ่งนี้จะไม่ส่งผลให้เกิดการปฏิเสธแอป ฉันไม่เห็นเอกสารนี้เลยจริงๆ
Bani Uppal

ขอบคุณ! ใช้งานได้ดีใน iOS 6 beta 3 ด้วย! @BaniUppal KVC ได้รับการบันทึกไว้อย่างแน่นอนเราแค่ใช้มันกับคีย์ที่หายากนิดหน่อย ผมคิดว่าเป็นประเด็นหลัก
Johannes Lund

9
ดูเหมือนว่า AF จะ
แฮ็ค

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/…
Pascalius

7
ใช่มันถูกเพิ่มใน iOS 6 แต่ยังรองรับใน iOS 5 ด้วยวิศวกรของ Apple พูดถึงเรื่องนั้นใน WWDC 2012 เซสชัน 216
nonamelive

37

สำหรับ iOS 4 คุณสามารถใช้UINibคลาสเพื่อช่วยแก้ปัญหานี้ได้

  1. สร้างแบบกำหนดเองของคุณ UINavigationBarคลาสย่อย
  2. สร้าง xib ว่างเพิ่มไฟล์ UINavigationControllerเป็นวัตถุเดียว
  3. ตั้งค่าระดับสำหรับUINavigationController'sUINavigationBarเพื่อ subclass กำหนดเองของคุณ
  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
IMHO นี่เป็นวิธีที่ "แฮ็ก" น้อยที่สุดในการแฮ็กที่หลีกเลี่ยงไม่ได้ในบางครั้ง
David Pisoni

2
ที่จริงปัญหาแปลกอย่างหนึ่ง เมื่อฉันทำเช่นนี้ navigationItem ของ navigationController ใหม่จะไม่ถูกตั้งค่าเป็นคุณสมบัติ navigationItem ที่กำหนดให้กับ viewController ที่ฉันกำลังผลักไปยังสแต็ก (ว่าง)
David Pisoni

26

เท่าที่ฉันสามารถบอกได้บางครั้งจำเป็นที่จะต้อง subclass UINavigationBar เพื่อทำการ restyling ที่ไม่ได้มาตรฐาน บางครั้งก็เป็นไปได้ที่จะหลีกเลี่ยงการใช้หมวดหมู่แต่ก็ไม่เสมอไป

ปัจจุบันเท่าที่ฉันรู้มีเพียงวิธีในการตั้งค่า UINavigationBar ที่กำหนดเองภายใน UIViewController คือผ่านทาง IB (นั่นคือผ่านไฟล์เก็บถาวร) - มันอาจจะไม่เป็นอย่างนั้น แต่สำหรับตอนนี้เราต้องอยู่กับมัน

ซึ่งมักจะใช้ได้ดี แต่บางครั้งการใช้ IB ก็ไม่สามารถทำได้จริงๆ

ดังนั้นฉันเห็นสามตัวเลือก:

  1. UINavigationBar คลาสย่อยและเชื่อมต่อทั้งหมดใน IB จากนั้นยุ่งเกี่ยวกับการโหลดหัวปากกาทุกครั้งที่ฉันต้องการ UINavigationController
  2. ใช้วิธีการแทนที่ภายในหมวดหมู่เพื่อเปลี่ยนลักษณะการทำงานของ UINavigationBar แทนที่จะเป็นคลาสย่อยหรือ
  3. Subclass 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-file โดยมีเพียง UINavigationController อยู่ในนั้น และตั้งค่า UINavigationBar Class เป็น Custom Class ของฉัน

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

[navigationController pushViewController:rootViewController animated:YES];

ดังนั้นชื่อที่เหมาะสมของ nib-file จะเป็น loadNibNamed: @ "navigationController จริง ๆ แล้วคุณได้รับ navigationController ทั้งหมดจากปลายปากกาไม่ใช่เฉพาะแถบใช่ไหม
aneuryzm

วิธีนี้ใช้ได้ผลสำหรับฉัน แต่เมื่อฉันคลิกที่ตัวเลือกใด ๆ ใน tableview ของฉันฉันทำปุ่มย้อนกลับหายไป
jfisk

5

โซลูชันของ Michael ใช้งานได้ แต่คุณสามารถหลีกเลี่ยงยูทิลิตี้ NSKeyedArchiver และ 'xxd' ได้ เพียงแค่ Subclass UINavigationController และ override initWithRootViewControllerโหลด NavigationController 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;
}

4

อัปเดต:การใช้งาน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
}

ดังที่ฉันได้ระบุไว้ในคำตอบก่อนหน้านี้ที่คุณโพสต์ไว้ - ผู้เชี่ยวชาญระดับทองของ 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: method ปัญหาคือการใช้ IB ฉันสามารถเปลี่ยนคลาสของแถบนำทางใน UINavigationController ได้ แต่โดยทางโปรแกรมฉันทำไม่ได้ และใช่ IB กำลังแทนที่แถบนำทาง: NSLog (@ "% @", self.navigationController.navigationBar); <CustomNavigationBar: 0x1806160; baseClass = UINavigationBar; เฟรม = (0 20; 320 44); clipsToBounds = ใช่; ทึบแสง = ไม่; ปรับขนาดอัตโนมัติ = W; ชั้น = <CALayer: 0x1806da0 >>
Duccio

ฉันยังใช้เทคนิคเดียวกันนี้และยังคงใช้งานได้ใน iOS5 (ไม่เหมือนเทคนิค UINavigationBar Category)
David Pisoni

0

หากคุณต้องการ subclass navBar เพียงเพื่อเปลี่ยนภาพพื้นหลัง - ไม่จำเป็นต้องทำใน iOS 5 จะมีวิธีการเช่นนี้หนึ่งsetBackgroundImage


1
ต้องใช้setBackgroundImage:forBarMetrics:ตามที่อธิบายไว้ที่นี่developer.apple.com/library/IOS/#documentation/UIKit/Reference/…
Ahti

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];
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.