KROK 1. Wymiana self
z Storyboard
Wymiana self
w initWithCoder:
sposób nie powiedzie się następujący błąd.
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
Zamiast tego możesz zamienić zdekodowany obiekt na awakeAfterUsingCoder:
(nie awakeFromNib
). lubić:
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
KROK 2. Zapobieganie wywołaniom rekurencyjnym
Oczywiście powoduje to również problem z połączeniami rekurencyjnymi. (dekodowanie scenorysu -> awakeAfterUsingCoder:
-> loadNibNamed:
->awakeAfterUsingCoder:
-> loadNibNamed:
-> ...)
Więc musisz sprawdzić, czy bieżący awakeAfterUsingCoder:
jest wywoływany w procesie dekodowania Storyboard lub w procesie dekodowania XIB. Możesz to zrobić na kilka sposobów:
a) Użyj prywatnego @property
który jest ustawiony tylko w NIB.
@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end
i ustaw „User Defined Runtime Attributes” tylko w „MyCustomView.xib”.
Plusy:
Cons:
- Po prostu nie działa:
setXib:
zostanie wywołany PO awakeAfterUsingCoder:
b) Sprawdź, czy self
ma jakieś podglądy
Zwykle masz podglądy w xib, ale nie w scenorysie.
- (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];
}
}
Plusy:
- Żadnej sztuczki w Interface Builder.
Cons:
- Nie możesz mieć podglądów podrzędnych w swoim Storyboard.
c) Ustaw statyczną flagę podczas loadNibNamed:
rozmowy
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;
}
}
Plusy:
- Prosty
- Żadnej sztuczki w Interface Builder.
Cons:
- Niezabezpieczone: statyczna wspólna flaga jest niebezpieczna
d) Użyj prywatnej podklasy w XIB
Na przykład zadeklaruj _NIB_MyCustomView
jako podklasę klasy MyCustomView
. I używaj _NIB_MyCustomView
zamiast MyCustomView
w swoim 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
Plusy:
- Brak wyraźnego
if
wMyCustomView
Cons:
- Poprzedzając
_NIB_
tricka w XIb konstruktora Interface
- stosunkowo więcej kodów
e) Użyj podklasy jako symbolu zastępczego w Storyboard
Podobnie jak w przypadku d)
użycia podklasy w Storyboard, oryginalnej klasy w XIB.
Tutaj deklarujemy MyCustomViewProto
jako podklasę 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
Plusy:
- Bardzo bezpieczny
- Czysty; Brak dodatkowego kodu w
MyCustomView
.
- Brak wyraźnego
if
sprawdzenia tak samo jakd)
Cons:
- Musisz użyć podklasy w scenorysie.
Myślę, że e)
to najbezpieczniejsza i najczystsza strategia. Więc przyjmujemy to tutaj.
KROK 3. Kopiuj właściwości
Po loadNibNamed:
w „awakeAfterUsingCoder:” musisz skopiować kilka właściwości, z self
których jest zdekodowana instancja Storyboard. frame
a właściwości autolayout / autoresize są szczególnie ważne.
- (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;
}
OSTATECZNE ROZWIĄZANIE
Jak widać, jest to fragment kodu standardowego. Możemy je zaimplementować jako „kategorię”. Tutaj rozszerzam powszechnie używany UIView+loadFromNib
kod.
#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];
}
Korzystając z tego, możesz zadeklarować MyCustomViewProto
:
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
return view;
}
@end
XIB:
Storyboard:
Wynik: