Memuat UIView dari nib di Swift


148

Berikut ini adalah kode Objective-C yang saya gunakan untuk memuat nib untuk penyesuaian saya UIView:

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}

Apa kode yang setara di Swift?

Jawaban:


172

Solusi Asli

  1. Saya membuat XIB dan kelas bernama SomeView (menggunakan nama yang sama untuk kenyamanan dan keterbacaan). Saya mendasarkan keduanya pada UIView.
  2. Di XIB, saya mengubah kelas "File's Owner" ke SomeView (di inspektur identitas).
  3. Saya membuat outlet UIView di SomeView.swift, menautkannya ke tampilan tingkat atas dalam file XIB (menamakannya "tampilan" untuk kenyamanan). Saya kemudian menambahkan outlet lain ke kontrol lain di file XIB sesuai kebutuhan.
  4. di SomeView.swift, saya memuat XIB di dalam inisialisasi "init with code". Tidak perlu menetapkan apa pun untuk "diri". Segera setelah XIB dimuat, semua outlet terhubung, termasuk tampilan tingkat atas. Satu-satunya hal yang hilang, adalah menambahkan tampilan atas ke hierarki tampilan:

.

class SomeView: UIView {
   required init(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
      self.addSubview(self.view);    // adding the top level view to the view hierarchy
   }
   ...
}

Perhatikan bahwa dengan cara ini saya mendapatkan kelas yang memuat dirinya sendiri dari nib. Saya kemudian dapat menggunakan SomeView sebagai kelas setiap kali UIView dapat digunakan dalam proyek (dalam pembangun antarmuka atau secara terprogram).

Perbarui - menggunakan sintaks Swift 3

Memuat xib dalam ekstensi berikut ini ditulis sebagai metode instance, yang kemudian dapat digunakan oleh penginisialisasi seperti yang di atas:

extension UIView {

    @discardableResult   // 1
    func fromNib<T : UIView>() -> T? {   // 2
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {    // 3
            // xib not loaded, or its top view is of the wrong type
            return nil
        }
        self.addSubview(contentView)     // 4
        contentView.translatesAutoresizingMaskIntoConstraints = false   // 5 
        contentView.layoutAttachAll(to: self)   // 6 
        return contentView   // 7
    }
}
  1. Menggunakan nilai balik yang dapat dibuang karena tampilan yang dikembalikan sebagian besar tidak menarik bagi penelepon ketika semua outlet sudah terhubung.
  2. Ini adalah metode umum yang mengembalikan objek opsional tipe UIView. Jika gagal memuat tampilan, mengembalikan nihil.
  3. Mencoba memuat file XIB dengan nama yang sama dengan instance kelas saat ini. Jika gagal, nol dikembalikan.
  4. Menambahkan tampilan tingkat atas ke hierarki tampilan.
  5. Baris ini menganggap kami menggunakan batasan untuk menata tampilan.
  6. Metode ini menambahkan kendala atas, bawah, depan & belakang - melampirkan tampilan ke "diri" di semua sisi (Lihat: https://stackoverflow.com/a/46279424/2274829 untuk detail)
  7. Mengembalikan tampilan tingkat atas

Dan metode penelepon mungkin terlihat seperti ini:

final class SomeView: UIView {   // 1.
   required init?(coder aDecoder: NSCoder) {   // 2 - storyboard initializer
      super.init(coder: aDecoder)
      fromNib()   // 5.
   }
   init() {   // 3 - programmatic initializer
      super.init(frame: CGRect.zero)  // 4.
      fromNib()  // 6.
   }
   // other methods ...
}
  1. SomeClass adalah subkelas UIView yang memuat kontennya dari file SomeClass.xib. Kata kunci "final" adalah opsional.
  2. Inisialisasi untuk ketika tampilan digunakan dalam storyboard (ingat untuk menggunakan SomeClass sebagai kelas kustom dari tampilan storyboard Anda).
  3. Penginisialisasi ketika tampilan dibuat secara terprogram (yaitu: "let myView = SomeView ()").
  4. Menggunakan bingkai all-nol sejak tampilan ini ditata menggunakan tata-letak otomatis. Perhatikan bahwa metode "init (frame: CGRect) {..}" tidak dibuat secara independen, karena tata letak otomatis digunakan secara eksklusif dalam proyek kami.
  5. & 6. Memuat file xib menggunakan ekstensi.

Kredit: Menggunakan ekstensi generik dalam solusi ini terinspirasi oleh jawaban Robert di bawah ini.

Edit Mengubah "view" menjadi "contentView" untuk menghindari kebingungan. Juga mengubah subscript array menjadi ".first".


7
Mengatur nama kelas untuk File's Ownermencapai sasaran ... Terima kasih!
Aviel Gross

13
UIView tidak memiliki tampilan properti, jadi memanggil self.view menyebabkan kesalahan
Nastya Gorban

4
@NastyaGorban self.view sebenarnya merujuk dalam kasus ini ke properti outlet (bernama "view) yang GK100 ditautkan dari tampilan tingkat atas di .xib ke SomeView.swift. Tidak menambahkan outlet itu akan memberi Anda kesalahan karena tidak ada tampilan" "properti di kelas NSView seperti yang Anda katakan.
Ausiàs

3
Saya mendapatkan crash saat memuat nib (loadNibNamed). Menggunakan Xcode 6.3 dan Swift
karthikPrabhu Alagu

11
panggilan fromNib()dari dalam init(coder aDecoder: NSCoder)menciptakan loop tak terbatas saat memuat Nib di dalam fromNib()metode membuat panggilan ke:init(coder aDecoder: NSCoder)
Matthew Cawley

334

Kontribusi saya:

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Kemudian panggil seperti ini:

let myCustomView: CustomView = UIView.fromNib()

..atau bahkan:

let myCustomView: CustomView = .fromNib()

20
Jawaban terbaik sejauh ini.
CodyMace

7
Jawaban terbaik di sini. Clean and Simple
Marquavious Draggon

3
@YuchenZhong - Saya lebih suka [0] lebih dari. Pertama karena akan mengembalikan opsional. Jika Anda memaksa membuka bungkusnya, itu tidak akan lebih aman. ... dan ini menimbulkan pertanyaan: Mengapa tidak mengembalikan opsional seperti beberapa solusi di atas? Jawab: Kamu bisa. Tidak ada yang salah dengan itu. Tapi ... jika itu akan kembali nihil, nama xib / kelas tidak cocok. Ini adalah kesalahan pengembang dan harus segera ditangkap dan tidak pernah diproduksi. Di sini saya lebih suka memiliki aplikasi crash daripada meninggalkannya dalam keadaan aneh. Hanya 2 sen / preferensi saya.
Robert Gummesson

1
@allenlinli - Metode ini merupakan ekstensi statis dari UIView seperti yang seharusnya untuk CustomView. Ia bekerja karena kompiler menyimpulkan tipe menggunakan anotasi tipe eksplisit. Karena CustomView adalah subkelas dari UIView dan jenisnya telah disimpulkan, kita tidak perlu menyimpulkannya lagi, karena itu UIView dapat dihilangkan seperti ditunjukkan pada contoh kedua saya. Karena itu, Anda jelas dapat membuat panggilan dengan cara Anda meletakkannya juga.
Robert Gummesson

3
Solusi ini tidak berfungsi untuk saya ketika ada tampilan kustom di dalam .xib. Saya akan menyarankan untuk memperbaiki bagian ini ke: return Bundle.main.loadNibNamed (String (menggambarkan: diri), pemilik: nil, opsi: nil)! [0] sebagai! T
Nadzeya

79

Sekarang dapat kembali -> Selfdengan cepat membantu menyederhanakan ini sedikit. Terakhir dikonfirmasi pada Swift 5.

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

Jika .xibfile dan subclass Anda memiliki nama yang sama, Anda dapat menggunakan:

let view = CustomView.fromNib()

Jika Anda memiliki nama khusus, gunakan:

let view = CustomView.fromNib(named: "special-case")

CATATAN:

Jika Anda mendapatkan kesalahan "tampilan jenis YourType tidak ditemukan .." maka Anda belum menetapkan kelas tampilan di .xib file

Pilih tampilan Anda di .xibfile, dan tekan cmd + opt + 4dan di classinput, masukkan kelas Anda


1
Saya tidak bisa menjalankan ini di bawah XCode 7.1 beta 3 - tidak yakin apakah ini beta tetapi pada dasarnya saya sudah mencoba segala cara untuk membuat tampilan kustom langsung dari nib di Swift dan saya selalu mendapatkan hasil yang sama: kelas yang dibuatnya tidak mematuhi KVC dengan outlet. Tidak yakin apakah itu sesuatu yang saya lakukan salah tetapi kelas saya cukup sederhana dan Pemilik File sudah benar. Saya biasa melakukan ini sepanjang waktu di bawah Objective-C.
Eselon

1
@Logan itu tidak benar-benar terkait dengan kode Anda, tetapi pandangan khusus imo harus mendukung pemuatan dari Storyboard / XIB. Komentar saya hanya pemberitahuan untuk mereka yang ingin membuat pandangan seperti itu
Nikita Took

1
Catatan Saya masih memiliki masalah menggunakan bentuk kedua memanggil fungsi ini, yaitu let myCustomView = UIView.fromNib() as? CustomView. Dalam hal ini, T.selfputuskan ke UIViewalih-alih CustomViewdan gagal menemukan nib. Saya tidak yakin mengapa ini - mungkin tipe yang disimpulkan untuk letfungsi yang disebut sebagai UIView?
Eselon

2
Penting untuk menunjukkan bahwa mencoba menggunakan Pemilik File untuk menghubungkan outlet (seperti yang kita lakukan di masa lalu) akan menyebabkan ini macet. Di IB, Pemilik File harus nihil / kosong dan outlet harus terhubung ke tampilan sebagai gantinya.
Eselon

1
@Echelon kamu menyelamatkan hariku !!! Saya menghubungkan outlet saya menggunakan Pemilik File dan itu tidak berfungsi, menggunakan tampilan yang berfungsi.
Jan Schlorf

19

coba kode berikut.

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView

Edit:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

    init()  {
        super.init()
       self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
    }


}

Saya perlu memanggil metode ini di dalam metode inisialisasi objek yang init () di Swift.
Bagusflyer

12

Ekstensi Protokol 4 Swift

public protocol NibInstantiatable {

    static func nibName() -> String

}

extension NibInstantiatable {

    static func nibName() -> String {
        return String(describing: self)
    }

}

extension NibInstantiatable where Self: UIView {

    static func fromNib() -> Self {

        let bundle = Bundle(for: self)
        let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)

        return nib!.first as! Self

    }

}

Adopsi

class MyView: UIView, NibInstantiatable {

}

Implementasi ini mengasumsikan bahwa Nib memiliki nama yang sama dengan kelas UIView. Ex. MyView.xib. Anda dapat memodifikasi perilaku ini dengan menerapkan nibName () di MyView untuk mengembalikan nama yang berbeda dari implementasi ekstensi protokol default.

Di xib pemilik file adalah MyView dan kelas tampilan root adalah MyView.

Pemakaian

let view = MyView.fromNib()

3
Sejauh ini ini adalah solusi yang paling elegan dan langsung dan saya tidak tahu mengapa itu bukan jawaban yang diterima!
horseshoe7

@ horseshoe7 karena ini ditulis 4 tahun setelah pertanyaan.
Freeubi

11

Saya mencapai ini dengan Swift dengan kode berikut:

class Dialog: UIView {
    @IBOutlet var view:UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.frame = UIScreen.mainScreen().bounds
        NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
        self.view.frame = UIScreen.mainScreen().bounds
        self.addSubview(self.view)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Jangan lupa menghubungkan outlet tampilan XIB Anda untuk melihatnya gerai didefinisikan dalam cepat. Anda juga dapat mengatur Responder Pertama ke nama kelas khusus Anda untuk mulai menghubungkan outlet tambahan apa pun.

Semoga ini membantu!


10

Diuji dalam Xcode 7 beta 4, Swift 2.0 dan iOS9 SDK. Kode berikut akan menetapkan xib ke uiview. Anda bisa dapat menggunakan tampilan xib khusus ini di storyboard dan mengakses objek IBOutlet juga.

import UIKit

@IBDesignable class SimpleCustomView:UIView
{
    var view:UIView!;

    @IBOutlet weak var lblTitle: UILabel!

   @IBInspectable var lblTitleText : String?
        {
        get{
            return lblTitle.text;
        }
        set(lblTitleText)
        {
            lblTitle.text = lblTitleText!;
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        loadViewFromNib ()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib ()
    }
    func loadViewFromNib() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        self.addSubview(view);



    }


}

Akses customview secara programatik

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Kode sumber - https://github.com/karthikprabhuA/CustomXIBSwift


9

Jika Anda memiliki banyak tampilan khusus dalam proyek Anda, Anda dapat membuat kelas seperti UIViewFromNib

Cepat 2.3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(self.dynamicType)
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        loadViewFromNib()
    }

    //MARK:
    private func loadViewFromNib() {
        contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
        contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Cepat 3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(describing: type(of: self))
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        loadViewFromNib()
    }

    //MARK:
    func loadViewFromNib() {
        contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Dan di setiap kelas yang diwarisi dari UIViewFromNib, Anda juga dapat mengganti nibNameproperti jika .xibfile memiliki nama yang berbeda:

class MyCustomClass: UIViewFromNib {

}

8

Membangun solusi di atas.

Ini akan bekerja di semua bundel proyek dan tidak perlu obat generik saat memanggil fromNib ().

Cepat 2

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nil)
    }

    public class func fromNib(nibName: String?) -> Self {

        func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
            let bundle = NSBundle(forClass: T.self)
            let name = nibName ?? String(T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName)
    }
}

Cepat 3

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nibName: nil)
    }

    public class func fromNib(nibName: String?) -> Self {
        func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
            let bundle = Bundle(for: T.self)
            let name = nibName ?? String(describing: T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName: nibName)
    }
}

Dapat digunakan seperti ini:

let someView = SomeView.fromNib()

Atau seperti ini:

let someView = SomeView.fromNib("SomeOtherNibFileName")

6

Cepat 4

Jangan lupa untuk menulis ".first as? CustomView".

if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {    
    self.view.addSubview(customView)
    }

Jika Anda ingin menggunakan di mana saja

Solusi Terbaik adalah jawaban Robert Gummesson .

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Kemudian panggil seperti ini:

let myCustomView: CustomView = UIView.fromNib()

5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]

Tetapi dalam init () dari Swift, tidak ada nilai yang dikembalikan. Saya lupa menyebutkan bahwa saya perlu memanggil loadNibNamed dalam inisialisasi UIView.
Bagusflyer

Apa maksudmu "tidak ada nilai balik"? selfsecara implisit dikembalikan dari semua initmetode ...
Grimxn

1
Yang saya maksud adalah saya memanggil loadNibNamed di dalam metode init. UIView yang dimuat ditugaskan ke self di ObjC. Tetapi dengan cepat, tidak.
Bagusflyer

5

Cara yang baik untuk melakukan ini dengan Swift adalah dengan menggunakan enum.

enum Views: String {
    case view1 = "View1" // Change View1 to be the name of your nib
    case view2 = "View2" // Change View2 to be the name of another nib

    func getView() -> UIView? {
        return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
    }
}

Kemudian dalam kode Anda, Anda dapat menggunakan:

let view = Views.view1.getView()

2
Perhatikan bahwa jika Anda melakukan ini dengan file nib kosong atau file nib tanpa node root UIView Anda akan crash karena Anda tidak waras memeriksa ukuran array atau elemen di posisi 0.
Matthew Cawley

4

Saya lebih suka solusi ini (berdasarkan jawaban jika @ GK100):

  1. Saya membuat XIB dan kelas bernama SomeView (menggunakan nama yang sama untuk kenyamanan dan keterbacaan). Saya mendasarkan keduanya pada UIView.
  2. Di XIB, saya mengubah kelas "File's Owner" ke SomeView (di inspektur identitas).
  3. Saya membuat outlet UIView di SomeView.swift, menautkannya ke tampilan tingkat atas dalam file XIB (menamakannya "tampilan" untuk kenyamanan). Saya kemudian menambahkan outlet lain ke kontrol lain di file XIB sesuai kebutuhan.
  4. Di SomeView.swift, saya memuat XIB di dalam initatau init:frame: CGRectinisialisasi. Tidak perlu menetapkan apa pun untuk "diri". Segera setelah XIB dimuat, semua outlet terhubung, termasuk tampilan tingkat atas. Satu-satunya hal yang hilang, adalah menambahkan tampilan atas ke hierarki tampilan:

    class SomeView: UIView {
      override init(frame: CGRect) {
        super.init(frame: frame)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
      required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
    
      ...
    }

saya lebih suka menggunakan init dengan bingkai jadi saya mencabut ini! satu hal yang perlu diperhatikan ... tambahkan self.view.frame = bingkai jika Anda ingin tampilan cocok dengan bingkai yang Anda lewati
Mike E

3

Jawaban Logan 3 versi cepat

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
            for nibView in nibViews {
                if let tog = nibView as? T {
                    view = tog
                }
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nib: UINib? {
        if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
            return UINib(nibName: nibName, bundle: nil)
        } else {
            return nil
        }
    }
}

3

Berikut ini adalah cara yang bersih dan deklaratif untuk memuat secara terprogram tampilan menggunakan protokol dan ekstensi protokol (Swift 4.2):

protocol XibLoadable {
    associatedtype CustomViewType
    static func loadFromXib() -> CustomViewType
}

extension XibLoadable where Self: UIView {
    static func loadFromXib() -> Self {
        let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
        guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
            // your app should crash if the xib doesn't exist
            preconditionFailure("Couldn't load xib for view: \(self)")
        }
        return customView
    }
}

Dan Anda dapat menggunakan ini seperti ini:

// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }

// and when you want to use it
let viewInstance = MyView.loadFromXib()

Beberapa pertimbangan tambahan :

  1. Pastikan file xib tampilan kustom Anda memiliki Custom Classset tampilan (dan outlet / tindakan ditetapkan dari sana), bukan milik Pemilik File.
  2. Anda dapat menggunakan protokol / ekstensi ini untuk tampilan kustom Anda atau internal. Anda mungkin ingin menggunakannya secara internal jika Anda memiliki beberapa pekerjaan pengaturan lain saat menginisialisasi tampilan Anda.
  3. Kelas tampilan khusus dan file xib Anda harus memiliki nama yang sama.

2

Saya hanya melakukan ini:

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

Sampel ini menggunakan tampilan pertama di nib "MyView.xib" di bundel utama. Tetapi Anda dapat memvariasikan indeks, nama pena, atau bundel (utama secara default).

Saya dulu membangunkan pandangan ke metode view init atau membuat metode generik seperti pada solusi di atas (yang cerdas dengan cara), tapi saya tidak melakukannya lagi.

Dengan cara ini saya bisa menggunakan tata letak atau sifat yang berbeda sambil menjaga logika dan kode tampilan yang sama.

Saya menemukan lebih mudah untuk membiarkan objek pabrik (biasanya viewController yang akan menggunakan tampilan) membuatnya sesuai kebutuhan. Terkadang Anda membutuhkan pemilik (Biasanya ketika tampilan yang dibuat terhubung ke pembuat), terkadang tidak ..

Mungkin itulah sebabnya Apple tidak memasukkan initFromNibmetode di kelas UIView-nya ...

Untuk mengambil contoh tingkat dasar, Anda tidak tahu bagaimana Anda dilahirkan. Anda baru saja dilahirkan. Begitu juga pandangannya;)


2

Yang harus Anda lakukan adalah memanggil metode init di komputer Anda UIView kelas .

Lakukan seperti itu:

class className: UIView {

    @IBOutlet var view: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    func setup() {
        UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
        addSubview(view)
        view.frame = self.bounds
    }
}

Sekarang, jika Anda ingin menambahkan tampilan ini sebagai sub tampilan dalam view controller, lakukan seperti itu dalam file view controller.swift:

self.view.addSubview(className())

itu jawaban yang bagus tetapi ada sesuatu yang salah, saya akan mengeditnya.
C0mrade

Ini adalah cara saya diimplementasikan. Tapi Anda bisa berimprovisasi. Terima kasih sebelumnya @ C0mrade
Alap Anerao

1

Mirip dengan beberapa jawaban di atas tetapi ekstensi Swift3 UIView yang lebih konsisten:

extension UIView {
    class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
        let bundle = bundle ?? Bundle.main
        let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
        return nibViews?.first as? A
    }

    class func fromNib<T: UIView>() -> T? {
        return fromNib(nibName: String(describing: T.self), bundle: nil)
    }
}

Yang memberikan kenyamanan untuk dapat memuat kelas dari nib bernama sendiri tetapi juga dari nibs / bundel lainnya.


1

Anda dapat melakukan ini melalui storyboard, cukup tambahkan batasan yang layak untuk dilihat. Anda dapat melakukan ini dengan mudah dengan mensubklasifikasikan setiap tampilan dari milik Anda sendiri katakanlah BaseView:

Objektif-C

BaseView.h


/*!
 @class BaseView
 @discussion Base View for getting view from xibFile
 @availability ios7 and later
 */
@interface BaseView : UIView

@end


BaseView.m


#import "BaseView.h"

@implementation BaseView

#pragma mark - Public

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - LifeCycle

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - Private

- (void)prepareView
{
    NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    UIView *view = [nibsArray firstObject];

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];
    [self addConstraintsForView:view];
}

#pragma mark - Add constraints

- (void)addConstraintsForView:(UIView *)view
{
    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeBottom
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeBottom
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeTop
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeTop
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeLeft
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeLeft
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeRight
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeRight
                                                       multiplier:1.0
                                                         constant:0]
                           ]];
}

@end

Cepat 4

import UIKit

class BaseView : UIView {

    // MARK: - LifeCycle

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        prepareView()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        prepareView()
    }

    internal class func xibName() -> String {
        return String(describing: self)
    }

    // MARK: - Private
    fileprivate func prepareView() {
        let nameForXib = BaseView.xibName()
        let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
        if let view = nibs?.first as? UIView {
            view.backgroundColor = UIColor.clear
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubviewWithConstraints(view, offset: false)
        }
    }
}

UIView+Subview


public extension UIView {
    // MARK: - UIView+Extensions

    public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
        subview.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "subview" : subview
        ]
        addSubview(subview)

        var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
        constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activate(constraints)
    }
}

Saya menyediakan 2 varian cara menambahkan kendala - yang umum dan dalam bahasa format visual - pilih yang Anda inginkan :)

Juga, secara default diasumsikan xibnama itu memiliki nama yang sama dengan nama kelas implementasi. Jika tidak - ubah sajaxibName parameter.

Jika Anda mensubklasifikasikan tampilan dari BaseView- Anda dapat dengan mudah menempatkan tampilan apa pun dan menentukan kelas dalam IB.


1
class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}

0

Versi yang lebih kuat berdasarkan jawaban Logan

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
            if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
                view = tog
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nibIndex: Int {
        return 0
    }

    public class var nibBundle: Bundle {
        return Bundle.main
    }
}

Dan Anda bisa menggunakan like

class BaseView: UIView {
    override class var nibName: String { return "BaseView" }
    weak var delegate: StandardStateViewDelegate?
}

class ChildView: BaseView {
    override class var nibIndex: Int { return 1 }
}

0

Implementasi yang paling nyaman. Di sini Anda memerlukan dua metode, untuk kembali langsung ke objek kelas Anda, bukan UIView.

  1. viewId ditandai sebagai kelas , memungkinkan override
  2. .Xib Anda dapat berisi lebih dari satu tampilan tingkat atas, situasi ini juga ditangani dengan benar.

extension UIView {

class var viewId: String {
    return String(describing: self)
}

static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
                    owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {

    return instancePrivate(from: bundle ?? Bundle.main,
                           nibName: nibName ?? viewId,
                           owner: owner,
                           options: options)
}

private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
                                              owner: Any?, options: [AnyHashable : Any]?) -> T? {

    guard
        let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
        let view = views.first(where: { $0 is T }) as? T else { return nil }

    return view
}
}

Contoh:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true

0

Jika Anda ingin subkelas Swift UIView sepenuhnya mandiri, dan memiliki kemampuan untuk dipakai menggunakan init atau init (frame :) tanpa memaparkan detail implementasi menggunakan Nib, maka Anda dapat menggunakan ekstensi protokol untuk mencapai ini. Solusi ini menghindari hierarki UIView yang bersarang seperti yang disarankan oleh banyak solusi lainnya.

public class CustomView: UIView {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!

    public convenience init() {
        self.init(frame: CGRect.zero)
    }

    public override convenience init(frame: CGRect) {
        self.init(internal: nil)
        self.frame = frame
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    fileprivate func commonInit() {
    }
}

fileprivate protocol _CustomView {
}

extension CustomView: _CustomView {
}

fileprivate extension _CustomView {

    // Protocol extension initializer - has the ability to assign to self, unlike
    // class initializers. Note that the name of this initializer can be anything
    // you like, here we've called it init(internal:)

    init(internal: Int?) {
        self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
    }
}

0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)

Kode hanya jawaban yang tidak disarankan. Harap tambahkan beberapa penjelasan tentang bagaimana ini menyelesaikan masalah, atau bagaimana ini berbeda dari jawaban yang ada. Dari Ulasan
Nick

0

Saya lebih suka ekstensi di bawah ini

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

Perbedaan antara ini dan ekstensi jawab teratas adalah Anda tidak perlu menyimpannya sebagai konstanta atau variabel.

class TitleView: UIView { }

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

self.navigationItem.titleView = TitleView.instanceFromNib

Versi Xcode apa yang Anda gunakan? Pastikan Anda menggunakan XCode versi terbaru. Bekerja dengan baik untuk saya dengan XCode 11.5 (versi terbaru sesuai tanggal).
iCoder

0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.