PASO 1. Reemplazo self
de Storyboard
Sustitución self
en el initWithCoder:
método con el error siguiente.
'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'
En su lugar, puede reemplazar el objeto decodificado con awakeAfterUsingCoder:
(not awakeFromNib
). me gusta:
@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
owner:nil
options:nil] objectAtIndex:0];
}
@end
PASO 2. Prevención de llamadas recursivas
Por supuesto, esto también causa problemas de llamadas recursivas. (decodificación del guión gráfico -> awakeAfterUsingCoder:
-> loadNibNamed:
-> awakeAfterUsingCoder:
-> loadNibNamed:
-> ...)
Por lo tanto, debe verificar que awakeAfterUsingCoder:
se llama a la corriente en el proceso de decodificación del guión gráfico o el proceso de decodificación XIB. Tienes varias formas de hacerlo:
a) Utilice privado @property
que está configurado en NIB solamente.
@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end
y establezca "Atributos de tiempo de ejecución definidos por el usuario" solo en 'MyCustomView.xib'.
Pros:
Contras:
- Simplemente no funciona:
setXib:
se llamará DESPUÉS awakeAfterUsingCoder:
b) Compruebe si self
tiene subvistas
Normalmente, tiene subvistas en el xib, pero no en el guión gráfico.
- (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];
}
}
Pros:
- Ningún truco en Interface Builder.
Contras:
- No puede tener subvistas en su Storyboard.
c) Establecer una bandera estática durante la loadNibNamed:
llamada
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;
}
}
Pros:
- Sencillo
- Ningún truco en Interface Builder.
Contras:
- No seguro: la bandera estática compartida es peligrosa
d) Usar subclase privada en XIB
Por ejemplo, declare _NIB_MyCustomView
como una subclase de MyCustomView
. Y utilícelo en _NIB_MyCustomView
lugar de solo MyCustomView
en su 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
Pros:
- No explícito
if
enMyCustomView
Contras:
- Truco
_NIB_
de prefijo en xib Interface Builder
- relativamente más códigos
e) Usar subclase como marcador de posición en Storyboard
Similar a la d)
subclase en Storyboard, pero con uso de la clase original en XIB.
Aquí, declaramos MyCustomViewProto
como una subclase de 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
Pros:
- Muy seguro
- Limpiar; No hay código adicional en
MyCustomView
.
- Sin
if
verificación explícita igual qued)
Contras:
- Necesita usar una subclase en el guión gráfico.
Creo que e)
es la estrategia más segura y limpia. Así que adoptamos eso aquí.
PASO 3. Copiar propiedades
Después, loadNibNamed:
en 'awakeAfterUsingCoder:', debe copiar varias propiedades de las self
que se decodifica la instancia del Storyboard. frame
y las propiedades de diseño automático / tamaño automático son especialmente importantes.
- (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;
}
SOLUCIÓN FINAL
Como puede ver, esto es un código repetitivo. Podemos implementarlos como 'categoría'. Aquí, extiendo el UIView+loadFromNib
código de uso común .
#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];
}
Usando esto, puede declarar MyCustomViewProto
como:
@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
MyCustomView *view = [MyCustomView loadFromNib];
[view copyPropertiesFromPrototype:self];
return view;
}
@end
XIB:
Guión gráfico:
Resultado: