КРОК 1. Заміна self
з Storyboard
Заміна self
в initWithCoder:
методі не вдасться з наступною помилкою.
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
Натомість декодований об’єкт можна замінити на awakeAfterUsingCoder:
(not awakeFromNib
). подібно до:
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
КРОК2. Запобігання рекурсивному дзвінку
Звичайно, це також спричиняє рекурсивну проблему дзвінка. (декодування розкадровки -> awakeAfterUsingCoder:
-> loadNibNamed:
-> awakeAfterUsingCoder:
-> loadNibNamed:
-> ...)
Отже, вам потрібно перевірити струмawakeAfterUsingCoder:
чи викликається у процесі розшифровки Storyboard або в процесі декодування XIB. У вас є кілька способів зробити це:
а) Використовуйте приватне, @property
яке встановлено лише у NIB.
@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end
і встановити "Визначені користувачем атрибути виконання" лише в "MyCustomView.xib".
Плюси:
Мінуси:
- Просто не працює:
setXib:
буде викликано ПІСЛЯ awakeAfterUsingCoder:
б) Перевірте, чи self
немає підпроектів
Зазвичай у вас є підпрогляди в xib, але не в розкадруванні.
- (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];
}
}
Плюси:
- Немає хитрості в Interface Builder.
Мінуси:
- Ви не можете мати підпрограми у своїй розкадровці.
в) Встановіть статичний прапор під час loadNibNamed:
дзвінка
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;
}
}
Плюси:
- Простий
- Немає хитрості в Interface Builder.
Мінуси:
- Небезпечно: статичний спільний прапор небезпечний
г) Використовуйте приватний підклас у XIB
Наприклад, оголосити _NIB_MyCustomView
як підклас MyCustomView
. І використовуйте _NIB_MyCustomView
замість цього лише MyCustomView
у своєму XIB.
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
Плюси:
- Немає явного
if
вMyCustomView
Мінуси:
- Приставка
_NIB_
трик в XIb Interface Builder
- відносно більше кодів
e) Використовуйте підклас як заповнювач у Storyboard
Подібно до, d)
але використовуйте підклас у Storyboard, оригінальний клас у XIB.
Тут ми оголошуємо MyCustomViewProto
як підклас 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
Плюси:
- Дуже безпечно
- Чистий; Немає зайвого коду в
MyCustomView
.
- Немає явної
if
перевірки, такої ж, якd)
Мінуси:
- Потрібно використовувати підклас у розкадруванні.
Я думаю, що e)
це найбезпечніша і найчистіша стратегія. Отже, ми приймаємо це тут.
КРОК3. Властивості копіювання
Після того, як loadNibNamed:
у 'awakeAfterUsingCoder:', Ви повинні скопіювати кілька властивостей, з self
яких декодується екземпляр розкадрування. frame
і особливо важливі властивості автоматичного розміщення / автоматичного зміни розміру.
- (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;
}
КІНЦЕВЕ РІШЕННЯ
Як бачите, це трохи зразкового коду. Ми можемо реалізувати їх як "категорію". Тут я розширюю загальновживаний UIView+loadFromNib
код.
#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];
}
Використовуючи це, ви можете оголосити, MyCustomViewProto
як:
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
return view;
}
@end
XIB:
Розкадровка:
Результат: