Я всегда находил решение «добавить его как подпредставление» неудовлетворительным, видя, как оно связано с (1) автоматическим раскладыванием, (2) @IBInspectable
и (3) выходами. Вместо этого позвольте мне познакомить вас с магией awakeAfter:
, в NSObject
методе.
awakeAfter
позволяет вам полностью заменить объект, фактически проснувшийся из NIB / Storyboard, другим объектом. Затем этот объект проходит процесс гидратации, вызывает awakeFromNib
его, добавляется в качестве представления и т. Д.
Мы можем использовать это в подклассе «картонный вырез» нашего представления, единственной целью которого будет загрузка представления из NIB и его возврат для использования в раскадровке. Встраиваемый подкласс затем указывается в инспекторе идентичности представления Storyboard, а не в исходном классе. На самом деле он не обязательно должен быть подклассом, чтобы это работало, но именно создание подкласса позволяет IB видеть любые свойства IBInspectable / IBOutlet.
Этот дополнительный шаблон может показаться неоптимальным - и в некотором смысле так оно и есть, потому что в идеале UIStoryboard
он справился бы с этим без проблем, - но у него есть то преимущество, что исходный NIB и UIView
подкласс не изменяются. Роль, которую он играет, в основном является ролью класса адаптера или моста и вполне допустима с точки зрения дизайна как дополнительный класс, даже если это достойно сожаления. С другой стороны, если вы предпочитаете быть экономным в своих классах, решение @ BenPatch работает путем реализации протокола с некоторыми другими незначительными изменениями. Вопрос о том, какое решение лучше, сводится к вопросу стиля программиста: предпочитает ли он композицию объектов или множественное наследование.
Примечание: класс, установленный для представления в файле NIB, остается прежним. Встраиваемый подкласс используется только в раскадровке. Подкласс нельзя использовать для создания экземпляра представления в коде, поэтому он не должен иметь никакой дополнительной логики. Он должен содержать только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, пока не найдет тот, который соответствует типу. Обратите внимание, что универсальный тип - это возвращаемое значение, поэтому тип должен быть указан на месте вызова.
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
}
}