Zawsze uważałem, że rozwiązanie „dodaj to jako podwidok” nie jest satysfakcjonujące, biorąc pod uwagę, że wkręca się w (1) autoukładanie, (2) @IBInspectablei (3) gniazda. Zamiast tego, pozwól mi przedstawić Wam magię awakeAfter:, o NSObjectmetodzie.
awakeAfterpozwala całkowicie zamienić obiekt faktycznie obudzony z NIB / Storyboard na inny obiekt. Ten obiekt jest następnie poddawany procesowi hydratacji, awakeFromNibwywoływany, dodawany jako widok itp.
Możemy to wykorzystać w podklasie "wycinanej tektury" naszego widoku, której jedynym celem będzie załadowanie widoku z NIB i zwrócenie go do użycia w Storyboard. Podklasa do osadzania jest następnie określana w inspektorze tożsamości widoku Storyboard, a nie w oryginalnej klasie. W rzeczywistości nie musi to być podklasa, aby to zadziałało, ale uczynienie z niej podklasy jest tym, co pozwala IB zobaczyć wszystkie właściwości IBInspectable / IBOutlet.
Ten dodatkowy szablon może wydawać się nieoptymalny - iw pewnym sensie tak jest, ponieważ idealnie UIStoryboardby sobie z tym poradził - ale ma tę zaletę, że pozostawia oryginalny NIB i UIViewpodklasę całkowicie niezmienioną. Rola, jaką odgrywa, jest zasadniczo rolą klasy adaptera lub mostka i jest całkowicie słuszna, jeśli chodzi o projekt, jako klasa dodatkowa, nawet jeśli jest to godne pożałowania. Z drugiej strony, jeśli wolisz być oszczędny w swoich klasach, rozwiązanie @ BenPatch działa poprzez implementację protokołu z kilkoma innymi drobnymi zmianami. Pytanie, które rozwiązanie jest lepsze, sprowadza się do kwestii stylu programisty: czy preferuje się kompozycję obiektów, czy wielokrotne dziedziczenie.
Uwaga: klasa ustawiona w widoku w pliku NIB pozostaje taka sama. Podklasa osadzalna jest używana tylko w scenorysie. Podklasy nie można użyć do utworzenia wystąpienia widoku w kodzie, więc sama nie powinna mieć żadnej dodatkowej logiki. Powinien zawierać tylkoawakeAfter haczyk.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ Jedyną istotną wadą jest to, że jeśli zdefiniujesz ograniczenia szerokości, wysokości lub proporcji w scenorysie, które nie odnoszą się do innego widoku, musisz je ręcznie skopiować. Ograniczenia, które odnoszą się do dwóch widoków, są instalowane na najbliższym wspólnym przodku, a widoki są budzone z serii ujęć od wewnątrz na zewnątrz, więc zanim te ograniczenia zostaną uwodnione w nadzorze, zamiana już nastąpiła. Ograniczenia, które dotyczą tylko danego widoku, są instalowane bezpośrednio w tym widoku, a zatem są odrzucane, gdy nastąpi zamiana, chyba że zostaną skopiowane.
Zauważ, że to, co się tutaj dzieje, to ograniczenia zainstalowane w widoku w scenorysie są kopiowane do nowo utworzonego widoku , który może już mieć własne ograniczenia, zdefiniowane w pliku końcówki. Te są nienaruszone.
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
}
}
instantiateViewFromNibjest bezpiecznym rozszerzeniem do UIView. Wszystko, co robi, to pętla po obiektach NIB, dopóki nie znajdzie takiego, który pasuje do typu. Zauważ, że typ rodzajowy jest powrót wartość, więc typ musi być określona w miejscu wywołania.
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
}
}