Initialisieren / Instanziieren einer benutzerdefinierten UIView-Klasse mit einer XIB-Datei in Swift

139

Ich habe eine Klasse namens, MyClassdie eine Unterklasse von ist UIView, die ich mit einer XIBDatei initialisieren möchte . Ich bin nicht sicher, wie ich diese Klasse mit der aufgerufenen xib-Datei initialisieren sollView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Stephen Fox
quelle
5
Siehe vollständiges Quellcodebeispiel für iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift und verwandten Thread stackoverflow.com/questions/24857986/…
karthikPrabhu Alagu

Antworten:

266

Ich habe diesen Code getestet und er funktioniert hervorragend:

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

Initialisieren Sie die Ansicht und verwenden Sie sie wie folgt:

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

ODER

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
quelle
1
Es sollte var view = MyClass.instanceFromNib()& self.view.addSubview(view)im Gegensatz zu var view = MyClass.instanceFromNib& sein self.view.addSubview(view()). Nur ein kleiner Vorschlag, um die Antwort zu verbessern :)
Logan
1
In meinem Fall Ansicht später initialisiert! Wenn es self.view.addSubview (view) ist, sollte view var view = MyClass.instanceFromNib () sein
Ezimet
2
@Ezimet was ist mit den IBActions in dieser Ansicht. wo man damit umgeht. Wie ich eine Schaltfläche in meiner Ansicht habe (xib), wie mit dem IBAction-Klickereignis dieser Schaltfläche umzugehen ist.
Qadir Hussain
6
Sollte "MyClass" anstelle von UIView zurückgegeben werden?
Kesong Xie
2
IBoutlets funktionieren nicht in diesem Ansatz ... Ich bekomme: "Diese Klasse ist nicht Schlüsselwertcodierungs-kompatibel für den Schlüssel"
Radek Wilczak
82

Sams Lösung ist bereits großartig, obwohl sie keine unterschiedlichen Bundles berücksichtigt (NSBundle: forClass hilft) und erfordert manuelles Laden, auch bekannt als Tippcode.

Wenn Sie volle Unterstützung für Ihre Xib-Outlets, verschiedene Bundles (Verwendung in Frameworks!) Und eine schöne Vorschau im Storyboard erhalten möchten, versuchen Sie Folgendes:

// 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
    }

}

Verwenden Sie Ihre xib wie gewohnt, dh verbinden Sie Outlets mit File Owner und setzen Sie File File Owner auf Ihre eigene Klasse.

Verwendung: Unterklassen Sie einfach Ihre eigene View-Klasse in NibLoadingView und setzen Sie den Klassennamen in der Xib-Datei auf File's Owner

Kein zusätzlicher Code mehr erforderlich.

Gutschriften, bei denen Gutschriften fällig sind: Mit geringfügigen Änderungen von DenHeadless auf GH. Mein Kern: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Frederik Winkelsdorf
quelle
8
Diese Lösung bremst Auslassverbindungen (da sie die geladene Ansicht als Unteransicht hinzufügt) und das Aufrufen nibSetupvon init?(coder:)führt beim Einbetten NibLoadingViewin eine XIB zu einer unendlichen Rekursion .
Redent84
3
@ redent84 Danke für deinen Kommentar und die Abwertung. Wenn Sie einen zweiten Blick darauf werfen, sollte dieser die vorherige Unteransicht ersetzen (es wurde keine neue Instanzvariable angegeben). Sie haben Recht mit der unendlichen Rekursion, die weggelassen werden sollte, wenn Sie mit IB kämpfen.
Frederik Winkelsdorf
1
Wie bereits erwähnt "Verbinden Sie Outlets mit File Owner und setzen Sie File File Owner auf Ihre eigene Klasse." Verbinden Sie die Steckdosen mit dem Dateibesitzer
Yusuf X
1
Es ist mir immer unangenehm, diese Methode zum Laden der Ansicht von xib zu verwenden. Grundsätzlich fügen wir eine Ansicht hinzu, die eine Unterklasse der Klasse A ist, in einer Ansicht, die auch eine Unterklasse der Klasse A ist. Gibt es keine Möglichkeit, diese Iteration zu verhindern?
Prajeet Shrestha
1
@PrajeetShrestha Dies liegt wahrscheinlich daran, dass nibSetup () die Hintergrundfarbe überschreibt .clearColor()- nach dem Laden aus dem Storyboard. Aber es sollte funktionieren, wenn Sie es nach der Instanziierung per Code tun. Wie gesagt, ein noch eleganterer Ansatz ist der protokollbasierte. Hier ist also ein Link für Sie: github.com/AliSoftware/Reusable . Ich verwende jetzt einen ähnlichen Ansatz in Bezug auf die UITableViewCells (die ich implementiert habe, bevor ich dieses wirklich nützliche Projekt entdeckt habe). hth!
Frederik Winkelsdorf
77

Ab Swift 2.0 können Sie eine Protokollerweiterung hinzufügen. Meiner Meinung nach ist dies ein besserer Ansatz, da der Rückgabetyp Selfeher als ist UIViewund der Aufrufer nicht in die Ansichtsklasse umwandeln muss.

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
quelle
4
Dies ist eine bessere Lösung als die ausgewählte Antwort, da kein Casting erforderlich ist und sie auch für alle anderen UIView-Unterklassen wiederverwendet werden kann, die Sie in Zukunft erstellen.
user3344977
3
Ich habe dies mit Swift 2.1 und Xcode 7.2.1 versucht. Es funktionierte manchmal und legte bei anderen mit einem Mutex-Schloss unvorhersehbar auf. Die letzten beiden Zeilen, die direkt im Code verwendet wurden, funktionierten jedes Mal mit der letzten Zeile, die geändert wurdevar myView = nib.instantiate... as! myViewType
Paul Linsay
@ jr-root-cs Deine Bearbeitung enthielt Tippfehler / Fehler, ich musste sie zurücksetzen. Und bitte fügen Sie vorhandenen Antworten keinen Code hinzu. Machen Sie stattdessen einen Kommentar. oder fügen Sie Ihre Version in Ihre eigene Antwort ein . Vielen Dank.
Eric Aya
Ich habe Code gepostet, den ich in meinem Projekt mit Swift 3(XCode 8.0 Beta 6) ohne Probleme geöffnet und getestet hatte. Der Tippfehler war in Swift 2. Warum sollte es eine andere Antwort sein, wenn diese Antwort gut ist und Benutzer wahrscheinlich gerne suchen, was die Änderungen sind, wenn sie XC8 verwenden
jr.root.cs
1
@ jr.root.cs Ja diese Antwort ist gut, deshalb sollte niemand sie ändern. Dies ist Sams Antwort, nicht deine. Wenn Sie es kommentieren möchten, hinterlassen Sie einen Kommentar. Wenn Sie eine neue / aktualisierte Version veröffentlichen möchten, tun Sie dies in Ihrem eigenen Beitrag . Änderungen sollen Tippfehler / Einrückungen / Tags korrigieren, um Ihre Versionen nicht in die Beiträge anderer Personen aufzunehmen. Vielen Dank.
Eric Aya
37

Und das ist die Antwort von Frederik auf 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.
quelle
31

Universelle Methode zum Laden der Ansicht von xib:

Beispiel:

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

Implementierung:

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))
    }
}
Максим Мартынов
quelle
Beste Antwort auf mich als Ausgabeansicht als Typ der UIView-Unterklasse
jfgrang
26

Swift 3 Antwort: In meinem Fall wollte ich eine Steckdose in meiner benutzerdefinierten Klasse haben, die ich ändern kann:

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
    }
}

Stellen Sie in der .xib sicher, dass das Feld Benutzerdefinierte Klasse MyClassView ist. Kümmere dich nicht um den Besitzer der Datei.

Stellen Sie sicher, dass die benutzerdefinierte Klasse MyClassView ist

Stellen Sie außerdem sicher, dass Sie die Steckdose in MyClassView mit dem Etikett verbinden: Outlet für myLabel

Um es zu instanziieren:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Jeremy
quelle
Ich bekomme zurück "lud die" MyClas "Feder, aber der View Outlet wurde nicht eingestellt." "Wenn der Besitzer nicht eingestellt ist
jcharch
22

Swift 4

In meinem Fall muss ich hier Daten an diese benutzerdefinierte Ansicht übergeben, damit ich eine statische Funktion zum Instanziieren der Ansicht erstelle.

  1. Erstellen Sie eine UIView-Erweiterung

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. Erstellen Sie 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. Setzen Sie die benutzerdefinierte Klasse in der XIB-Datei auf MyCustomView. Schließen Sie die Steckdose bei Bedarf wie gewohnt an. Geben Sie hier die Bildbeschreibung ein

  4. Sofortansicht

    let view = MyCustomView.instantiate(message: "Hello World.")
Mnemonik23
quelle
Wenn Schaltflächen in der benutzerdefinierten Ansicht vorhanden sind, wie können wir ihre Aktionen in verschiedenen Ansichtssteuerungen verarbeiten?
Jayant Rawat
Sie können den Protokolldelegierten verwenden. Schauen Sie sich hier stackoverflow.com/questions/29602612/… an .
Mnemonik23
Es konnte kein Bild in xib geladen werden, und es wurde der folgende Fehler angezeigt. Das Bild " IBBrokenImage ", auf das von einer Schreibfeder im Bundle mit der Kennung "test.Testing" verwiesen wurde, konnte nicht geladen werden
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
quelle
schlecht formatierter Code, keine Erklärung, daher die Abwertung.
J. Doe
Was hat das alles mit der Frage zu tun?
Ashley Mills