Saya selalu menemukan solusi "tambahkan sebagai subview" tidak memuaskan, karena sekrup dengan (1) autolayout, (2) @IBInspectable
, dan (3) outlet. Sebagai gantinya, izinkan saya memperkenalkan Anda pada keajaiban awakeAfter:
, NSObject
metode.
awakeAfter
memungkinkan Anda menukar objek yang sebenarnya dibangunkan dari NIB / Storyboard dengan objek yang sama sekali berbeda. Itu objek kemudian dimasukkan melalui proses hidrasi, memilikiawakeFromNib
disebut di atasnya, ditambahkan sebagai pandangan, dll
Kita bisa menggunakan ini dalam subkelas "kardus cut-out" dari pandangan kita, satu-satunya tujuan yang akan memuat pandangan dari NIB dan mengembalikannya untuk digunakan dalam Storyboard. Subclass yang dapat disematkan kemudian ditentukan dalam inspektur identitas tampilan Storyboard, daripada kelas aslinya. Sebenarnya tidak harus menjadi subclass agar ini berfungsi, tetapi menjadikannya subclass adalah apa yang memungkinkan IB untuk melihat properti IBInspectable / IBOutlet.
Pelat ekstra ini mungkin tampak suboptimal — dan dalam arti tertentu, karena idealnya UIStoryboard
akan menangani hal ini dengan mulus — tetapi memiliki keuntungan meninggalkan NIB asli danUIView
subkelas sepenuhnya tidak dimodifikasi. Peran yang dimainkannya pada dasarnya adalah kelas adaptor atau jembatan, dan sangat valid, desain-bijaksana, sebagai kelas tambahan, bahkan jika itu disesalkan. Di sisi lain, jika Anda lebih suka pelit dengan kelas Anda, solusi @ BenPatch bekerja dengan mengimplementasikan protokol dengan beberapa perubahan kecil lainnya. Pertanyaan tentang solusi mana yang lebih baik bermuara pada masalah gaya programmer: apakah orang lebih suka komposisi objek atau multiple inheritance.
Catatan: kelas yang diatur pada tampilan di file NIB tetap sama. Subclass yang dapat disematkan hanya digunakan di storyboard. Subkelas tidak dapat digunakan untuk membuat tampilan dalam kode, jadi seharusnya tidak memiliki logika tambahan. Seharusnya hanya berisi awakeAfter
kait.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ Satu kelemahan signifikan di sini adalah jika Anda menentukan batasan rasio lebar, tinggi, atau aspek dalam storyboard yang tidak berhubungan dengan tampilan lain, maka harus disalin secara manual. Kendala yang berhubungan dengan dua pandangan dipasang pada leluhur bersama terdekat, dan pandangan terbangun dari storyboard dari dalam-luar, sehingga pada saat kendala terhidrasi pada superview pertukaran telah terjadi. Kendala yang hanya melibatkan tampilan yang dipermasalahkan dipasang langsung pada tampilan itu, dan dengan demikian dilemparkan ketika swap terjadi kecuali mereka disalin.
Perhatikan bahwa yang terjadi di sini adalah kendala yang dipasang pada tampilan di storyboard disalin ke tampilan yang baru dipakai , yang mungkin sudah memiliki kendala sendiri, yang didefinisikan dalam file nib-nya. Mereka tidak terpengaruh.
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
adalah ekstensi aman untuk UIView
. Yang dilakukannya hanyalah mengulang-ulang objek NIB hingga menemukan yang cocok dengan tipenya. Perhatikan bahwa tipe generik adalah nilai balik , jadi tipe tersebut harus ditentukan di situs panggilan.
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
}
}