Ich fand die Lösung "Als Unteransicht hinzufügen" immer unbefriedigend, da sie mit (1) Autolayout-, (2) @IBInspectable
und (3) Steckdosen verschraubt ist. Lassen Sie mich Ihnen stattdessen die Magie awakeAfter:
einer NSObject
Methode vorstellen .
awakeAfter
Mit dieser Option können Sie das tatsächlich von einer NIB / Storyboard aufgeweckte Objekt gegen ein völlig anderes Objekt austauschen. Dieses Objekt wird dann dem Hydratationsprozess unterzogen, hat es awakeFromNib
aufgerufen, wird als Ansicht hinzugefügt usw.
Wir können dies in einer Unterklasse "Pappausschnitt" unserer Ansicht verwenden, deren einziger Zweck darin besteht, die Ansicht von der NIB zu laden und zur Verwendung im Storyboard zurückzugeben. Die einbettbare Unterklasse wird dann im Identitätsinspektor der Storyboard-Ansicht und nicht in der ursprünglichen Klasse angegeben. Es muss eigentlich keine Unterklasse sein, damit dies funktioniert, aber wenn Sie es zu einer Unterklasse machen, kann IB alle IBInspectable / IBOutlet-Eigenschaften anzeigen.
Diese zusätzliche Boilerplate mag suboptimal erscheinen - und in gewissem Sinne, weil sie dies im Idealfall UIStoryboard
nahtlos handhaben würde -, hat jedoch den Vorteil, dass die ursprüngliche NIB und UIView
Unterklasse vollständig unverändert bleiben. Die Rolle, die es spielt, ist im Grunde die eines Adapters oder einer Bridge-Klasse und ist in Bezug auf das Design als zusätzliche Klasse absolut gültig, auch wenn es bedauerlich ist. Auf der anderen Seite, wenn Sie es vorziehen, mit Ihren Klassen sparsam umzugehen, implementiert die Lösung von @ BenPatch ein Protokoll mit einigen anderen geringfügigen Änderungen. Die Frage, welche Lösung besser ist, hängt vom Programmierstil ab: ob man die Objektzusammensetzung oder die Mehrfachvererbung bevorzugt.
Hinweis: Die in der Ansicht in der NIB-Datei festgelegte Klasse bleibt unverändert. Die einbettbare Unterklasse wird nur im Storyboard verwendet. Die Unterklasse kann nicht zum Instanziieren der Ansicht im Code verwendet werden, daher sollte sie selbst keine zusätzliche Logik haben. Es sollte nur den awakeAfter
Haken enthalten .
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ Der einzige wesentliche Nachteil hierbei ist, dass wenn Sie im Storyboard Einschränkungen für Breite, Höhe oder Seitenverhältnis definieren, die sich nicht auf eine andere Ansicht beziehen, diese manuell kopiert werden müssen. Einschränkungen, die zwei Ansichten betreffen, werden auf dem nächsten gemeinsamen Vorfahren installiert, und Ansichten werden von innen nach außen aus dem Storyboard geweckt. Wenn diese Einschränkungen in der Übersicht hydratisiert sind, ist der Austausch bereits erfolgt. Einschränkungen, die nur die betreffende Ansicht betreffen, werden direkt in dieser Ansicht installiert und werden daher beim Auslagern verworfen, sofern sie nicht kopiert werden.
Beachten Sie, dass hier Einschränkungen in der Ansicht im Storyboard installiert werden, die in die neu instanziierte Ansicht kopiert werden , für die möglicherweise bereits eigene Einschränkungen in der NIB-Datei definiert sind. Diese sind nicht betroffen.
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
ist eine typsichere Erweiterung von UIView
. Es werden lediglich die Objekte der NIB durchlaufen, bis eines gefunden wird, das dem Typ entspricht. Beachten Sie, dass der generische Typ der Rückgabewert ist, daher muss der Typ am Aufrufstandort angegeben werden.
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
}
}