Siempre he encontrado que la solución "agregarlo como una subvista" no es satisfactoria, ya que se atornilla con (1) salida automática, (2) @IBInspectable
y (3) salidas. En cambio, déjame presentarte la magia de awakeAfter:
un NSObject
método.
awakeAfter
le permite cambiar el objeto realmente despertado de un NIB / Storyboard con un objeto completamente diferente. Ese objeto luego se somete al proceso de hidratación, lo invoca awakeFromNib
, se agrega como una vista, etc.
Podemos usar esto en una subclase de "recorte de cartón" de nuestra vista, cuyo único propósito será cargar la vista desde el NIB y devolverla para usarla en el Storyboard. La subclase incorporable se especifica en el inspector de identidad de la vista del Guión gráfico, en lugar de la clase original. En realidad, no tiene que ser una subclase para que esto funcione, pero convertirlo en una subclase es lo que le permite a IB ver las propiedades de IBInspectable / IBOutlet.
Esta placa adicional podría parecer subóptima, y en cierto sentido lo es, porque lo ideal UIStoryboard
sería manejar esto sin problemas, pero tiene la ventaja de dejar el NIB original yUIView
subclase sin modificar por completo. El papel que desempeña es básicamente el de un adaptador o una clase de puente, y es perfectamente válido, en cuanto al diseño, como una clase adicional, incluso si es lamentable. Por otro lado, si prefiere ser parsimonioso con sus clases, la solución de @BenPatch funciona mediante la implementación de un protocolo con algunos otros cambios menores. La cuestión de qué solución es mejor se reduce a una cuestión de estilo de programador: si se prefiere la composición de objetos o la herencia múltiple.
Nota: la clase establecida en la vista en el archivo NIB sigue siendo la misma. La subclase integrable solo se usa en el guión gráfico. La subclase no se puede usar para crear una instancia de la vista en código, por lo que no debería tener ninguna lógica adicional. Debe solamente contendrá el awakeAfter
gancho.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ El único inconveniente importante aquí es que si define restricciones de ancho, alto o relación de aspecto en el guión gráfico que no se relacionan con otra vista, entonces deben copiarse manualmente. Las restricciones que relacionan dos vistas se instalan en el ancestro común más cercano, y las vistas se despiertan desde el guión gráfico desde adentro hacia afuera, por lo que para cuando esas restricciones se hidratan en la supervista, el intercambio ya ha ocurrido. Las restricciones que solo involucran la vista en cuestión se instalan directamente en esa vista y, por lo tanto, se lanzan cuando se produce el intercambio a menos que se copien.
Tenga en cuenta que lo que está sucediendo aquí es que las restricciones instaladas en la vista en el guión gráfico se copian en la vista recién instanciada , que ya puede tener sus propias restricciones, definidas en su archivo nib. Esos no se ven afectados.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!
for constraint in constraints {
if constraint.secondItem != nil {
newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
} else {
newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
}
}
return newView as Any
}
}
instantiateViewFromNib
es una extensión de tipo seguro para UIView
. Todo lo que hace es recorrer los objetos de la NIB hasta que encuentre uno que coincida con el tipo. Tenga en cuenta que el tipo genérico es el valor de retorno , por lo que el tipo debe especificarse en el sitio de la llamada.
extension UIView {
public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
if let objects = bundle.loadNibNamed(nibName, owner: nil) {
for object in objects {
if let object = object as? T {
return object
}
}
}
return nil
}
}