SCHRITT 1. Ersetzen self
aus dem Storyboard
Ersetzen self
in initWithCoder:
Methode fehl mit folgendem Fehler.
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
Stattdessen können Sie dekodierte Objekte durch awakeAfterUsingCoder:
(nicht awakeFromNib
) ersetzen . mögen:
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
SCHRITT 2. Rekursiven Aufruf verhindern
Dies verursacht natürlich auch ein Problem mit rekursiven Aufrufen. (Storyboard-Dekodierung -> awakeAfterUsingCoder:
-> loadNibNamed:
-> awakeAfterUsingCoder:
-> loadNibNamed:
-> ...)
Sie müssen also überprüfen, ob der aktuelle awakeAfterUsingCoder:
Wert im Storyboard-Dekodierungsprozess oder im XIB-Dekodierungsprozess aufgerufen wird. Sie haben mehrere Möglichkeiten, dies zu tun:
a) Verwenden Sie private, @property
die nur in der NIB festgelegt ist.
@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end
und setzen Sie "Benutzerdefinierte Laufzeitattribute" nur in "MyCustomView.xib".
Vorteile:
Nachteile:
- Funktioniert einfach nicht:
setXib:
wird NACHher aufgerufen awakeAfterUsingCoder:
b) Überprüfen Sie, ob self
Unteransichten vorhanden sind
Normalerweise haben Sie Unteransichten in der xib, aber nicht im Storyboard.
- (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];
}
}
Vorteile:
- Kein Trick im Interface Builder.
Nachteile:
- Sie können keine Unteransichten in Ihrem Storyboard haben.
c) Setzen Sie während des loadNibNamed:
Anrufs ein statisches Flag
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;
}
}
Vorteile:
- Einfach
- Kein Trick im Interface Builder.
Nachteile:
- Nicht sicher: Statisches Shared Flag ist gefährlich
d) Verwenden Sie eine private Unterklasse in XIB
Deklarieren Sie beispielsweise _NIB_MyCustomView
als Unterklasse von MyCustomView
. Und verwenden Sie _NIB_MyCustomView
statt MyCustomView
nur in Ihrem 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
Vorteile:
- Keine explizite
if
inMyCustomView
Nachteile:
- Prefixing
_NIB_
Trick in xib Interface Builder
- relativ mehr Codes
e) Verwenden Sie die Unterklasse als Platzhalter im Storyboard
Ähnlich, d)
aber Unterklasse in Storyboard verwenden, Originalklasse in XIB.
Hier deklarieren wir MyCustomViewProto
als Unterklasse von 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
Vorteile:
- Sehr sicher
- Sauber; Kein zusätzlicher Code in
MyCustomView
.
- Keine explizite
if
Prüfung wied)
Nachteile:
- Unterklasse muss im Storyboard verwendet werden.
Ich denke, das e)
ist die sicherste und sauberste Strategie. Also übernehmen wir das hier.
SCHRITT 3. Eigenschaften kopieren
Nach loadNibNamed:
'awakeAfterUsingCoder:' müssen Sie mehrere Eigenschaften kopieren, aus self
denen die Instanz des Storyboards dekodiert wird. frame
und Autolayout / Autoresize-Eigenschaften sind besonders wichtig.
- (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;
}
ENDGÜLTIGE LÖSUNG
Wie Sie sehen können, ist dies ein bisschen Boilerplate-Code. Wir können sie als "Kategorie" implementieren. Hier erweitere ich häufig verwendeten UIView+loadFromNib
Code.
#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];
}
Mit diesem können Sie MyCustomViewProto
wie folgt deklarieren :
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
return view;
}
@end
XIB:
Storyboard:
Ergebnis: