Tạo UIView có thể tái sử dụng với xib (và tải từ bảng phân cảnh)


81

OK, có hàng tá bài đăng trên StackOverflow về vấn đề này, nhưng không có bài nào đặc biệt rõ ràng về giải pháp. Tôi muốn tạo một tùy chỉnh UIViewvới tệp xib đi kèm. Các yêu cầu là:

  • Không riêng biệt UIViewController- một lớp học hoàn toàn khép kín
  • Các cửa hàng trong lớp để cho phép tôi thiết lập / lấy các thuộc tính của khung nhìn

Cách tiếp cận hiện tại của tôi để làm điều này là:

  1. Ghi đè -(id)initWithFrame:

    -(id)initWithFrame:(CGRect)frame {
        self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:self
                                            options:nil] objectAtIndex:0];
        self.frame = frame;
        return self;
    }
    
  2. Khởi tạo theo chương trình bằng cách sử dụng -(id)initWithFrame:trong bộ điều khiển chế độ xem của tôi

    MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    [self.view insertSubview:myCustomView atIndex:0];
    

Điều này hoạt động tốt (mặc dù không bao giờ gọi [super init]và chỉ đơn giản thiết lập đối tượng bằng cách sử dụng nội dung của ngòi đã tải có vẻ hơi đáng ngờ - có lời khuyên ở đây là thêm một chế độ xem phụ trong trường hợp này cũng hoạt động tốt). Tuy nhiên, tôi cũng muốn có thể khởi tạo chế độ xem từ bảng phân cảnh. Do đó, tôi có thể:

  1. Đặt UIViewở chế độ xem chính trong bảng phân cảnh
  2. Đặt lớp tùy chỉnh của nó thành MyCustomView
  3. Ghi đè -(id)initWithCoder:- mã mà tôi thường thấy nhất phù hợp với một mẫu như sau:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(void)initializeSubviews {
        typeof(view) view = [[[NSBundle mainBundle]
                             loadNibNamed:NSStringFromClass([self class])
                                    owner:self
                                  options:nil] objectAtIndex:0];
        [self addSubview:view];
    }
    

Tất nhiên, điều này không hoạt động, như cho dù tôi sử dụng cách tiếp cận ở trên, hay liệu tôi khởi tạo theo chương trình, cả hai đều kết thúc gọi đệ quy -(id)initWithCoder:khi nhập -(void)initializeSubviewsvà tải nib từ tệp.

Một số câu hỏi SO khác giải quyết vấn đề này như đây , đây , đâyđây . Tuy nhiên, không có câu trả lời nào được đưa ra khắc phục được vấn đề một cách thỏa đáng:

  • Một gợi ý phổ biến dường như là nhúng toàn bộ lớp vào một UIViewController và thực hiện tải nib ở đó, nhưng điều này có vẻ không tối ưu với tôi vì nó yêu cầu thêm một tệp khác chỉ như một trình bao bọc

Bất cứ ai có thể cho lời khuyên về cách giải quyết vấn đề này và có được các cửa hàng hoạt động theo một tùy chỉnh UIViewvới mức tối thiểu phiền phức / không có trình bao bọc bộ điều khiển mỏng? Hoặc có một cách thay thế, gọn gàng hơn để thực hiện mọi việc với mã bảng soạn sẵn tối thiểu không?


1
Bạn đã bao giờ nhận được câu trả lời thỏa đáng cho điều này? Tôi đang đấu tranh cho điều này vào lúc này. Tất cả các câu trả lời khác dường như không đủ tốt, như bạn đề cập. Bạn luôn có thể tự trả lời câu hỏi nếu bạn phát hiện ra bất cứ điều gì trong vài tháng qua.
Mike Meyers

13
Tại sao rất khó để tạo các chế độ xem có thể sử dụng lại trong iOS?
thợ đồng hồ


1
Trong thực tế, câu trả lời bạn liên kết với công dụng chính xác phương pháp tương tự (mặc dù câu trả lời của bạn không bao gồm init từ chức năng rect, có nghĩa là nó chỉ có thể được khởi tạo từ các kịch bản và không programatically)
Ken Chatfield

1
liên quan đến QA rất cũ này, Apple cuối cùng đã giới thiệu TÀI LIỆU THAM KHẢO VỀ BẢNG CÂU CHUYỆN ... developer.apple.com/library/ios/recipes/… ... vậy là xong, phew!
Fattie

Câu trả lời:


13

Sự cố của bạn đang gọi loadNibNamed:từ (hậu duệ của) initWithCoder:. loadNibNamed:cuộc gọi nội bộ initWithCoder:. Nếu bạn muốn ghi đè người viết mã bảng phân cảnh và luôn tải triển khai xib của mình, tôi đề xuất kỹ thuật sau. Thêm một thuộc tính vào lớp dạng xem của bạn và trong tệp xib, hãy đặt nó thành một giá trị được xác định trước (trong Thuộc tính thời gian chạy do người dùng xác định). Bây giờ, sau khi gọi, [super initWithCoder:aDecoder];hãy kiểm tra giá trị của thuộc tính. Nếu đó là giá trị được xác định trước, không gọi [self initializeSubviews];.

Vì vậy, một cái gì đó như thế này:

-(instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];

    if (self && self._xibProperty != 666)
    {
        //We are in the storyboard code path. Initialize from the xib.
        self = [self initializeSubviews];

        //Here, you can load properties that you wish to expose to the user to set in a storyboard; e.g.:
        //self.backgroundColor = [aDecoder decodeObjectOfClass:[UIColor class] forKey:@"backgroundColor"];
    }

    return self;
}

-(instancetype)initializeSubviews {
    id view =   [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];

    return view;
}

Cảm ơn @LeoNatan! Tôi chấp nhận câu trả lời này vì nó là giải pháp tốt nhất cho vấn đề như đã nêu ban đầu. Tuy nhiên, lưu ý rằng nó không còn khả thi trong Swift nữa - Tôi đã thêm một số ghi chú riêng biệt về một công việc có thể xảy ra trong trường hợp đó.
Ken Chatfield

@KenChatfield Tôi nhận thấy điều đó trong dự án phụ Swift của mình và cảm thấy khó chịu vì nó. Tôi không chắc họ đang nghĩ gì, vì nếu không có điều này, rất nhiều việc triển khai Cocoa / Cocoa Touch bên trong là không thể trong Swift. Tôi cá là sẽ có một số tính năng động khi chúng thực sự có thời gian tập trung vào các tính năng hơn là lỗi. Swift vẫn chưa sẵn sàng, và những kẻ vi phạm tồi tệ nhất chính là công cụ phát triển.
Leo Natan

Vâng, bạn nói đúng, Swift hiện có rất nhiều góc cạnh thô ráp. Phải nói rằng, có vẻ như việc gán trực tiếp cho bản thân là một điều hơi khó hiểu, vì vậy tôi rất vui theo cách mà trình biên dịch hiện cảnh báo chống lại những điều như vậy (kiểm tra kiểu chặt chẽ hơn này có vẻ là một trong những điều tốt đẹp về ngôn ngữ). Tuy nhiên, bạn nói đúng rằng nó làm cho việc liên kết các chế độ xem tùy chỉnh với xibs rất lộn xộn và hơi không thỏa mãn. Hãy hy vọng như bạn nói rằng khi họ đã phân loại xong các lỗi, chúng ta sẽ thấy một số tính năng năng động hơn để giúp chúng ta hiểu thêm một chút về những thứ như thế này!
Ken Chatfield

Đó không phải là một cuộc tấn công, đó là cách các nhóm lớp hoạt động. Và thực sự, không có vấn đề kỹ thuật nào cho phép trả về một đối tượng là lớp con của lớp trả về để thay thế. Vì nó là, một trong những nền tảng của Cocoa và Cocoa Touch - cụm lớp - là không thể thực hiện. Khủng khiếp. Một số framework, như Core Data, không thể triển khai được trong Swift, điều này khiến nó trở thành một ngôn ngữ vô dụng đối với hầu hết các mục đích sử dụng của tôi.
Leo Natan

1
Bằng cách nào đó, nó không hoạt động với tôi (iOS8.1 SDK). Tôi đặt một định danh phục hồi trong XIB thay vì một thuộc tính thời gian chạy, so với nó hoạt động. Ví dụ như tôi đặt "MyViewRestorationID" trong xib, so với initWithCoder: Tôi đã kiểm tra rằng [[tự restorationIdentifier] isEqualToString: @ "MyViewRestorationID"]!
ingaham

26

Lưu ý rằng QA này (giống như nhiều) thực sự chỉ là mối quan tâm lịch sử.

Ngày nay Trong nhiều năm, bây giờ trong iOS mọi thứ chỉ là một chế độ xem vùng chứa. Hướng dẫn đầy đủ tại đây

(Thật vậy, Apple cuối cùng đã thêm Tài liệu tham khảo bảng phân cảnh , một thời gian trước đây, làm cho nó dễ dàng hơn nhiều.)

Đây là một bảng phân cảnh điển hình với chế độ xem vùng chứa ở khắp mọi nơi. Mọi thứ đều là dạng xem vùng chứa. Đó chỉ là cách bạn tạo ứng dụng.

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

(Như một sự tò mò, câu trả lời của KenC cho thấy chính xác cách thức, nó từng được thực hiện để tải một xib vào một loại chế độ xem trình bao bọc, vì bạn không thể thực sự "gán cho chính mình".)


Vấn đề với điều này là bạn sẽ kết thúc với rất nhiều ViewControllers cho tất cả các chế độ xem nội dung bạn đã xếp chồng.
Bogdan Onu

Chào bạn @BogdanOnu! Bạn nên có nhiều, nhiều, nhiều bộ điều khiển chế độ xem. Đối với điều "nhỏ nhất" - bạn nên có một bộ điều khiển chế độ xem.
Fattie

2
Điều này rất hữu ích - cảm ơn @JoeBlow. Sử dụng khung nhìn vùng chứa chắc chắn có vẻ là một cách tiếp cận thay thế và là một cách đơn giản để tránh tất cả các phức tạp khi xử lý trực tiếp các xibs. Tuy nhiên, có vẻ như đây không phải là giải pháp thay thế thỏa đáng 100% cho việc tạo các thành phần có thể tái sử dụng để phân phối / sử dụng trong các dự án, vì nó yêu cầu tất cả thiết kế giao diện người dùng phải được nhúng trực tiếp vào bảng phân cảnh.
Ken Chatfield

3
Tôi gặp ít vấn đề hơn với việc sử dụng thêm một ViewController trong trường hợp này vì nó sẽ chỉ chứa logic chương trình mà nếu không sẽ thuộc về lớp View tùy chỉnh trong trường hợp xib, nhưng việc kết hợp chặt chẽ với bảng phân cảnh có nghĩa là tôi không chắc chắn các chế độ xem vùng chứa có thể giải quyết hoàn toàn vấn đề này. Có lẽ trong hầu hết các tình huống thực tế, các chế độ xem đều dành riêng cho dự án và vì vậy đây là giải pháp tốt nhất và 'tiêu chuẩn' nhất, nhưng thật ngạc nhiên là vẫn không có cách dễ dàng để đóng gói các chế độ xem tùy chỉnh để sử dụng bảng phân cảnh. Xung lập trình của tôi để phân chia và chinh phục các thành phần bị cô lập là ngứa;)
Ken Chatfield

1
Ngoài ra, với sự ra đời của render sống của lớp con tùy chỉnh UIView trong Xcode 6, Tôi không chắc liệu tôi mua tiền đề rằng việc tạo quan điểm sử dụng xibs theo cách này hiện đang bị phản đối
Ken Chatfield

24

Tôi thêm bài đăng này như một bài đăng riêng biệt để cập nhật tình hình với việc phát hành Swift. Cách tiếp cận được LeoNatan mô tả hoạt động hoàn hảo trong Objective-C. Tuy nhiên, việc kiểm tra thời gian biên dịch chặt chẽ hơn ngăn cản selfviệc được gán khi tải từ tệp xib trong Swift.

Do đó, không có tùy chọn nào khác ngoài việc thêm chế độ xem được tải từ tệp xib làm chế độ xem phụ của lớp con UIView tùy chỉnh, thay vì thay thế hoàn toàn bản thân. Điều này tương tự với cách tiếp cận thứ hai được nêu trong câu hỏi ban đầu. Sơ lược về một lớp trong Swift sử dụng phương pháp này như sau:

@IBDesignable // <- to optionally enable live rendering in IB
class ExampleView: UIView {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initializeSubviews()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        initializeSubviews()
    }

    func initializeSubviews() {
        // below doesn't work as returned class name is normally in project module scope
        /*let viewName = NSStringFromClass(self.classForCoder)*/
        let viewName = "ExampleView"
        let view: UIView = NSBundle.mainBundle().loadNibNamed(viewName,
                               owner: self, options: nil)[0] as! UIView
        self.addSubview(view)
        view.frame = self.bounds
    }

}

Nhược điểm của cách tiếp cận này là việc giới thiệu thêm một lớp dư thừa trong hệ thống phân cấp chế độ xem mà không tồn tại khi sử dụng cách tiếp cận do LeoNatan nêu trong Objective-C. Tuy nhiên, điều này có thể được coi là một điều xấu cần thiết và là sản phẩm của cách cơ bản mà mọi thứ được thiết kế trong Xcode (tôi vẫn thấy điên rồ khi liên kết lớp UIView tùy chỉnh với bố cục giao diện người dùng theo cách hoạt động nhất quán trên cả bảng phân cảnh và từ mã) - thay thế selfbán buôn trong trình khởi tạo trước đây dường như chưa bao giờ là một cách hoạt động đặc biệt dễ hiểu, mặc dù về cơ bản có hai lớp chế độ xem cho mỗi chế độ xem dường như cũng không tuyệt vời như vậy.

Tuy nhiên, một kết quả đáng mừng của cách tiếp cận này là chúng ta không còn cần đặt lớp tùy chỉnh của chế độ xem thành tệp lớp của chúng ta trong trình tạo giao diện để đảm bảo hành vi chính xác khi gán cho selfvà do đó, lệnh gọi đệ quy tới init(coder aDecoder: NSCoder)khi phát hành loadNibNamed()bị hỏng (bằng cách không thiết lập lớp tùy chỉnh trong tệp xib, init(coder aDecoder: NSCoder)thay vào đó là UIView vani đơn giản chứ không phải phiên bản tùy chỉnh của chúng tôi).

Mặc dù chúng tôi không thể thực hiện tùy chỉnh lớp đối với chế độ xem được lưu trữ trong xib trực tiếp, chúng tôi vẫn có thể liên kết chế độ xem với lớp con UIView 'cha' của chúng tôi bằng cách sử dụng các cửa hàng / hành động, v.v. sau khi đặt chủ sở hữu tệp của chế độ xem thành lớp tùy chỉnh của chúng tôi:

Đặt thuộc tính chủ sở hữu tệp của chế độ xem tùy chỉnh

Bạn có thể tìm thấy video mô tả việc triển khai từng bước một lớp chế độ xem như vậy bằng cách sử dụng phương pháp này trong video sau .


Xin chào Joe - cảm ơn những nhận xét của bạn, đó là rất nhiều điều táo bạo (cũng như nhiều nhận xét khác của bạn!) Như tôi đã nêu khi trả lời câu trả lời của bạn, tôi đồng ý rằng đối với hầu hết các tình huống, chế độ xem vùng chứa có lẽ là cách tiếp cận tốt nhất, nhưng trong những trường hợp đó nơi mà các chế độ xem phải được sử dụng trên các dự án (hoặc được phân phối) thì ít nhất nó cũng có ý nghĩa đối với tôi và dường như đối với những người khác cũng có một giải pháp thay thế. Cá nhân bạn có thể tin rằng đó là phong cách tồi, nhưng có lẽ bạn có thể để nhiều bài đăng khác ở đây gợi ý cách thực hiện điều này để tham khảo và để mọi người tự đánh giá. Cả hai cách tiếp cận dường như hữu ích.
Ken Chatfield,

Cám ơn vì cái này. Tôi đã thử một số cách tiếp cận khác nhau trong Swift mà không thành công cho đến khi tôi nghe theo lời khuyên của bạn về việc rời khỏi lớp của nib với tư cách là UIView. Tôi đồng ý rằng thật điên rồ khi Apple chưa bao giờ làm điều này trở nên dễ dàng và bây giờ nó gần như là không thể. Một container không phải lúc nào cũng là câu trả lời.
Echelon

16

BƯỚC 1. Thay thế selftừ Storyboard

Thay thế selftrong initWithCoder:phương thức sẽ không thành công với lỗi sau.

'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'

Thay vào đó, bạn có thể thay thế đối tượng được giải mã bằng awakeAfterUsingCoder:(not awakeFromNib). giống:

@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

BƯỚC 2. Ngăn chặn cuộc gọi đệ quy

Tất nhiên, điều này cũng gây ra vấn đề cuộc gọi đệ quy. (giải mã storyboard -> awakeAfterUsingCoder:-> loadNibNamed:-> awakeAfterUsingCoder:-> loadNibNamed:-> ...)
Vì vậy, bạn phải kiểm tra dòng điện awakeAfterUsingCoder:được gọi là trong quá trình giải mã Storyboard hay quá trình giải mã XIB. Bạn có một số cách để làm điều đó:

a) Sử dụng chế độ riêng tư @propertychỉ được đặt trong NIB.

@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end

và đặt "Thuộc tính thời gian chạy do người dùng xác định" chỉ trong 'MyCustomView.xib'.

Ưu điểm:

  • không ai

Nhược điểm:

  • Đơn giản là không hoạt động: setXib:sẽ được gọi là SAU awakeAfterUsingCoder:

b) Kiểm tra xem selfcó bất kỳ lượt xem phụ nào không

Thông thường, bạn có lượt xem phụ trong xib, nhưng không có trong bảng phân cảnh.

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    if(self.subviews.count > 0) {
        // loading xib
        return self;
    }
    else {
        // loading storyboard
        return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:nil
                                            options:nil] objectAtIndex:0];
    }
}

Ưu điểm:

  • Không có mẹo nào trong Trình tạo giao diện.

Nhược điểm:

  • Bạn không thể có lượt xem phụ trong Bảng phân cảnh của mình.

c) Đặt cờ tĩnh trong khi loadNibNamed:gọi

static BOOL _loadingXib = NO;

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    if(_loadingXib) {
        // xib
        return self;
    }
    else {
        // storyboard
        _loadingXib = YES;
        typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                                           owner:nil
                                                         options:nil] objectAtIndex:0];
        _loadingXib = NO;
        return view;
    }
}

Ưu điểm:

  • Đơn giản
  • Không có mẹo nào trong Trình tạo giao diện.

Nhược điểm:

  • Không an toàn: cờ chia sẻ tĩnh rất nguy hiểm

d) Sử dụng lớp con riêng trong XIB

Ví dụ: khai báo _NIB_MyCustomViewnhư một lớp con của MyCustomView. Và, sử dụng _NIB_MyCustomViewthay vì chỉ MyCustomViewtrong XIB của bạn.

MyCustomView.h:

@interface MyCustomView : UIView
@end

MyCustomView.m:

#import "MyCustomView.h"

@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In Storyboard decoding path.
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

@interface _NIB_MyCustomView : MyCustomView
@end

@implementation _NIB_MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In XIB decoding path.
    // Block recursive call.
    return self;
}
@end

Ưu điểm:

  • Không rõ ràng iftrongMyCustomView

Nhược điểm:

  • Prefixing _NIB_trick trong xib Interface Builder
  • tương đối nhiều mã hơn

e) Sử dụng lớp con làm trình giữ chỗ trong Bảng phân cảnh

Tương tự như d)nhưng sử dụng lớp con trong Storyboard, lớp gốc trong XIB.

Ở đây, chúng tôi khai báo MyCustomViewProtodưới dạng một lớp con của MyCustomView.

@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In storyboard decoding
    // Returns MyCustomView loaded from NIB.
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self superclass])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

Ưu điểm:

  • Rất an toàn
  • Dọn dẹp; Không có thêm mã trong MyCustomView.
  • Không có ifkiểm tra rõ ràng giống nhưd)

Nhược điểm:

  • Cần sử dụng lớp con trong bảng phân cảnh.

Tôi nghĩ e)là chiến lược an toàn và sạch sẽ nhất. Vì vậy, chúng tôi áp dụng điều đó ở đây.

BƯỚC 3. Sao chép thuộc tính

Sau loadNibNamed:trong 'awafterUsingCoder:', bạn phải sao chép một số thuộc tính selfmà từ đó được giải mã bản sao cho Bảng phân cảnh. framevà thuộc tính autolayout / autoresize đặc biệt quan trọng.

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                                       owner:nil
                                                     options:nil] objectAtIndex:0];
    // copy layout properities.
    view.frame = self.frame;
    view.autoresizingMask = self.autoresizingMask;
    view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;

    // copy autolayout constraints
    NSMutableArray *constraints = [NSMutableArray array];
    for(NSLayoutConstraint *constraint in self.constraints) {
        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;
        if(firstItem == self) firstItem = view;
        if(secondItem == self) secondItem = view;
        [constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
                                                            attribute:constraint.firstAttribute
                                                            relatedBy:constraint.relation
                                                               toItem:secondItem
                                                            attribute:constraint.secondAttribute
                                                           multiplier:constraint.multiplier
                                                             constant:constraint.constant]];
    }

    // move subviews
    for(UIView *subview in self.subviews) {
        [view addSubview:subview];
    }
    [view addConstraints:constraints];

    // Copy more properties you like to expose in Storyboard.

    return view;
}

GIẢI PHÁP CUỐI CÙNG

Như bạn có thể thấy, đây là một đoạn mã soạn sẵn. Chúng tôi có thể triển khai chúng dưới dạng 'danh mục'. Ở đây, tôi mở rộng UIView+loadFromNibmã thường được sử dụng .

#import <UIKit/UIKit.h>

@interface UIView (loadFromNib)
@end

@implementation UIView (loadFromNib)

+ (id)loadFromNib {
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self)
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}

- (void)copyPropertiesFromPrototype:(UIView *)proto {
    self.frame = proto.frame;
    self.autoresizingMask = proto.autoresizingMask;
    self.translatesAutoresizingMaskIntoConstraints = proto.translatesAutoresizingMaskIntoConstraints;
    NSMutableArray *constraints = [NSMutableArray array];
    for(NSLayoutConstraint *constraint in proto.constraints) {
        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;
        if(firstItem == proto) firstItem = self;
        if(secondItem == proto) secondItem = self;
        [constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
                                                            attribute:constraint.firstAttribute
                                                            relatedBy:constraint.relation
                                                               toItem:secondItem
                                                            attribute:constraint.secondAttribute
                                                           multiplier:constraint.multiplier
                                                             constant:constraint.constant]];
    }
    for(UIView *subview in proto.subviews) {
        [self addSubview:subview];
    }
    [self addConstraints:constraints];
}

Sử dụng điều này, bạn có thể khai báo MyCustomViewProtonhư sau:

@interface MyCustomViewProto : MyCustomView
@end

@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    MyCustomView *view = [MyCustomView loadFromNib];
    [view copyPropertiesFromPrototype:self];

    // copy additional properties as you like.

    return view;
}
@end

XIB:

Ảnh chụp màn hình XIB

Bảng phân cảnh:

Bảng phân cảnh

Kết quả:

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


3
Giải pháp phức tạp hơn vấn đề ban đầu. Để dừng vòng lặp đệ quy, bạn chỉ cần thiết lập đối tượng Chủ sở hữu tệp thay vì khai báo chế độ xem nội dung là kiểu lớp MyCustomView.
Bogdan Onu

Nó chỉ là sự đánh đổi của a) quy trình khởi tạo đơn giản nhưng phân cấp chế độ xem phức tạp và b) quy trình khởi tạo phức tạp nhưng phân cấp chế độ xem đơn giản. n'est-ce pas? ;)
rintaro

có bất kỳ liên kết tải xuống cho dự án này không?
karthikeyan

13

Đừng quên

Hai điểm quan trọng:

  1. Đặt Chủ sở hữu tệp của tệp .xib thành tên lớp của chế độ xem tùy chỉnh của bạn.
  2. Không đặt tên lớp tùy chỉnh trong IB cho chế độ xem gốc của .xib.

Tôi đã đến trang Hỏi & Đáp này vài lần trong khi học cách tạo các chế độ xem có thể sử dụng lại. Việc quên những điểm trên đã khiến tôi lãng phí rất nhiều thời gian để tìm ra nguyên nhân khiến đệ quy vô hạn xảy ra. Những điểm này được đề cập trong các câu trả lời khác ở đây và ở nơi khác , nhưng tôi chỉ muốn nhấn mạnh lại chúng ở đây.

Câu trả lời Swift đầy đủ của tôi với các bước ở đây .


2

Có một giải pháp sạch hơn nhiều so với các giải pháp ở trên: https://www.youtube.com/watch?v=xP7YvdlnHfA

Không có thuộc tính Runtime, không có sự cố gọi đệ quy nào cả. Tôi đã thử và nó hoạt động như một sự quyến rũ khi sử dụng từ bảng phân cảnh và từ XIB với các thuộc tính IBOutlet (iOS8.1, XCode6).

Chúc may mắn cho việc viết mã!


1
Cảm ơn @ingaham! Tuy nhiên, cách tiếp cận được nêu trong video giống với giải pháp thứ hai được đề xuất trong câu hỏi ban đầu (mã Swift được trình bày trong câu trả lời của tôi ở trên). Như trong cả hai trường hợp đó, nó liên quan đến việc thêm một subview vào lớp con UIView của wrapper và đây là lý do tại sao không có vấn đề gì với các cuộc gọi đệ quy, cũng như không cần phải dựa vào các thuộc tính thời gian chạy hoặc bất kỳ điều gì phức tạp khác. Như đã đề cập, nhược điểm là một chế độ xem con bổ sung dư thừa cần được thêm vào lớp UIView tùy chỉnh. Như đã thảo luận, đây có thể là giải pháp tốt nhất và đơn giản nhất hiện nay.
Ken Chatfield

Vâng, bạn hoàn toàn đúng, đây là những giải pháp giống hệt nhau. Tuy nhiên, một chế độ xem dư thừa là cần thiết, nhưng nó là giải pháp sạch sẽ và có thể bảo trì tốt nhất. Vì vậy, tôi quyết định sử dụng cái này.
ingaham

Tôi tin rằng một quan điểm thừa là hoàn toàn và hoàn toàn tự nhiên, và không bao giờ có thể có một giải pháp khác. Lưu ý rằng bạn đang nói "" cái gì đó "sẽ đi đến" đây "" ... rằng "đây" là một điều tự nhiên tồn tại. Đơn giản là phải có "một cái gì đó" ở đó, một "nơi bạn sẽ đặt mọi thứ" - chính định nghĩa của "khung cảnh" là gì. Và chờ đợi! Công cụ "chế độ xem vùng chứa" của Apple , thực sự, chính xác là .. có một "khung", "chế độ xem người giữ" ("chế độ xem vùng chứa") mà bạn xác định thứ gì đó. Thật vậy, các giải pháp chế độ xem 'dư thừa' chính xác là chế độ xem vùng chứa được làm thủ công! Chỉ cần sử dụng của Apple.
Fattie
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.