Я всегда находил решение «добавить его в качестве подпредставления» неудовлетворительным, поскольку он привинчивает (1) автопоставку, (2) @IBInspectable
и (3) выходы. Вместо этого позвольте мне познакомить вас с магией awakeAfter:
, NSObject
метода.
awakeAfter
позволяет вам полностью заменить объект, фактически пробужденный из NIB / раскадровки. Затем объект проходит через процесс гидратации, вызывается awakeFromNib
, добавляется как вид и т. Д.
Мы можем использовать это в подклассе «вырез из картона» нашего представления, единственной целью которого будет загрузка представления из NIB и возврат его для использования в раскадровке. Встраиваемый подкласс затем указывается в инспекторе идентификации представления Раскадровки, а не в исходном классе. На самом деле это не обязательно должен быть подкласс, чтобы это работало, но, сделав его подклассом, IB может видеть любые свойства IBInspectable / IBOutlet.
Этот дополнительный шаблон может показаться неоптимальным - и в некотором смысле это так, потому что в идеале UIStoryboard
справился бы с этим беспрепятственно - но у него есть преимущество, заключающееся в том, что исходный NIB и подкласс UIView
полностью не изменяются. Роль, которую он играет, - это, в основном, класс адаптера или моста, и он совершенно допустим с точки зрения дизайна в качестве дополнительного класса, даже если он вызывает сожаление. С другой стороны, если вы предпочитаете экономить на своих классах, решение @ BenPatch работает путем реализации протокола с некоторыми другими незначительными изменениями. Вопрос о том, какое решение лучше, сводится к стилю программиста: предпочитаете ли вы композицию объектов или множественное наследование.
Примечание: класс, установленный для представления в файле NIB, остается прежним. Встраиваемый подкласс only используется в раскадровке. Подкласс не может использоваться для создания экземпляра представления в коде, поэтому у него не должно быть никакой дополнительной логики. Он должен только содержать awakeAfter
крючок.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ Единственным существенным недостатком здесь является то, что если вы определяете ограничения по ширине, высоте или соотношению сторон в раскадровке, которые не относятся к другому представлению, их необходимо скопировать вручную. Ограничения, которые связывают два представления, устанавливаются на ближайшем общем предке, и представления просыпаются из раскадровки изнутри, поэтому к тому времени, когда эти ограничения гидратируются в суперпредставлении, обмен уже произошел. Ограничения, которые включают только рассматриваемое представление, устанавливаются непосредственно в это представление и, таким образом, отбрасываются, когда происходит обмен, если они не скопированы.
Обратите внимание, что здесь происходит ограничение, установленное на представление в раскадровке копируются в недавно созданный экземпляр , который может уже иметь собственные ограничения, определенные в его файл пера. Это не влияет.
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
является типобезопасным расширением для UIView
. Все, что он делает, это перебирает объекты NIB, пока не найдет тот, который соответствует типу. Обратите внимание, что универсальным типом является значение return , поэтому тип должен быть указан на сайте вызова.
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
}
}