Cara menginisialisasi / membuat instance kelas UIView khusus dengan file XIB di Swift

139

Saya memiliki kelas yang disebut MyClasssubclass UIView, yang ingin saya inisialisasi dengan XIBfile. Saya tidak yakin bagaimana menginisialisasi kelas ini dengan file xib bernamaView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Stephen Fox
sumber
5
lihat contoh kode sumber lengkap untuk iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift dan utas terkait stackoverflow.com/questions/24857986/…
karthikPrabhu Alagu

Jawaban:

266

Saya menguji kode ini dan berfungsi dengan baik:

class MyClass: UIView {        
    class func instanceFromNib() -> UIView {
        return UINib(nibName: "nib file name", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
    }    
}

Inisialisasi tampilan dan gunakan seperti di bawah ini:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

ATAU

var view = MyClass.instanceFromNib
self.view.addSubview(view())

UPDATE Swift> = 3.x & Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Ezimet
sumber
1
Seharusnya var view = MyClass.instanceFromNib()& self.view.addSubview(view)sebagai kebalikan dari var view = MyClass.instanceFromNib& self.view.addSubview(view()). Hanya saran kecil untuk meningkatkan jawabannya :)
Logan
1
Dalam tampilan kasus saya diinisialisasi nanti! jika itu self.view.addSubview (view), view harus var view = MyClass.instanceFromNib ()
Ezimet
2
@ Ezimet bagaimana dengan IBActions di dalam tampilan itu. tempat menanganinya. Seperti saya memiliki tombol dalam pandangan saya (xib) bagaimana menangani acara klik IBAction tombol itu.?
Qadir Hussain
6
Haruskah mengembalikan "MyClass" bukan hanya UIView?
Kesong Xie
2
IBoutlets tidak bekerja dalam pendekatan ini ... Saya mendapatkan: "kelas ini tidak sesuai dengan kode kunci untuk kunci"
Radek Wilczak
82

Solusi Sam sudah bagus, meskipun tidak memperhitungkan bundel yang berbeda (NSBundle: forClass datang untuk menyelamatkan) dan membutuhkan pemuatan manual, alias mengetik kode.

Jika Anda ingin dukungan penuh untuk Outlet Xib Anda, Bundel yang berbeda (digunakan dalam kerangka kerja!) Dan dapatkan pratinjau yang bagus di Storyboard coba ini:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Gunakan xib Anda seperti biasa, yaitu hubungkan Outlet ke Pemilik File dan setel kelas Pemilik File ke kelas Anda sendiri.

Penggunaan: Cukup subkelas kelas Tampilan Anda sendiri dari NibLoadingView & Atur nama kelas menjadi Pemilik File di file Xib

Tidak diperlukan kode tambahan lagi.

Kredit di mana kredit jatuh tempo: Diperbaiki dengan perubahan kecil dari DenHeadless di GH. My Gist: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Frederik Winkelsdorf
sumber
8
Solusi ini mengerem koneksi outlet (karena menambahkan tampilan yang dimuat sebagai subview) dan menelepon nibSetupdari init?(coder:)akan menyebabkan rekursi tak terbatas jika menanamkan NibLoadingViewdalam XIB.
redent84
3
@ redent84 Terima kasih atas komentar Anda dan downvote. Jika Anda memiliki tampilan kedua, ini harus menggantikan SubView sebelumnya (tidak ada variabel instance baru yang diberikan). Anda benar tentang rekursi tak terbatas, yang harus dihilangkan jika berjuang dengan IB.
Frederik Winkelsdorf
1
Seperti disebutkan "hubungkan Outlet ke Pemilik File dan setel kelas Pemilik File ke kelas Anda sendiri." sambungkan outlet ke Pemilik File
Yusuf X
1
Saya selalu merasa tidak nyaman menggunakan metode ini untuk memuat tampilan dari xib. Kami pada dasarnya menambahkan pandangan yang merupakan subkelas dari Kelas A, dalam pandangan yang juga merupakan subkelas dari Kelas A. Apakah tidak ada cara untuk mencegah iterasi ini?
Prajeet Shrestha
1
@PrajeetShrestha Itu kemungkinan karena nibSetup () mengesampingkan warna Background ke .clearColor()- setelah memuatnya dari Storyboard. Tetapi itu harus bekerja jika Anda melakukannya dengan kode setelah itu dipakai. Bagaimanapun, seperti yang dikatakan pendekatan yang lebih elegan adalah yang berbasis protokol. Jadi yakin, inilah tautan untuk Anda: github.com/AliSoftware/Reusable . Saya menggunakan pendekatan serupa sekarang mengenai UITableViewCells (yang saya implementasikan sebelum saya menemukan proyek yang sangat berguna). hth!
Frederik Winkelsdorf
77

Pada Swift 2.0, Anda dapat menambahkan ekstensi protokol. Menurut pendapat saya, ini adalah pendekatan yang lebih baik karena tipe kembalinya Selflebih daripada UIView, jadi penelepon tidak perlu dilemparkan ke kelas tampilan.

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Sam
sumber
4
Ini adalah solusi yang lebih baik daripada jawaban yang dipilih karena tidak ada kebutuhan untuk casting dan juga akan dapat digunakan kembali di seluruh subkelas UIView lainnya yang Anda buat di masa depan.
user3344977
3
Saya mencoba ini dengan Swift 2.1 dan Xcode 7.2.1. Itu bekerja beberapa waktu dan akan menutup telepon pada orang lain dengan kunci mutex. Dua baris terakhir yang digunakan secara langsung dalam kode bekerja setiap kali dengan baris terakhir diubah menjadivar myView = nib.instantiate... as! myViewType
Paul Linsay
@ jr-root-cs Hasil edit Anda mengandung kesalahan ketik, saya harus mengembalikannya. Lagi pula tolong jangan menambahkan kode ke jawaban yang ada. Sebaliknya, buat komentar; atau tambahkan versi Anda dalam jawaban Anda sendiri . Terima kasih.
Eric Aya
Saya memposting kode yang telah saya buka dan uji dalam proyek saya menggunakan Swift 3(XCode 8.0 beta 6) tanpa masalah. Ketik itu di Swift 2. Mengapa itu harus menjadi jawaban lain ketika jawaban ini bagus dan pengguna mungkin akan suka mencari apa saja perubahan ketika mereka akan menggunakan XC8
jr.root.cs
1
@ jr.root.cs Ya jawaban ini bagus, itu sebabnya tidak ada yang harus mengubahnya. Ini jawaban Sam, bukan jawabanmu. Jika Anda ingin mengomentarinya, tinggalkan komentar; jika Anda ingin memposting versi baru / diperbarui, lakukan di posting Anda sendiri . Pengeditan dimaksudkan untuk memperbaiki kesalahan ketik / lekukan / tag agar tidak menambahkan versi Anda di pos orang lain. Terima kasih.
Eric Aya
37

Dan ini adalah jawaban Frederik on Swift 3.0

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}
Mahmood S
sumber
31

Cara universal memuat tampilan dari xib:

Contoh:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

Penerapan:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}
Максим Мартынов
sumber
Jawaban terbaik untuk saya sebagai tampilan output sebagai Jenis subclass UIView
jfgrang
26

Swift 3 Jawab: Dalam kasus saya, saya ingin memiliki outlet di kelas khusus yang dapat saya modifikasi:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

Ketika di .xib, pastikan bahwa bidang Kelas Kustom adalah MyClassView. Jangan repot-repot dengan Pemilik File.

Pastikan Kelas Kustom adalah MyClassView

Selain itu, pastikan Anda menghubungkan outlet di MyClassView ke label: Outlet untuk myLabel

Untuk instantiate:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Jeremy
sumber
Saya kembali "memuat nib" MyClas "tetapi tampilan outlet tidak disetel. '" Jika pemilik tidak disetel
jcharch
22

Cepat 4

Di sini, dalam kasus saya, saya harus meneruskan data ke tampilan kustom itu, jadi saya membuat fungsi statis untuk membuat tampilan.

  1. Buat ekstensi UIView

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

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
  3. Setel kelas khusus ke MyCustomView dalam file .xib. Hubungkan outlet jika perlu seperti biasa. masukkan deskripsi gambar di sini

  4. Tampilan Instantiate

    let view = MyCustomView.instantiate(message: "Hello World.")
mnemonic23
sumber
jika ada tombol dalam tampilan khusus, bagaimana kita dapat menangani aksi mereka dalam pengontrol tampilan yang berbeda?
jayant rawat
Anda dapat menggunakan delegasi protokol. lihat di sini stackoverflow.com/questions/29602612/… .
mnemonic23
Gagal memuat gambar dalam xib, mendapatkan kesalahan berikut. Tidak dapat memuat gambar " IBBrokenImage " yang dirujuk dari ujung di bundel dengan pengidentifikasi "tes. Pengujian"
Ravi Raja Jangid
-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

class func instanceFromNib() -> LAAlertView {
    return UINib(nibName: "LAAlertView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! LAAlertView
}

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}
Madan Kumawat
sumber
kode yang diformat buruk, tidak ada penjelasan, maka downvote.
J. Doe
Apa hubungannya semua ini dengan pertanyaan?
Ashley Mills