Ho sempre trovato insoddisfacente la soluzione "aggiungi come sottoview", visto che si avvita con (1) autolayout, (2) @IBInspectable
e (3) outlet. Invece, lascia che ti presenti la magia di awakeAfter:
, unNSObject
metodo.
awakeAfter
consente di scambiare l'oggetto effettivamente svegliato da un NIB / Storyboard con un oggetto completamente diverso. Che scopo viene poi messo attraverso il processo di idratazione, ha awakeFromNib
chiamato su di esso, viene aggiunto come vista, etc.
Possiamo usarlo in una sottoclasse di "ritagli di cartone" secondo la nostra visione, il cui unico scopo sarà caricare la vista dal NIB e restituirla per l'uso nello Storyboard. La sottoclasse incorporabile viene quindi specificata nella finestra di ispezione delle identità della vista Storyboard, piuttosto che nella classe originale. In realtà non deve essere una sottoclasse affinché funzioni, ma renderla una sottoclasse è ciò che consente a IB di vedere le proprietà IBInspectable / IBOutlet.
Questa piastra aggiuntiva potrebbe sembrare non ottimale - e in un certo senso lo è, perché idealmente UIStoryboard
dovrebbe gestirla senza problemi - ma ha il vantaggio di lasciare completamente il NIB originale e la UIView
sottoclasse. Il ruolo che svolge è fondamentalmente quello di un adattatore o di una classe bridge ed è perfettamente valido, dal punto di vista del design, come classe aggiuntiva, anche se è deplorevole. D'altro canto, se preferisci essere parsimonioso con le tue classi, la soluzione di @ BenPatch funziona implementando un protocollo con alcune altre modifiche minori. La domanda su quale soluzione sia migliore si riduce a una questione di stile del programmatore: se si preferisce la composizione degli oggetti o l'ereditarietà multipla.
Nota: la classe impostata nella vista nel file NIB rimane la stessa. La sottoclasse incorporabile viene utilizzata solo nello storyboard. La sottoclasse non può essere utilizzata per creare un'istanza della vista nel codice, quindi non dovrebbe avere alcuna logica aggiuntiva. Dovrebbe contenere solo il awakeAfter
gancio.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ L'unico svantaggio significativo qui è che se si definiscono vincoli di larghezza, altezza o proporzioni nello storyboard che non si riferiscono a un'altra vista, devono essere copiati manualmente. I vincoli che riguardano due viste sono installati sull'antenato comune più vicino e le viste vengono risvegliate dallo storyboard dall'interno verso l'esterno, quindi quando questi vincoli vengono idratati sulla superview lo scambio è già avvenuto. I vincoli che riguardano solo la vista in questione vengono installati direttamente su quella vista e vengono quindi lanciati quando si verifica lo scambio a meno che non vengano copiati.
Si noti che ciò che sta accadendo qui è che i vincoli installati nella vista nello storyboard vengono copiati nella vista appena istanziata , che potrebbe già avere vincoli propri, definiti nel suo file pennino. Quelli non sono interessati.
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
è un'estensione sicura per i tipi di UIView
. Tutto ciò che fa è passare in rassegna gli oggetti del NIB fino a quando non trova quello che corrisponde al tipo. Si noti che il tipo generico è il valore restituito , quindi il tipo deve essere specificato nel sito della chiamata.
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
}
}