วิธีที่ถูกต้องในการโหลด Nib สำหรับคลาสย่อย UIView


98

ฉันทราบว่ามีการถามคำถามนี้มาก่อน แต่คำตอบขัดแย้งกันและฉันสับสนดังนั้นโปรดอย่าทำให้ฉันตกใจ

ฉันต้องการมีUIViewคลาสย่อยที่ใช้ซ้ำได้ในแอปของฉัน ฉันต้องการอธิบายอินเทอร์เฟซโดยใช้ไฟล์ nib

ตอนนี้สมมติว่าเป็นมุมมองตัวบ่งชี้การโหลดที่มีตัวบ่งชี้กิจกรรมอยู่ในนั้น ฉันต้องการให้เหตุการณ์บางอย่างสร้างอินสแตนซ์ของมุมมองนี้และทำให้เคลื่อนไหวในมุมมองของตัวควบคุมมุมมอง ฉันสามารถอธิบายอินเทอร์เฟซของมุมมองได้อย่างไม่มีปัญหาโดยทางโปรแกรมสร้างองค์ประกอบโดยทางโปรแกรมและตั้งค่าเฟรมภายในวิธีการเริ่มต้นเป็นต้น

ฉันจะทำสิ่งนี้โดยใช้ปลายปากกาได้อย่างไร? การรักษาขนาดที่กำหนดในตัวสร้างอินเทอร์เฟซโดยไม่ต้องกำหนดกรอบ

ฉันทำแบบนี้แล้ว แต่ฉันแน่ใจว่ามันผิด (มันเป็นเพียงมุมมองที่มีตัวเลือกอยู่ในนั้น):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

แต่ฉันเขียนทับตัวเองและเมื่อฉันสร้างอินสแตนซ์:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

ฉันต้องตั้งค่าเฟรมด้วยตนเองแทนที่จะรับจาก IB

แก้ไข: ฉันต้องการสร้างมุมมองแบบกำหนดเองนี้ในตัวควบคุมมุมมองและมีสิทธิ์เข้าถึงเพื่อควบคุมองค์ประกอบของมุมมอง ฉันไม่ต้องการตัวควบคุมมุมมองใหม่

ขอบคุณ

แก้ไข: ฉันไม่รู้ว่านี่เป็นแนวทางปฏิบัติที่ดีที่สุดหรือไม่ฉันแน่ใจว่าไม่ใช่ แต่นี่คือวิธีที่ฉันทำ:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

การปฏิบัติที่ถูกต้องยังคงต้องการ

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


แต่โค้ดบรรทัดแรกไม่เหมือนกับโค้ดที่คุณใช้ในคำถามเดิมinitWithDataSourceใช่หรือไม่ อย่างไรก็ตามสิ่งนี้จะใช้ได้แม้ว่าคุณจะให้เจ้าของเป็น 'Nil'
Rakesh

เป็นที่น่าสังเกตว่าทุกวันนี้ใน 99.99999% ของกรณีหนึ่งจะใช้มุมมองคอนเทนเนอร์ซึ่งตอนนี้ใช้ทุกที่และตลอดเวลาใน iOS บทความวิธีใช้
Fattie

โปรดทราบว่าคุณสามารถแทนที่ [NSString stringWithFormat: @ "% @", [FSASelectView class]] ด้วย NSStringFromClass ([FSASelectView class])
naomimichiko

คำตอบ:


132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

ฉันใช้สิ่งนี้เพื่อเริ่มต้นมุมมองแบบกำหนดเองที่ใช้ซ้ำได้ที่ฉันมี


โปรดทราบว่าคุณสามารถใช้"firstObject"ต่อท้ายได้ซึ่งจะสะอาดกว่าเล็กน้อย "firstObject" เป็นวิธีการที่สะดวกสำหรับ NSArray และ NSMutableArray

นี่คือตัวอย่างทั่วไปของการโหลด xib เพื่อใช้เป็นส่วนหัวของตาราง ในไฟล์ YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

โดยปกติในTopArea.xibคุณจะคลิกที่ไฟล์เจ้าของและตั้งเจ้าของไฟล์เพื่อ YourClass จากนั้นใน YourClass.hคุณจะมีคุณสมบัติ IBOutlet ในTopArea.xibคุณสามารถลากตัวควบคุมไปยังร้านเหล่านั้น

อย่าลืมว่าในTopArea.xibนั้นคุณอาจต้องคลิกที่ Viewแล้วลากไปยังเต้าเสียบบางแห่งดังนั้นคุณจึงสามารถควบคุมได้หากจำเป็น (เคล็ดลับที่คุ้มค่ามากคือเมื่อคุณทำสิ่งนี้กับแถวเซลล์ตารางคุณต้องทำอย่างนั้น - คุณต้องเชื่อมต่อมุมมองกับคุณสมบัติที่เกี่ยวข้องในโค้ดของคุณ)


ไม่มี initWithNibName: วิธีการสำหรับ UIView เป็นเพียงสำหรับ UIViewController
meronix

สำหรับคอนโทรลเลอร์มุมมองฉันต้องการใช้ UIView และมีคอนโทรลเลอร์ที่สร้างมันขึ้นมาเพื่อเป็นเจ้าของ
Adam Waite

ฉันขอโทษฉันคัดลอกรหัสผิดด้วยความเร่งรีบฉันได้อัปเดตคำตอบแล้วโปรดดู
Premsuraj

ฉันจะทำเครื่องหมายว่าถูกต้อง ฉันไม่รู้ว่ามันเป็นแนวทางปฏิบัติที่ดีที่สุด แต่ได้ผล
Adam Waite

@ Joe Blow ขออภัยสำหรับการชน แต่ทำไมต้องทำสิ่งนี้: "อย่าลืมว่าใน TopArea.xib คุณอาจต้องคลิกที่ View แล้วลากไปยังเต้าเสียบเพื่อให้คุณสามารถควบคุมได้ , ในกรณีที่จำเป็น." คุณสามารถใช้ myViewObject เพื่อเข้าถึงมุมมองพื้นฐานแทนได้หรือไม่เนื่องจากเป็น UIView เอง เหตุใดจึงมีความต้องการอสังหาริมทรัพย์
user1686342

39

หากคุณต้องการให้ของคุณCustomViewและxibเป็นอิสระจากFile's Ownerนั้นทำตามขั้นตอนเหล่านี้

  • เว้นFile's Ownerช่องว่างไว้
  • คลิกที่มุมมองจริงในxibไฟล์ของคุณCustomViewและตั้งCustom Classเป็นCustomView(ชื่อของคลาสมุมมองที่กำหนดเองของคุณ)
  • เพิ่มIBOutletใน.hไฟล์ของมุมมองแบบกำหนดเองของคุณ
  • ในแฟ้มในมุมมองของคุณเองคลิกที่มุมมองและไปใน.xib Connection Inspectorที่นี่คุณจะ IBOutlets ของคุณทั้งหมดที่คุณกำหนดไว้ใน.hไฟล์
  • เชื่อมต่อกับมุมมองตามลำดับ

ใน.mไฟล์ของCustomViewชั้นเรียนของคุณให้แทนที่initวิธีการดังต่อไปนี้

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

ตอนนี้เมื่อคุณต้องการโหลดของคุณCustomViewให้ใช้โค้ดบรรทัดต่อไปนี้ [[CustomView alloc] init];


1
สำหรับ iOS 9 นี่เป็นโซลูชันเดียวที่แสดงในหน้านี้ซึ่งใช้งานได้จริงสำหรับฉัน (วิธีแก้ปัญหาอื่น ๆ ทำให้เกิดปัญหา)
lifjoy

โปรดทราบว่าวิธีนี้ใช้ไม่ได้กับการโหลดมุมมองแบบกำหนดเองของคุณจาก XIB ที่มีอยู่เนื่องจากไม่มีการเรียก-initใช้ แต่มันจะโทรinitWithFrame:และ-awakeFromNib. หากคุณเขียนโค้ดเดียวกันเพื่อโหลดมุมมองของคุณเช่นเดียวกับ-initวิธีการคุณจะเข้าสู่การวนซ้ำที่ไม่มีที่สิ้นสุด
ปิแอร์

25

ทำตามขั้นตอนต่อไปนี้

  1. สร้างชั้นเรียนที่มีชื่อ MyView .h / .m UIViewประเภท
  2. สร้าง Xib MyView.xibชื่อเดียวกัน
  3. ตอนนี้เปลี่ยนคลาส File Owner เป็นUIViewControllerfrom NSObjectใน xib ดูภาพด้านล่าง ป้อนคำอธิบายภาพที่นี่
  4. เชื่อมต่อ File Owner View กับ View ของคุณ ดูภาพด้านล่าง ป้อนคำอธิบายภาพที่นี่

  5. MyViewเปลี่ยนชั้นเรียนของคุณดูไป เช่นเดียวกับ 3.

  6. วางการควบคุมสร้าง IBOutlets

นี่คือรหัสสำหรับโหลด View:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

หวังว่าจะช่วยได้

ชี้แจง :

UIViewControllerใช้เพื่อโหลด xib ของคุณและมุมมองที่UIViewControllerมีจริงMyViewซึ่งคุณได้กำหนดไว้ใน MyView xib ..

การสาธิต ฉันได้ทำการสาธิตที่นี่


ทำไมคนลงคะแนนนี้ผิด? ฉันยังไม่ได้ลอง แต่ดูเหมือนว่ามันจะไม่ทำตามที่ฉันต้องการ
Adam Waite

ฉันโหวตไม่ลง แต่นั่นทำให้ฉันอ่านคำตอบของคุณผิด (คิดว่าคุณกำลังกำหนดคลาสย่อย UIView เป็นเจ้าของไฟล์) ขอโทษด้วยกับเรื่องนั้น.
Rakesh

2
ในไฟล์ xib ของคุณไม่สนใจเจ้าของไฟล์ จากนั้นคุณสามารถหลีกเลี่ยงการสร้าง UIViewController ได้เพียงแค่ทำ MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk

1
@LightningStryk ใช่แน่นอน แต่เป็นอีกวิธีหนึ่งที่จะทำ
iphonic

1
คือการสร้างคลาสย่อย UIView ที่ใช้ซ้ำได้ไม่ใช่เพื่อใช้ UIViewController เพื่อจุดประสงค์นั้น
arielyz

11

ตอบคำถามของตัวเองประมาณ 2 ปีต่อมาที่นี่ แต่ ...

ใช้ส่วนขยายโปรโตคอลเพื่อให้คุณสามารถทำได้โดยไม่ต้องมีรหัสพิเศษสำหรับทุกคลาส

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()

8

ใน Swift:

ตัวอย่างเช่นชื่อคลาสที่กำหนดเองของคุณคือ InfoView

ในตอนแรกคุณสร้างไฟล์InfoView.xibและทำInfoView.swiftสิ่งนี้:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

จากนั้นตั้งค่าFile's Ownerเป็นUIViewControllerดังนี้:

ป้อนคำอธิบายภาพที่นี่

เปลี่ยนชื่อViewเป็นInfoView:

ป้อนคำอธิบายภาพที่นี่

คลิกขวาเพื่อFile's Ownerเชื่อมต่อviewฟิลด์ของคุณกับInfoView:

ป้อนคำอธิบายภาพที่นี่

ตรวจสอบให้แน่ใจว่าชื่อคลาสคือInfoView:

ป้อนคำอธิบายภาพที่นี่

และหลังจากนี้คุณสามารถเพิ่มปุ่ม action to ในคลาสที่กำหนดเองได้โดยไม่มีปัญหา:

ป้อนคำอธิบายภาพที่นี่

และการใช้คลาสที่กำหนดเองนี้ในMainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}

2

คุณสามารถเริ่มต้น xib โดยใช้ตัวควบคุมมุมมองและใช้ viewController.view หรือทำในแบบที่คุณทำ การสร้างUIViewคลาสย่อยเป็นตัวควบคุมเท่านั้นUIViewเป็นความคิดที่ไม่ดี

หากคุณไม่มีร้านค้าใด ๆ จากมุมมองที่กำหนดเองของคุณคุณสามารถใช้UIViewControllerคลาสเพื่อเริ่มต้นได้โดยตรง

Update:ในกรณีของคุณ:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

มิฉะนั้นคุณต้องกำหนดเองUIViewController(เพื่อให้เป็นเจ้าของไฟล์เพื่อให้มีการต่อสายที่เหมาะสม)

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

ทุกที่ที่คุณต้องการเพิ่มมุมมองที่คุณสามารถใช้ได้

[parentView addSubview:yCustCon.view];

อย่างไรก็ตามการส่งตัวควบคุมมุมมองอื่น (ถูกใช้สำหรับมุมมองอื่นอยู่แล้ว) ในฐานะเจ้าของในขณะที่โหลด xib ไม่ใช่ความคิดที่ดีเนื่องจากคุณสมบัติมุมมองของคอนโทรลเลอร์จะเปลี่ยนไปและเมื่อคุณต้องการเข้าถึงมุมมองดั้งเดิมคุณจะไม่ มีการอ้างอิงถึงมัน

แก้ไข:คุณจะประสบปัญหานี้หากคุณตั้งค่า xib ใหม่กับเจ้าของไฟล์เป็นUIViewControllerคลาสหลักเดียวกันและเชื่อมโยงคุณสมบัติมุมมองกับมุมมอง xib ใหม่

ได้แก่ ;

  • YourMainViewController - จัดการ - mainView
  • CustomView - ต้องโหลดจาก xib เป็นและเมื่อจำเป็น

โค้ดด้านล่างนี้จะทำให้เกิดความสับสนในภายหลังหากคุณเขียนในมุมมองภายในไม่ได้โหลดYourMainViewControllerขึ้นมา นั่นเป็นเพราะself.viewจากจุดนี้ไปจะอ้างถึงมุมมองที่คุณกำหนดเอง

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}

ตกลงโดยทั่วไปแนวทางปฏิบัติที่ดีที่สุดระบุว่า UIView ทุกตัวควรมีตัวควบคุมที่เกี่ยวข้องหรือไม่?
Adam Waite

ไม่จำเป็น. แต่เมื่อโหลดมุมมองจาก xib แยกต่างหากนั่นจะเป็นวิธีที่ไม่มีปัญหา Apple Docs รุ่นก่อน IOS5 กล่าวว่าไม่ควรใช้คอนโทรลเลอร์มุมมองเดียวเพื่อควบคุมมากกว่าหนึ่งฉาก (xib) และในทางกลับกัน
Rakesh

xib เป็นขนาดของมุมมองการแจ้งเตือนเท่านั้น
Adam Waite

โดยส่วนตัวแล้วฉันรู้สึกว่ามันขึ้นอยู่กับความซับซ้อนของ code / xib ว่าคุณต้องสร้างคอนโทรลเลอร์ใหม่หรือไม่ หากคุณสามารถจัดการกับปัญหาที่เกิดขึ้นได้สำเร็จและหากคุณไม่ได้มองไปที่การแก้ไขในอนาคตก็ไม่ใช่ปัญหา แต่การสร้างตัวควบคุมมุมมองแยกต่างหากจะช่วยแก้ปัญหาให้คุณได้มาก และเนื่องจากในกรณีของคุณเป็นเพียงตัวบ่งชี้กิจกรรมคุณสามารถใช้วัตถุ UIViewController (ตามที่กล่าวไว้ในคำตอบ) ได้อย่างง่ายดาย ฉันจะอัปเดตคำตอบเพื่อดำเนินการตามนี้
Rakesh

1
ดังนั้นฉันสามารถวาดอินเทอร์เฟซในตัวสร้างอินเทอร์เฟซแทนที่จะทำโดยใช้โปรแกรม ไม่ใช่ปัญหาในการทำแบบเป็นโปรแกรมฉันแค่อยากรู้วิธีซับคลาส UIView และให้มันปลายปากกา! นี่ไม่ควรจะยาก
Adam Waite
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.