Eu sempre achei a solução "adicione-a como uma subvisão" insatisfatória, pois ela se aperta com (1) autolayout, (2) @IBInspectable
e (3) tomadas. Em vez disso, deixe-me apresentar-lhe a magia de awakeAfter:
um NSObject
método.
awakeAfter
permite trocar o objeto realmente acordado de um NIB / Storyboard por um objeto completamente diferente. Esse objeto é então submetido ao processo de hidratação, awakeFromNib
chamado, adicionado como uma vista, etc.
Podemos usar isso em uma subclasse "recorte de papelão" de nossa visão, cujo único objetivo será carregar a visão do NIB e devolvê-la para uso no Storyboard. A subclasse incorporável é especificada no inspetor de identidade da exibição Storyboard, em vez da classe original. Na verdade, ele não precisa ser uma subclasse para que isso funcione, mas torná-la uma subclasse é o que permite ao IB ver quaisquer propriedades do IBInspectable / IBOutlet.
Esse padrão adicional pode parecer abaixo do ideal - e, em certo sentido, é, porque o ideal UIStoryboard
seria lidar com isso sem problemas -, mas tem a vantagem de deixar o NIB original e a UIView
subclasse completamente inalterada. O papel que desempenha é basicamente o de uma classe de adaptador ou ponte e é perfeitamente válido, em termos de design, como uma classe adicional, mesmo que seja lamentável. Por outro lado, se você preferir ser parcimonioso com suas classes, a solução do @ BenPatch funciona implementando um protocolo com algumas outras pequenas alterações. A questão de qual solução é melhor se resume a uma questão de estilo de programador: se prefere a composição do objeto ou a herança múltipla.
Nota: a classe configurada na visualização no arquivo NIB permanece a mesma. A subclasse incorporável é usada apenas no storyboard. A subclasse não pode ser usada para instanciar a visualização no código; portanto, ela não deve ter nenhuma lógica adicional. Deve conter apenas o awakeAfter
gancho.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ A única desvantagem aqui é que, se você definir restrições de largura, altura ou proporção no storyboard que não se relacionam a outra vista, elas deverão ser copiadas manualmente. As restrições que relacionam duas visualizações são instaladas no ancestral comum mais próximo e as visualizações são despertadas do storyboard de dentro para fora; portanto, quando essas restrições são hidratadas na superview, a troca já ocorreu. As restrições que envolvem apenas a visualização em questão são instaladas diretamente nessa visualização e, portanto, são lançadas quando a troca ocorre, a menos que sejam copiadas.
Observe que o que está acontecendo aqui é que as restrições instaladas na exibição no storyboard são copiadas para a exibição recém-instanciada , que já pode ter restrições próprias, definidas em seu arquivo de ponta. Aqueles não são afetados.
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
é uma extensão de tipo seguro para UIView
. Tudo o que faz é percorrer os objetos do NIB até encontrar um que corresponda ao tipo. Observe que o tipo genérico é o valor de retorno , portanto, o tipo deve ser especificado no site de chamada.
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
}
}