J'ai toujours trouvé la solution «ajouter comme sous-vue» insatisfaisante, car elle se visse avec (1) la mise en page automatique, (2) @IBInspectable
et (3) prises. Au lieu de cela, laissez-moi vous présenter la magie d' awakeAfter:
une NSObject
méthode.
awakeAfter
vous permet d'échanger l'objet réellement réveillé d'un NIB / Storyboard avec un objet complètement différent. Cet objet est ensuite soumis au processus d'hydratation, l'a awakeFromNib
appelé, est ajouté en tant que vue, etc.
Nous pouvons utiliser ceci dans une sous-classe "découpée en carton" de notre vue, dont le seul but sera de charger la vue depuis le NIB et de la renvoyer pour une utilisation dans le Storyboard. La sous-classe intégrable est ensuite spécifiée dans l'inspecteur d'identité de la vue Storyboard, plutôt que dans la classe d'origine. Il n'est pas nécessaire que ce soit une sous-classe pour que cela fonctionne, mais en faire une sous-classe est ce qui permet à IB de voir toutes les propriétés IBInspectable / IBOutlet.
Ce passe-partout supplémentaire peut sembler sous-optimal - et dans un sens, il l'est, car idéalement UIStoryboard
le gérerait de manière transparente - mais il a l'avantage de laisser le NIB d'origine et la UIView
sous - classe complètement inchangés. Le rôle qu'il joue est essentiellement celui d'un adaptateur ou d'une classe de pont, et est parfaitement valable, du point de vue de la conception, en tant que classe supplémentaire, même si c'est regrettable. D'un autre côté, si vous préférez être parcimonieux avec vos classes, la solution de @ BenPatch fonctionne en implémentant un protocole avec quelques autres changements mineurs. La question de savoir quelle solution est la meilleure se résume à une question de style programmeur: si l'on préfère la composition d'objets ou l'héritage multiple.
Remarque: la classe définie sur la vue dans le fichier NIB reste la même. La sous - classe intégrable est uniquement utilisé dans le story - board. La sous-classe ne peut pas être utilisée pour instancier la vue dans le code, elle ne devrait donc pas avoir de logique supplémentaire elle-même. Il doit seulement contenir le awakeAfter
crochet.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ Le seul inconvénient majeur ici est que si vous définissez des contraintes de largeur, de hauteur ou de rapport hauteur / largeur dans le storyboard qui ne sont pas liées à une autre vue, elles doivent être copiées manuellement. Les contraintes qui relient deux vues sont installées sur l'ancêtre commun le plus proche, et les vues sont réveillées du storyboard de l'intérieur vers l'extérieur, donc au moment où ces contraintes sont hydratées sur la supervision, l'échange a déjà eu lieu. Les contraintes qui n'impliquent que la vue en question sont installées directement sur cette vue, et sont donc rejetées lorsque l'échange se produit, à moins qu'elles ne soient copiées.
Notez que ce qui se passe ici, c'est que les contraintes installées sur la vue dans le storyboard sont copiées dans la vue nouvellement instanciée , qui peut déjà avoir ses propres contraintes, définies dans son fichier nib. Ceux-ci ne sont pas affectés.
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
est une extension de type sécurisé de UIView
. Tout ce qu'il fait est de parcourir les objets du NIB jusqu'à ce qu'il en trouve un qui correspond au type. Notez que le type générique est la valeur de retour , le type doit donc être spécifié sur le site d'appel.
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
}
}