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) @IBInspectable
i (3) gniazda. Zamiast tego, pozwól mi przedstawić Wam magię awakeAfter:
, o NSObject
metodzie.
awakeAfter
pozwala całkowicie zamienić obiekt faktycznie obudzony z NIB / Storyboard na inny obiekt. Ten obiekt jest następnie poddawany procesowi hydratacji, awakeFromNib
wywoł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 UIStoryboard
by sobie z tym poradził - ale ma tę zaletę, że pozostawia oryginalny NIB i UIView
podklasę 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
}
}
instantiateViewFromNib
jest 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
}
}