Cách tải UIView bằng tệp nib được tạo bằng Trình tạo giao diện


241

Tôi đang cố gắng làm một cái gì đó hơi phức tạp, nhưng một cái gì đó có thể. Vì vậy, đây là một thách thức cho tất cả các bạn chuyên gia ngoài kia (diễn đàn này là một gói của rất nhiều bạn :)).

Tôi đang tạo một "thành phần" Bảng câu hỏi mà tôi muốn tải trên NavigationContoller(của tôi QuestionManagerViewController). "Thành phần" là "trống" UIViewController, có thể tải các chế độ xem khác nhau tùy thuộc vào câu hỏi cần trả lời.

Cách tôi đang làm là:

  1. Tạo đối tượng Câu hỏi 1 dưới dạng một UIViewlớp con, xác định một số IBOutlets.
  2. Tạo (sử dụng Trình tạo giao diện) Question1View.xib (TẠI ĐÂY LÀ VẤN ĐỀ CỦA TÔI Ở ĐÂU ). Tôi đặt cả UIViewControllervà và UIViewlà của lớp Câu hỏi 1.
  3. Tôi liên kết các cửa hàng với thành phần của khung nhìn (sử dụng IB).
  4. Tôi ghi đè lên initWithNibcủa tôi QuestionManagerViewControllerđể trông như thế này:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }

Khi tôi chạy mã, tôi gặp lỗi này:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Ứng dụng chấm dứt do ngoại lệ chưa được xử lý ' NSInternalInconsistencyException', lý do: ' -[UIViewController _loadViewFromNibNamed:bundle:]đã tải ngòi "Câu hỏi 1View" nhưng ổ cắm chế độ xem chưa được đặt.'

Tôi chắc chắn có một cách để tải chế độ xem bằng tệp nib, mà không cần phải tạo lớp viewContoder.

Câu trả lời:


224

Ngoài ra còn có một cách dễ dàng hơn để truy cập vào chế độ xem thay vì xử lý ngòi dưới dạng một mảng.

1 ) Tạo một lớp con Xem tùy chỉnh với bất kỳ cửa hàng nào bạn muốn có quyền truy cập sau này. - Xem của tôi

2 ) trong UIViewContoder mà bạn muốn tải và xử lý ngòi, tạo một thuộc tính IBOutlet sẽ giữ chế độ xem của nib đã tải, chẳng hạn

trong MyViewControll (một lớp con UIViewControll)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(đừng quên tổng hợp nó và phát hành nó trong tệp .m của bạn)

3) mở ngòi của bạn (chúng tôi sẽ gọi nó là 'myViewNib.xib') trong IB, đặt Chủ sở hữu tệp của bạn thành MyViewControll

4) bây giờ kết nối ổ cắm Chủ sở hữu tệp của bạn myViewFromNib với chế độ xem chính trong ngòi bút.

5) Bây giờ trong MyViewControll, hãy viết dòng sau:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Bây giờ ngay khi bạn làm điều đó, việc gọi tài sản của bạn là "self.myViewFromNib" sẽ cho phép bạn truy cập vào chế độ xem từ ngòi của bạn!


4
Nó sẽ hoạt động cho chế độ xem bộ điều khiển chính (self.view)? Vì một số lý do, tôi cần tải chế độ xem chính từ nib sau phương thức init của bộ điều khiển tiêu chuẩn. Lý do - cấu trúc phân lớp phức tạp
Lukasz

@Lukasz Bao nhiêu bạn thành công để làm điều đó? Ngay cả tôi đang tìm kiếm một cái gì đó giống như bạn.
Ajay Sharma

Xin lỗi tôi có thể dày, nhưng tôi bối rối ở điểm đầu tiên. Làm cách nào để tạo các cửa hàng trong một lớp con xem tùy chỉnh? Tôi nghĩ các cửa hàng chỉ dành cho UIViewControllers?
jowie

1
Còn trường hợp khi khung nhìn này xuất hiện trên nhiều khung nhìn cha mẹ thì sao? Vì vậy, bạn có đề xuất chỉnh sửa xib cho mỗi người trong số họ bằng tay? Đó là QUÁ TUYỆT VỜI !!!
dùng2083364

2
Điều này chỉ hoạt động nếu bạn muốn sử dụng ngòi tùy chỉnh trong một bộ điều khiển xem duy nhất. Trong trường hợp bạn muốn sử dụng nó trên các bộ điều khiển chế độ xem khác nhau, mỗi bộ tạo một thể hiện của ngòi tùy chỉnh một cách linh hoạt, điều này sẽ không hoạt động.
Junc

120

Cảm ơn tất cả. Tôi đã tìm ra cách để làm những gì tôi muốn.

  1. Tạo của bạn UIViewvới IBOutlets bạn cần.
  2. Tạo xib trong IB, thiết kế nó theo ý thích của bạn và liên kết nó như thế này: Chủ sở hữu tệp là của lớp UIViewController(Không có lớp con tùy chỉnh, nhưng là lớp "thực"). Khung nhìn của Chủ sở hữu tệp được kết nối với khung nhìn chính và lớp của nó được khai báo là khung nhìn từ bước 1).
  3. Kết nối điều khiển của bạn với IBOutlets.
  4. DynamicViewControllerthể chạy logic của nó để quyết định xem / xib tải gì. Khi nó thực hiện phân rã, trong loadViewphương thức đặt một cái gì đó như thế này:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;

Đó là nó!

loadNibNamedPhương thức của gói chính sẽ đảm nhiệm việc khởi tạo chế độ xem và tạo các kết nối.

Bây giờ ViewContoder có thể hiển thị một khung nhìn hoặc một khung nhìn khác tùy thuộc vào dữ liệu trong bộ nhớ và màn hình "cha mẹ" không cần phải bận tâm với logic này.


2
Tôi có một vấn đề rất giống nhau - tôi chỉ đơn giản muốn tải một UIView từ tệp xib. Các bước này không hoạt động đối với tôi - ứng dụng gặp sự cố với "quyền truy cập xấu" ngay sau khi thêm chế độ xem được tải dưới dạng chế độ xem phụ.
Justicle

2
Đối với iPhone 3.0, hãy sử dụng objectAt Index: 0 để lấy phần tử đầu tiên. Sự cố này đối với tôi chính xác như được mô tả bởi Justicle. Bất cứ ý tưởng tại sao?
tba

1
+1 nó hoạt động hoàn hảo cho tôi. Tôi muốn thêm nhiều chế độ xem có bộ điều khiển một chế độ xem (tôi đang sử dụng kịch bản xem lật trong đó một phần của chế độ xem) Tôi phải tạo chỉ mục trên mảng 0 (iPhone 3.0)
Aran Mulholland

42
Đây là câu trả lời đúng, tuy nhiên, mã phải được sửa đổi để nó lặp qua nibView và kiểm tra lớp trên từng đối tượng, do đó bạn chắc chắn sẽ nhận được đúng đối tượng. objectAt Index: 1 là một giả định nguy hiểm.
M. Ryan

2
Jasconius đã đúng. Bạn nên lặp qua mảng nibViews như thế này: gist.github.com/769539
leviathan

71

Tôi không chắc một số câu trả lời đang nói về điều gì, nhưng tôi cần đặt câu trả lời này ở đây khi tôi tìm kiếm trong Google vào lần tới. Từ khóa: "Cách tải UIView từ ngòi" hoặc "Cách tải UIView từ NSBundle."

Đây là mã gần như 100% ngay từ cuốn sách Apress Beginning iPhone 3 (trang 247, "Sử dụng ô xem bảng mới"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Điều này giả sử bạn có một UIViewlớp con được gọi Blah, một ngòi gọi Blahcó chứa một UIViewlớp có lớp được đặt Blah.


Thể loại: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Mở rộng Swift

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

Và một ví dụ được sử dụng:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}

2
Nếu bạn sử dụng, isKindOfbạn được bảo vệ khỏi các thay đổi cấu trúc Bundle.
Dan Rosenstark

1
An assertcó lẽ là một giải pháp tốt hơn. Bằng cách này, bạn sẽ chỉ che giấu vấn đề.
Sulthan

1
@Sulthan một khẳng định vốn đã khác. Tôi muốn đối tượng của loại Blah bất cứ nơi nào nó ngồi trong Nib . Tôi không quan tâm nếu nó được thăng chức hoặc hạ cấp. Tuy nhiên, để đáp lại bình luận của bạn, tôi đã thêm một xác nhận, nhưng nó không thay thế cho forvòng lặp. Nó bổ sung cho nó.
Dan Rosenstark

Bạn có thể thực hiện loadFromNib mà không có tham số: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([self class]) for (id object in bundle) {if ([object isKindOfClass: [self class]]) {return object; }} return nil; }
JVC

22

Đối với tất cả những người cần quản lý nhiều hơn một phiên bản của chế độ xem tùy chỉnh, đó là Bộ sưu tập Outlet , tôi đã hợp nhất và tùy chỉnh các câu trả lời @Gonso, @AVeryDev và @Olie theo cách này:

  1. Tạo một tùy chỉnh MyView : UIViewvà đặt nó là " Lớp tùy chỉnh " của thư mục gốc UIViewtrong XIB mong muốn ; lớp học tùy chỉnh

  2. Tạo tất cả các cửa hàng bạn cần MyView(thực hiện ngay bây giờ vì sau điểm 3, IB sẽ đề xuất bạn kết nối các cửa hàng với UIViewControllervà không với chế độ xem tùy chỉnh như chúng tôi muốn); cửa hàng lớp học tùy chỉnh

  3. Đặt UIViewControllerlàm " Chủ sở hữu tệp " của chế độ xem tùy chỉnh XIB ; nhập mô tả hình ảnh ở đây

  4. bên trong UIViewController thêm mới UIViewscho từng phiên bản MyViewbạn muốn và kết nối chúng để UIViewControllertạo Bộ sưu tập Outlet : các chế độ xem này sẽ đóng vai trò là chế độ xem "trình bao bọc" cho các phiên bản xem tùy chỉnh; nhập mô tả hình ảnh ở đây

  5. Cuối cùng, trong viewDidLoadcác bạn UIViewControlleradd các dòng sau:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..

Xin chào ... bạn biết làm thế nào để làm điều này theo chương trình tức là. không phải bằng cách sử dụng tập tin xib?
Bộ giải mã X

19

Tôi sẽ sử dụng UINib để khởi tạo một UIView tùy chỉnh được sử dụng lại

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

Các tệp cần thiết trong trường hợp này là MyCustomView.xib , MyCustomViewClass.hMyCustomViewClass.m Lưu ý [UINib instantiateWithOwner]trả về một mảng, vì vậy bạn nên sử dụng phần tử phản ánh UIView bạn muốn sử dụng lại. Trong trường hợp này, đó là yếu tố đầu tiên.


1
Bạn không có nghĩa là "customNib" trong dòng 2 thay vì "xếp hạngNib"
Danny

11

Bạn không nên đặt lớp của trình điều khiển chế độ xem của mình thành một lớp con UIViewtrong Trình tạo giao diện. Đó chắc chắn là ít nhất một phần của vấn đề của bạn. Để lại một trong hai UIViewControllerlớp con của nó hoặc một số lớp tùy chỉnh khác mà bạn có.

Đối với việc chỉ tải một chế độ xem từ xib, tôi đã giả định rằng bạn phải có một bộ điều khiển chế độ xem nào đó (ngay cả khi nó không mở rộng UIViewController, có thể quá nặng đối với nhu cầu của bạn) được đặt làm Chủ sở hữu tệp trong Giao diện Builder nếu bạn muốn sử dụng nó để xác định giao diện của bạn. Tôi đã làm một nghiên cứu nhỏ để xác nhận điều này là tốt. Điều này là do nếu không, sẽ không có cách nào để truy cập bất kỳ thành phần giao diện nào trong đó UIView, cũng như không có cách nào để các phương thức của riêng bạn trong mã được kích hoạt bởi các sự kiện.

Nếu bạn sử dụng UIViewControllerlàm Chủ sở hữu tệp cho các chế độ xem của mình, bạn chỉ có thể sử dụng initWithNibName:bundle:để tải tệp đó và lấy lại đối tượng điều khiển xem. Trong IB, đảm bảo bạn đặt viewổ cắm ở chế độ xem với giao diện của bạn trong xib. Nếu bạn sử dụng một số loại đối tượng khác làm Chủ sở hữu tệp của mình, bạn sẽ cần sử dụng phương thức NSBundlecủa nó loadNibNamed:owner:options:để tải ngòi, chuyển một thể hiện của Chủ sở hữu tệp cho phương thức. Tất cả các thuộc tính của nó sẽ được đặt đúng theo các cửa hàng bạn xác định trong IB.


Tôi đang đọc cuốn sách Apress, "Bắt đầu phát triển iPhone: Khám phá SDK iPhone" và họ đã thảo luận về phương pháp này ở chương 9: Bộ điều khiển điều hướng và Chế độ xem bảng. Nó không có vẻ quá phức tạp.
Lyndsey Ferguson

Tôi tò mò muốn xem họ nói thế nào rồi. Tôi không có một bản sao của cuốn sách đó vào lúc này.
Marc W

10

Bạn cũng có thể sử dụng initWithNibName của UIViewContoder thay vì loadNibNamed. Nó đơn giản hơn, tôi tìm thấy.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Bây giờ bạn chỉ cần tạo MySubView.xib và MySubView.h / m. Trong MySubView.xib, đặt lớp Chủ sở hữu tệp thành UIViewControll và xem lớp thành MySubView.

Bạn có thể định vị và kích thước của subviewtệp xib cha.


8

Đây là một câu hỏi hay (+1) và các câu trả lời gần như hữu ích;) Xin lỗi các bạn, nhưng tôi đã có một chút thời gian để lén lút thông qua điều này, mặc dù cả Gonso & AVeryDev đều đưa ra gợi ý tốt. Hy vọng, câu trả lời này sẽ giúp người khác.

MyVC là bộ điều khiển xem giữ tất cả những thứ này.

MySubview là chế độ xem mà chúng tôi muốn tải từ xib

  • Trong MyVC.xib, tạo chế độ xem loại MySubViewcó kích thước & hình dạng phù hợp & định vị nơi bạn muốn.
  • Trong MyVC.h, có

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • Trong MyVC.m, @synthesize mySubView;và đừng quên phát hành nó dealloc.

  • Trong MySubview.h, có một ổ cắm / thuộc tính cho UIView *view(có thể không cần thiết, nhưng hoạt động với tôi.) Tổng hợp và phát hành nó trong .m
  • Trong MySubview.xib
    • đặt loại chủ sở hữu tệp thành MySubviewvà liên kết thuộc tính chế độ xem với chế độ xem của bạn.
    • Bố trí tất cả các bit và kết nối với các IBOutlettùy chọn
  • Quay lại MyVC.m, có

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

Một mẹo nhỏ đối với tôi là: các gợi ý trong các câu trả lời khác đã tải chế độ xem của tôi từ xib, nhưng KHÔNG thay thế chế độ xem trong MyVC (duh!) - Tôi phải tự đổi chỗ đó.

Ngoài ra, để có quyền truy cập vào mySubviewcác phương thức, thuộc viewtính trong tệp .xib phải được đặt thành MySubview. Nếu không, nó trở lại như một người già UIView.

Nếu có một cách để tải mySubviewtrực tiếp từ xib của chính nó, thì đó là đá, nhưng điều này đã đưa tôi đến nơi tôi cần.


1
Tôi đã sử dụng phương pháp này, cảm ơn. Một thay đổi tôi đã thực hiện để cho phép nó hỗ trợ các chế độ xem lồng nhau là thay đổi [ self.view insertSubview: msView ở trênSubview: mySubview]; đến [ mySubview.superview insertSubview: msView ở trênSubview: mySubview];
Jason

8

Đây là một cái gì đó phải dễ dàng hơn. Tôi đã kết thúc việc mở rộng UIViewControllervà thêm một loadNib:inPlaceholder:bộ chọn. Bây giờ tôi có thể nói

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Đây là mã cho danh mục (nó thực hiện cùng một Rigamarole như được mô tả bởi Gonso):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end

7

Trong nhanh chóng

Trên thực tế, giải pháp của tôi cho vấn đề này là, để tải chế độ xem trong viewDidLoad trong CustonViewControll của tôi, nơi tôi muốn sử dụng chế độ xem như thế:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Đừng tải chế độ xem trong một loadView()phương thức! Phương thức loadView phục vụ để tải chế độ xem cho ViewContoder tùy chỉnh của bạn.


6

Tôi cũng muốn làm một cái gì đó tương tự, đây là những gì tôi tìm thấy: (SDK 3.1.3)

Tôi có bộ điều khiển xem A (được sở hữu bởi bộ điều khiển Nav) tải VC B trên một nút nhấn:

Trong AViewControll.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Bây giờ, VC B có giao diện từ Bnib, nhưng khi nhấn nút, tôi muốn chuyển sang 'chế độ chỉnh sửa' có giao diện người dùng riêng từ một ngòi khác, nhưng tôi không muốn có VC mới cho chế độ chỉnh sửa, Tôi muốn ngòi mới được liên kết với B VC hiện tại của tôi.

Vì vậy, trong BViewControll.m (trong phương thức nhấn nút)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Sau đó, trên một nút khác nhấn (để thoát chế độ chỉnh sửa):

[editView removeFromSuperview];

và tôi trở lại Bnib ban đầu của tôi.

Điều này hoạt động tốt, nhưng lưu ý EditMode.nib của tôi chỉ có 1 obj cấp cao nhất trong đó, một obj UIView. Không quan trọng việc Chủ sở hữu tệp trong ngòi này được đặt là BViewControll hay NSObject mặc định, NHƯNG đảm bảo rằng Cửa hàng Xem trong Chủ sở hữu tệp KHÔNG được đặt thành bất cứ điều gì. Nếu đúng như vậy, thì tôi gặp sự cố exc_bad_access và xcode tiến hành tải 6677 khung ngăn xếp hiển thị phương thức UIView bên trong được gọi liên tục ... vì vậy trông giống như một vòng lặp vô hạn. (Tuy nhiên, View Outlet IS được đặt trong Bnib ban đầu của tôi)

Hi vọng điêu nay co ich.


Đọc giữa các dòng, điều này cũng giúp tôi ra.
petert

Theo thông tin tại liên kết, bạn không nên thao tác điều khiển xem theo cách này.
T. Markle

6

Tôi đã tạo một danh mục mà tôi thích:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Sau đó, gọi như thế này:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Sử dụng tên ngòi nếu ngòi của bạn được đặt tên khác với tên của lớp bạn.

Để ghi đè nó trong các lớp con của bạn cho hành vi bổ sung, nó có thể trông như thế này:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}

5

Đối với người dùng Swift với tùy chọn có thể thiết kế:

  1. Tạo một UIViewlớp con tùy chỉnh và một tệp xib , mà chúng ta sẽ đặt tên theo tên lớp của chính chúng ta: trong trường hợp của chúng ta là MemeView. Bên trong lớp Meme View hãy nhớ định nghĩa nó là có thể thiết kế được với @IBDesignablethuộc tính trước khi khai báo lớp
  2. Tháng chín để đặt Chủ sở hữu tệp trong xib với UIViewlớp con tùy chỉnh của chúng tôi trong bảng điều khiển Indetity Inspector

    nhập mô tả hình ảnh ở đây

  3. Trong tệp xib bây giờ chúng ta có thể xây dựng giao diện của mình, tạo các ràng buộc, tạo các cửa hàng, hành động, v.v. nhập mô tả hình ảnh ở đây

  4. Chúng ta cần triển khai một vài phương thức cho lớp tùy chỉnh của mình để mở xib khi được khởi tạo

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. Trong lớp tùy chỉnh của chúng tôi, chúng tôi cũng có thể định nghĩa một số thuộc tính có thể kiểm tra để có toàn quyền kiểm soát chúng từ trình tạo giao diện

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

Một vài ví dụ:
nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Nếu chúng ta cần thêm nhiều thông tin hơn, khung nhìn được hiển thị bên trong bảng phân cảnh hoặc xib khác, để thực hiện điều đó, chúng ta sẽ thực hiện prepareForInterfaceBuilder()phương thức này trong khi mở tệp trong trình tạo giao diện. Nếu bạn đã làm mọi thứ tôi đã viết nhưng không có gì hiệu quả, thì đó là cách để gỡ lỗi một khung nhìn sigle bằng cách thêm các điểm dừng trong quá trình thực hiện. nhập mô tả hình ảnh ở đây
Đây là hệ thống phân cấp lượt xem. Xem hiearchy

Hy vọng điều này sẽ giúp một mẫu đầy đủ có thể được tải xuống ở đây


Có thể xem toàn bộ bài viết từ blog cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah của tôi nhưng tôi đã đăng phần có liên quan.
Andrea

let nib = loadNib(nibName)đây là một cá thể XibbedView, nếu bạn gọi `` `addSubview (nib)` `thì bạn có thêm một cá thể XibbedView vào một cá thể XibbedView khác không?
William Hu

Tôi đồng ý về điểm này. Khi bạn cố gắng xem nó từ "Phân cấp chế độ xem gỡ lỗi", có vẻ như XibbedView chứa XibbedView. Bạn có thể thấy nó.
William Hu

@WilliamHu nếu bạn tải ví dụ cung cấp, bạn có thể thấy MemeView.xib chỉ là một phiên bản UIView với một loạt các bản xem xét, người giữ tệp là MemeView là một lớp con của XibbedView. Do đó, ví dụ duy nhất của XibbedView chỉ là MemeView tải tệp xib trong đó chế độ xem chính chỉ là chế độ xem
Andrea

Ồ được rồi. Tôi sẽ kiểm tra nó. Cảm ơn bạn!
William Hu

4

Tôi thấy bài đăng trên blog này của Aaron Hillegass (tác giả, người hướng dẫn, ninja ninja) rất ngộ. Ngay cả khi bạn không áp dụng cách tiếp cận đã sửa đổi của mình để tải các tệp NIB thông qua trình khởi tạo được chỉ định, ít nhất bạn cũng có thể hiểu rõ hơn về quy trình đang diễn ra. Gần đây tôi đã sử dụng phương pháp này để thành công lớn!


Liên kết đã chết. Tôi tin rằng bây giờ nó được đặt tại bignerdranch.com/blog/ Khăn
joelliusp

3

Câu trả lời trước không tính đến sự thay đổi cấu trúc NIB (XIB) xảy ra giữa 2.0 và 2.1 của SDK iPhone. Nội dung người dùng bây giờ bắt đầu ở chỉ số 0 thay vì 1.

Bạn có thể sử dụng macro 2.1 hợp lệ cho tất cả các phiên bản 2.1 trở lên (đó là hai dấu gạch dưới trước IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

Chúng tôi sử dụng một kỹ thuật tương tự như thế này cho hầu hết các ứng dụng của chúng tôi.

Barney


2
Barney: "Câu trả lời trước" là vô nghĩa đối với Stack Overflow vì câu trả lời thay đổi vị trí dựa trên biểu quyết. Tốt hơn nên tham khảo "câu trả lời của Fred" hoặc "câu trả lời của Barney" hoặc "câu trả lời của olie."
Olie

3

Tôi có lý do để làm điều tương tự (lập trình tải chế độ xem từ tệp XIB), nhưng tôi cần phải làm điều này hoàn toàn từ ngữ cảnh của một lớp con của một lớp con của một UIView(tức là không liên quan đến trình điều khiển chế độ xem theo bất kỳ cách nào). Để làm điều này tôi đã tạo phương thức tiện ích này:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Sau đó, tôi gọi nó từ initWithFramephương thức lớp con của tôi như vậy:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Đăng cho lợi ích chung; Nếu bất cứ ai nhìn thấy bất kỳ vấn đề mà không làm theo cách này, xin vui lòng cho tôi biết.


Tôi đã triển khai mã của bạn, nhưng nó tiếp tục vượt quá phạm vi trên init. Tôi đã thực hiện phương thức tĩnh của bạn trong một lớp Tiện ích. Tôi đã tạo các tệp h / m có tên MyView (được phân lớp từ UIView) và một xib có tên giống nhau. Trong IB tôi đặt chủ sở hữu tệp xib VÀ chế độ xem thành "MyView." Trong trình gỡ lỗi, nó nằm ngoài phạm vi. Tôi có thiếu điều gì trong IB không?
TigerCoding

Xin hãy xem câu hỏi SO mới của tôi ở đây: stackoverflow.com/questions/9224857/NH
TigerCoding

self = [Tiện ích initWithNibName: @ "XIB1" withSelf: self]; Bạn đang vượt qua chính mình khi nó chưa được thiết lập. Không phải mã đó giống như thế này: self = [Tiện ích initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt

@Jeef: mẫu mã của tôi ở đây có thể không hoạt động với ARC - Tôi chưa kiểm tra nó với bật ARC.
MusiGenesis

3

Đây là một cách để làm điều đó trong Swift (hiện đang viết Swift 2.0 trong XCode 7 beta 5).

Từ UIViewlớp con của bạn mà bạn đặt là "Lớp tùy chỉnh" trong Trình tạo giao diện, hãy tạo một phương thức như thế này (lớp con của tôi được gọi là RecordFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Sau đó, bạn có thể gọi nó như thế này:

let recordingFooterView = RecordingFooterView.loadFromNib()


2

Không có câu trả lời nào giải thích cách tạo XIB độc lập là gốc của câu hỏi này. Không cóXcode 4 tùy chọn để "Tạo tệp XIB mới".

Để làm điều này

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Điều này có vẻ đơn giản nhưng nó có thể giúp bạn tiết kiệm vài phút chọc ngoáy vì từ "XIB" không xuất hiện ở bất cứ đâu.


1

@AVeryDev

6) Để đính kèm chế độ xem được tải vào chế độ xem của trình điều khiển chế độ xem của bạn:

[self.view addSubview:myViewFromNib];

Có lẽ, cần phải loại bỏ nó khỏi chế độ xem để tránh rò rỉ bộ nhớ.

Để làm rõ: bộ điều khiển khung nhìn có một số IBOutlets, một số trong số đó được kết nối với các mục trong tệp nib gốc (như bình thường) và một số được kết nối với các mục trong ngòi được tải. Cả hai ngòi đều có cùng một lớp chủ sở hữu. Chế độ xem được tải chồng lên bản gốc.

Gợi ý: đặt độ mờ của chế độ xem chính trong ngòi được tải thành 0, sau đó nó sẽ không làm mờ các mục khỏi ngòi gốc.


điều tốt để đề phòng, nhưng việc thêm chế độ xem dưới dạng một khung nhìn phụ sẽ tự động xóa nó khỏi cha mẹ trước của nó để nó không bị rò rỉ.
averydev

1

Sau khi dành nhiều giờ, tôi giả mạo giải pháp sau đây. Thực hiện theo các bước sau để tạo tùy chỉnhUIView .

1) Tạo lớp myCustomView được kế thừa từ UIView .
nhập mô tả hình ảnh ở đây

2) Tạo .xib với tên myCustomView .
nhập mô tả hình ảnh ở đây

3) Thay đổi Lớp UIView trong tệp .xib của bạn, gán Lớp myCustomView ở đó.
nhập mô tả hình ảnh ở đây

4) Tạo IBOutlets
nhập mô tả hình ảnh ở đây

5) Tải .xib trong myCustomView * customView . Sử dụng mã mẫu sau.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Lưu ý: Đối với những người vẫn gặp phải vấn đề có thể nhận xét, tôi sẽ cung cấp cho họ liên kết của dự án mẫu với myCustomView


1

Phiên bản ngắn nhất:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];

0

Tôi có một quy ước đặt tên xibs với các khung nhìn trong đó giống như khung nhìn. Tương tự như người ta sẽ làm cho một bộ điều khiển xem. Sau đó, tôi không phải viết tên lớp trong mã. Tôi tải một UIView từ một tệp nib có cùng tên.

Ví dụ cho một lớp được gọi là MyView.

  • Tạo một tệp nib có tên MyView.xib trong Interface Builder
  • Trong Trình tạo giao diện, thêm một UIView. Đặt lớp của nó thành MyView. Tùy chỉnh nội dung trái tim của bạn, kết nối các biến thể hiện của MyView với các lần xem trước mà bạn có thể muốn truy cập sau này.
  • Trong mã của bạn, hãy tạo một MyView mới như thế này:

    MyView * myView = [MyView nib_viewFromNibWithOwner: chủ sở hữu];

Đây là danh mục cho việc này:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Sau đó, tôi nối các nút với các hành động từ bộ điều khiển tôi đang sử dụng và đặt mọi thứ trên nhãn bằng các ổ cắm trong lớp con chế độ xem tùy chỉnh của tôi.


0

Để lập trình tải chế độ xem từ nib / xib trong Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}

-1

Cuối cùng tôi đã thêm một danh mục vào UIView cho việc này:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

giải thích ở đây: viewcontroll ít xem tải trong ios & mac


Hrm. Có gì với thẻ?
n13

để biết đối tượng cấp cao nhất sẽ giữ lại. tại thời điểm đó là cần thiết, nhưng có lẽ bạn có thể lấy ra các phần giữ lại để tuân thủ ARC ngay bây giờ.
gngrwzrd
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.