Cách đúng để tải Nib cho lớp con UIView


98

Tôi biết câu hỏi này đã được hỏi trước đây nhưng các câu trả lời mâu thuẫn và tôi bối rối, vì vậy xin vui lòng không châm lửa cho tôi.

Tôi muốn có một UIViewlớp con có thể sử dụng lại trong ứng dụng của mình. Tôi muốn mô tả giao diện bằng tệp nib.

Bây giờ giả sử đó là chế độ xem chỉ báo tải với chỉ báo hoạt động trong đó. Tôi muốn trong một số sự kiện để khởi tạo chế độ xem này và tạo hoạt ảnh trong chế độ xem của bộ điều khiển chế độ xem. Tôi có thể mô tả giao diện của chế độ xem không có vấn đề gì theo lập trình, tạo các phần tử theo lập trình và đặt khung của chúng bên trong một phương thức init, v.v.

Làm thế nào tôi có thể làm điều này bằng cách sử dụng một ngòi? Duy trì kích thước đã cho trong trình tạo giao diện mà không cần phải thiết lập khung.

Tôi đã quản lý để làm điều đó như thế này, nhưng tôi chắc chắn là sai (đó chỉ là một chế độ xem với một bộ chọn trong đó):

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

Nhưng tôi đang ghi đè bản thân và khi tôi khởi tạo nó:

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

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

Tôi phải tự thiết lập khung hình chứ không phải chọn nó từ IB.

CHỈNH SỬA: Tôi muốn tạo chế độ xem tùy chỉnh này trong bộ điều khiển chế độ xem và có quyền truy cập để kiểm soát các phần tử của chế độ xem. Tôi không muốn một bộ điều khiển chế độ xem mới.

Cảm ơn

CHỈNH SỬA: Tôi không biết đây có phải là phương pháp hay nhất hay không, tôi chắc chắn là không, nhưng đây là cách tôi đã làm:

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) {
                        }];

Thực hành đúng vẫn muốn

Điều này có nên được thực hiện bằng cách sử dụng bộ điều khiển chế độ xem và init với tên nib không? Tôi có nên đặt nib trong một số phương thức khởi tạo UIView trong mã không? Hay những gì tôi đã làm có ổn không?


Nhưng không phải dòng mã đầu tiên gần giống với dòng mã bạn đã sử dụng trong câu hỏi ban đầu initWithDataSourcesao? Dù sao điều này sẽ hoạt động ngay cả khi bạn đặt chủ sở hữu là 'Nil'.
Rakesh

Điều đáng chú ý là ngày nay trong 99,99999% trường hợp, một người sẽ chỉ sử dụng chế độ xem vùng chứa , hiện được sử dụng ở mọi nơi và luôn trong iOS. làm thế nào để bài viết
Fattie

lưu ý rằng bạn có thể thay thế [NSString stringWithFormat: @ "% @", [FSASelectView class]] bằng NSStringFromClass ([FSASelectView class])
naomimichiko

Câu trả lời:


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

Tôi đang sử dụng cái này để khởi tạo các chế độ xem tùy chỉnh có thể sử dụng lại mà tôi có.


Lưu ý rằng bạn có thể sử dụng "firstObject" ở cuối ở đó, nó gọn gàng hơn một chút. "firstObject" là một phương thức tiện dụng cho NSArray và NSMutableArray.

Đây là một ví dụ điển hình về việc tải xib để sử dụng làm tiêu đề bảng. Trong tệp YourClass.m của bạn

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

Thông thường, trong TopArea.xib, bạn sẽ nhấp vào Chủ sở hữu tệp và đặt chủ sở hữu tệp thành YourClass . Sau đó, thực sự trong YourClass.h bạn sẽ có các thuộc tính IBOutlet. Trong TopArea.xib, bạn có thể kéo các điều khiển đến các cửa hàng đó.

Đừng quên rằng trong đó TopArea.xib, bạn có thể phải nhấp vào chính Chế độ xem và kéo nó đến một cửa hàng nào đó, vì vậy bạn có quyền kiểm soát nó, nếu cần. (Một mẹo rất đáng giá là khi bạn đang làm điều này cho các hàng ô của bảng, bạn hoàn toàn phải làm điều đó - bạn phải kết nối chính chế độ xem với thuộc tính có liên quan trong mã của bạn.)


không có initWithNibName: Phương pháp cho một UIView, nó chỉ cho UIViewController
meronix

Đó là đối với bộ điều khiển chế độ xem, tôi muốn sử dụng UIView và có bộ điều khiển tạo ra nó để sở hữu nó.
Adam Waite

Tôi xin lỗi, tôi đã sao chép sai mã trong lúc vội vàng, tôi đã cập nhật câu trả lời, vui lòng xem.
Premsuraj

Tôi sẽ đánh dấu điều này chính xác. Tôi không biết đó có phải là phương pháp hay nhất không nhưng nó có hiệu quả.
Adam Waite

@Joe Blow Xin lỗi vì sự cố, nhưng tại sao người ta phải làm điều này: "Đừng quên rằng trong TopArea.xib, bạn có thể phải nhấp vào chính Chế độ xem và kéo nó đến một cửa hàng nào đó, vì vậy bạn có quyền kiểm soát nó , Nếu cần." Thay vào đó, bạn có thể chỉ sử dụng myViewObject để truy cập chế độ xem bên dưới vì bản thân nó là một UIView? Tại sao cần có tài sản?
user1686342

39

Nếu bạn muốn giữ độc lập CustomViewcủa mình và của nó , hãy làm theo các bước sauxibFile's Owner

  • Để File's Ownertrống trường.
  • Nhấp vào chế độ xem thực tế trong xibtệp của bạn CustomViewvà đặt nó Custom ClassCustomView(tên của lớp chế độ xem tùy chỉnh của bạn)
  • Thêm IBOutletvào .htệp của chế độ xem tùy chỉnh của bạn.
  • Trong .xibtệp chế độ xem tùy chỉnh của bạn, hãy nhấp vào chế độ xem và đi vào Connection Inspector. Tại đây, bạn sẽ tìm thấy tất cả IBOutlet mà bạn xác định trong .htệp
  • Kết nối chúng với chế độ xem tương ứng.

trong .mtệp CustomViewlớp của bạn , ghi đè initphương thức như sau

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

Bây giờ khi bạn muốn tải CustomView, hãy sử dụng dòng mã sau [[CustomView alloc] init];


1
Đối với iOS 9, đây là giải pháp duy nhất được liệt kê trên trang này thực sự hiệu quả với tôi (các giải pháp khác gây ra sự cố).
lifejoy

Chỉ cần lưu ý rằng cách tiếp cận này không tương thích với việc tải chế độ xem tùy chỉnh của bạn từ XIB hiện có vì nó không gọi -init. Thay vào đó nó sẽ gọi initWithFrame:-awakeFromNib. Nếu bạn viết cùng một mã để tải chế độ xem của mình như trong -initphương thức, bạn sẽ nhập một vòng lặp vô hạn.
Dustt

25

Làm theo các bước sau

  1. Tạo một lớp có tên MyView .h / .m kiểu UIView.
  2. Tạo một xib cùng tên MyView.xib.
  3. Bây giờ thay đổi lớp Chủ sở hữu tệp thành UIViewControllertừ NSObjecttrong xib. Xem hình ảnh bên dưới nhập mô tả hình ảnh ở đây
  4. Kết nối Chế độ xem chủ sở hữu tệp với Chế độ xem của bạn. Xem hình ảnh bên dưới nhập mô tả hình ảnh ở đây

  5. Thay đổi loại Chế độ xem của bạn thành MyView. Tương tự như 3.

  6. Đặt các điều khiển tạo IBOutlet.

Đây là mã để tải Chế độ xem:

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

Hy vọng nó giúp.

Làm rõ :

UIViewControllerđược sử dụng để tải xib của bạn và Chế độ xem UIViewControllerthực sự MyViewcó mà bạn đã chỉ định trong xib của MyView ..

Demo Tôi đã tạo một bản demo lấy ở đây


Tại sao mọi người lại bỏ phiếu điều này, sai? Tôi vẫn chưa thử, nhưng điều này có vẻ như nó sẽ không làm được những gì tôi muốn.
Adam Waite

Tôi đã bỏ phiếu thấp, nhưng điều đó khiến tôi đọc sai câu trả lời của bạn (nghĩ rằng bạn đang chỉ định một lớp con UIView làm chủ sở hữu của tệp). Xin lỗi vì điều đó.
Rakesh

2
Trong tệp xib của bạn, hãy bỏ qua chủ sở hữu tệp. Sau đó, bạn có thể tránh phải tạo một UIViewController bằng cách chỉ thực hiện MyView * view = [[UINib InstantiateWithOwner: nil options: nil] lastObject];
LightningStryk

1
@LightningStryk Tất nhiên là có, nhưng nó chỉ là một cách khác để làm điều đó.
iphonic

1
Đó là tạo một lớp con UIView có thể sử dụng lại, không sử dụng UIViewController cho mục đích đó.
arielyz

11

Trả lời câu hỏi của riêng tôi khoảng 2 hoặc năm sau ở đây nhưng ...

Nó sử dụng một phần mở rộng giao thức để bạn có thể thực hiện mà không cần thêm bất kỳ mã nào cho tất cả các lớp.

/*

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

Trong Swift:

Ví dụ: tên của lớp tùy chỉnh của bạn là InfoView

Lúc đầu, bạn tạo tệp InfoView.xibInfoView.swiftnhư thế này:

import Foundation
import UIKit

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

Sau đó đặt File's Ownerthành UIViewControllernhư thế này:

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

Đổi tên của bạn Viewthành InfoView:

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

Nhấp chuột phải vào File's Ownervà kết nối viewtrường của bạn với InfoView:

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

Đảm bảo rằng tên lớp là InfoView:

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

Và sau đó, bạn có thể thêm nút action vào trong lớp tùy chỉnh của mình mà không gặp vấn đề gì:

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

Và việc sử dụng lớp tùy chỉnh này trong MainViewController:

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

2

Bạn cũng có thể khởi tạo xib bằng cách sử dụng bộ điều khiển chế độ xem và sử dụng viewController.view. hoặc làm theo cách bạn đã làm. Chỉ tạo một UIViewlớp con làm bộ điều khiển UIViewlà một ý tưởng tồi.

Nếu bạn không có bất kỳ cửa hàng nào từ dạng xem tùy chỉnh của mình thì bạn có thể trực tiếp sử dụng một UIViewControllerlớp để khởi tạo nó.

Cập nhật: Trong trường hợp của bạn:

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

Nếu không, bạn phải thực hiện một tùy chỉnh UIViewController(đặt nó với tư cách là chủ sở hữu của tệp để các cửa hàng được kết nối đúng cách).

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

Bất cứ nơi nào bạn muốn thêm chế độ xem, bạn có thể sử dụng.

[parentView addSubview:yCustCon.view];

Tuy nhiên, việc chuyển bộ điều khiển chế độ xem khác (đã được sử dụng cho chế độ xem khác) làm chủ sở hữu trong khi tải xib không phải là ý kiến ​​hay vì thuộc tính chế độ xem của bộ điều khiển sẽ bị thay đổi và khi bạn muốn truy cập chế độ xem ban đầu, bạn sẽ không có một tham chiếu đến nó.

CHỈNH SỬA: Bạn sẽ phải đối mặt với vấn đề này nếu bạn đã thiết lập xib mới của mình với chủ sở hữu tệp là cùng một UIViewControllerlớp chính và gắn thuộc tính chế độ xem với chế độ xem xib mới.

I E;

  • YourMainViewController - quản lý - mainView
  • CustomView - cần tải từ xib khi được yêu cầu.

Đoạn mã dưới đây sẽ gây nhầm lẫn sau này, nếu bạn viết nó vào bên trong chế độ xem YourMainViewController. Đó là vì self.viewtừ thời điểm này trở đi sẽ tham chiếu đến chế độ xem tùy chỉnh của bạn

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

Ok, về cơ bản, phương pháp hay nhất nói rằng mọi UIView nên có một bộ điều khiển liên kết?
Adam Waite

Không cần thiết. Nhưng khi tải một chế độ xem từ một xib riêng biệt, đó sẽ là cách không có vấn đề. Tài liệu apple trước IOS5 cho biết không nên sử dụng bộ điều khiển một chế độ xem để điều khiển nhiều hơn một cảnh (xib) và ngược lại.
Rakesh

Các xib thực sự chỉ là kích thước của một cái nhìn tỉnh táo
Adam Waite

Cá nhân tôi cảm thấy, nó phụ thuộc vào độ phức tạp của mã / xib cho dù bạn có phải tạo bộ điều khiển mới hay không. Nếu bạn có thể xử lý các sự cố gây ra thành công và nếu bạn không xem xét các sửa đổi trong tương lai thì đó không phải là vấn đề. Nhưng việc tạo một bộ điều khiển chế độ xem riêng biệt sẽ giải quyết rất nhiều vấn đề cho bạn. Và vì trong trường hợp của bạn, nó chỉ là một chỉ báo hoạt động nên bạn có thể sử dụng một đối tượng UIViewController (như đã đề cập trong câu trả lời) một cách dễ dàng. Tôi sẽ cập nhật câu trả lời của mình để làm điều này cho phù hợp.
Rakesh

1
Vì vậy, tôi có thể vẽ giao diện trong trình tạo giao diện hơn là thực hiện nó theo chương trình. Không thành vấn đề khi thực hiện nó theo chương trình, tôi chỉ muốn biết cách phân lớp một UIView và cung cấp cho nó một nib! Điều này không khó.
Adam Waite
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.