BƯỚC 1. Thay thế self
từ Storyboard
Thay thế self
trong 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ư @property
chỉ đượ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:
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 self
có 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) {
return self;
}
else {
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) {
return self;
}
else {
_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_MyCustomView
như một lớp con của MyCustomView
. Và, sử dụng _NIB_MyCustomView
thay vì chỉ MyCustomView
trong XIB của bạn.
MyCustomView.h:
@interface MyCustomView : UIView
@end
MyCustomView.m:
#import "MyCustomView.h"
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
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 {
return self;
}
@end
Ưu điểm:
- Không rõ ràng
if
trongMyCustomView
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 MyCustomViewProto
dưới dạng một lớp con của MyCustomView
.
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
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ó
if
kiể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 self
mà từ đó được giải mã bản sao cho Bảng phân cảnh. frame
và 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];
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
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]];
}
for(UIView *subview in self.subviews) {
[view addSubview:subview];
}
[view addConstraints:constraints];
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+loadFromNib
mã 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 MyCustomViewProto
như sau:
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
return view;
}
@end
XIB:
Bảng phân cảnh:
Kết quả: