Laden Sie eine UIView von der Feder in Swift

148

Hier ist mein Objective-C-Code, mit dem ich eine Schreibfeder für meine benutzerdefinierten Elemente lade UIView:

-(id)init{

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

}

Was ist der entsprechende Code in Swift?

Bagusflyer
quelle

Antworten:

172

Ursprüngliche Lösung

  1. Ich habe eine XIB und eine Klasse namens SomeView erstellt (aus Bequemlichkeits- und Lesbarkeitsgründen denselben Namen verwendet). Ich habe beide auf einem UIView basiert.
  2. In der XIB habe ich die Klasse "File's Owner" in SomeView (im Identitätsinspektor) geändert.
  3. Ich habe in SomeView.swift einen UIView-Ausgang erstellt, der mit der Ansicht der obersten Ebene in der XIB-Datei verknüpft ist (der Einfachheit halber "Ansicht" genannt). Ich habe dann nach Bedarf weitere Steuerelemente zu anderen Steuerelementen in der XIB-Datei hinzugefügt.
  4. In SomeView.swift habe ich die XIB in den Initialisierer "init with code" geladen. Es besteht keine Notwendigkeit, "sich selbst" etwas zuzuweisen. Sobald das XIB geladen ist, sind alle Steckdosen verbunden, einschließlich der Ansicht der obersten Ebene. Das einzige, was fehlt, ist das Hinzufügen der Draufsicht zur Ansichtshierarchie:

.

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

Beachten Sie, dass ich auf diese Weise eine Klasse erhalte, die sich selbst von nib lädt. Ich könnte dann SomeView als Klasse verwenden, wenn UIView im Projekt verwendet werden könnte (im Interface Builder oder programmgesteuert).

Update - mit Swift 3-Syntax

Das Laden einer xib in der folgenden Erweiterung wird als Instanzmethode geschrieben, die dann von einem Initialisierer wie dem oben genannten verwendet werden kann:

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. Die Verwendung eines verwerfbaren Rückgabewerts, da die zurückgegebene Ansicht für den Anrufer meistens nicht von Interesse ist, wenn alle Steckdosen bereits verbunden sind.
  2. Dies ist eine generische Methode, die ein optionales Objekt vom Typ UIView zurückgibt. Wenn die Ansicht nicht geladen werden kann, wird null zurückgegeben.
  3. Versuch, eine XIB-Datei mit demselben Namen wie die aktuelle Klasseninstanz zu laden. Wenn dies fehlschlägt, wird nil zurückgegeben.
  4. Hinzufügen der Ansicht der obersten Ebene zur Ansichtshierarchie.
  5. In dieser Zeile wird davon ausgegangen, dass wir Einschränkungen verwenden, um die Ansicht zu gestalten.
  6. Diese Methode fügt obere, untere, führende und nachfolgende Einschränkungen hinzu - indem die Ansicht von allen Seiten an "self" angehängt wird ( Details siehe: https://stackoverflow.com/a/46279424/2274829 ).
  7. Rückgabe der Ansicht der obersten Ebene

Und die Aufrufermethode könnte folgendermaßen aussehen:

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 ist eine UIView-Unterklasse, die ihren Inhalt aus einer SomeClass.xib-Datei lädt. Das Schlüsselwort "final" ist optional.
  2. Ein Initialisierer für die Verwendung der Ansicht in einem Storyboard (denken Sie daran, SomeClass als benutzerdefinierte Klasse Ihrer Storyboard-Ansicht zu verwenden).
  3. Ein Initialisierer für das programmgesteuerte Erstellen der Ansicht (dh: "let myView = SomeView ()").
  4. Verwenden eines Rahmens mit Nullen, da diese Ansicht mithilfe des automatischen Layouts erstellt wird. Beachten Sie, dass eine "init (frame: CGRect) {..}" - Methode nicht unabhängig erstellt wird, da das automatische Layout ausschließlich in unserem Projekt verwendet wird.
  5. & 6. Laden der xib-Datei mit der Erweiterung.

Kredit: Die Verwendung einer generischen Erweiterung in dieser Lösung wurde von Roberts Antwort unten inspiriert.

Bearbeiten Ändern von "Ansicht" in "Inhaltsansicht", um Verwirrung zu vermeiden. Außerdem wurde der Array-Index in ".first" geändert.

GK100
quelle
7
Den Klassennamen so einstellen, dass er File's Ownergenau richtig ist ... Danke!
Aviel Gross
13
UIView hat keine Eigenschaftsansicht, daher verursacht das Aufrufen von self.view einen Fehler
Nastya Gorban
4
@NastyaGorban self.view bezieht sich in diesem Fall tatsächlich auf die Outlet-Eigenschaft (mit dem Namen "view"), die GK100 von der Ansicht der obersten Ebene in der .xib mit SomeView.swift verknüpft hat. Wenn Sie diesen Outlet nicht hinzufügen, erhalten Sie einen Fehler, da keine "Ansicht" vorhanden ist "Eigenschaft in NSView-Klassen, wie Sie sagen.
Ausiàs
3
Beim Laden der Feder (loadNibNamed) tritt ein Absturz auf. Verwenden von Xcode 6.3 und Swift
karthikPrabhu Alagu
10
Das Aufrufen fromNib()von innen init(coder aDecoder: NSCoder)erzeugt eine Endlosschleife, da beim Laden der Feder innerhalb der fromNib()Methode Folgendes aufgerufen wird:init(coder aDecoder: NSCoder)
Matthew Cawley
333

Mein Beitrag:

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

Dann nenne es so:

let myCustomView: CustomView = UIView.fromNib()

..oder auch:

let myCustomView: CustomView = .fromNib()
Robert Gummesson
quelle
20
Beste Antwort bei weitem.
CodyMace
7
Beste Antwort hier. Sauber und einfach
Marquavious Draggon
3
@YuchenZhong - Ich bevorzuge [0] gegenüber .first, da dies eine Option zurückgeben würde. Wenn Sie das Auspacken erzwingen, ist es nicht sicherer. ... und dies wirft die Frage auf: Warum nicht ein optionales als einige der oben genannten Lösungen zurückgeben? Antwort: Sie können. Daran ist nichts auszusetzen. Aber ... wenn es jemals null zurückgeben würde, stimmt der Name der xib / Klasse nicht überein. Dies ist ein Entwicklerfehler und sollte sofort abgefangen werden und niemals in die Produktion gelangen. Hier würde ich es vorziehen, wenn die App abstürzt, anstatt sie in einem seltsamen Zustand zu belassen. Nur meine 2 Cent / Präferenz.
Robert Gummesson
1
@allenlinli - Die Methode ist eine statische Erweiterung von UIView gemäß CustomView. Dies funktioniert, weil der Compiler den Typ mithilfe der expliziten Typanmerkung ableitet. Da CustomView eine Unterklasse von UIView ist und der Typ bereits abgeleitet wurde, müssen wir ihn nicht erneut ableiten. UIView kann daher weggelassen werden, wie in meinem zweiten Beispiel gezeigt. Wenn dies gesagt ist, können Sie den Anruf natürlich auch so tätigen, wie Sie ihn abgelegt haben.
Robert Gummesson
3
Diese Lösung funktionierte für mich nicht, wenn es in der .xib eine benutzerdefinierte Ansicht gab. Ich würde vorschlagen, diesen Teil zu reparieren, um: Bundle.main.loadNibNamed (String (Beschreibung: self), Eigentümer: nil, Optionen: nil) zurückzugeben! [0] as! T
Nadzeya
79

Die Möglichkeit, -> Selfschnell zurückzukehren, vereinfacht dies ein wenig. Zuletzt bestätigt auf 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
    }
}

Wenn Ihre .xibDatei und Unterklasse denselben Namen haben, können Sie Folgendes verwenden:

let view = CustomView.fromNib()

Wenn Sie einen benutzerdefinierten Namen haben, verwenden Sie:

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

HINWEIS:

Wenn die Fehlermeldung "Ansicht vom Typ YourType nicht gefunden in ..." angezeigt wird, haben Sie die Klasse der Ansicht in der .xibDatei nicht festgelegt

Wählen Sie Ihre Ansicht in der .xibDatei aus und drücken Sie cmd + opt + 4und geben Sie in der classEingabe Ihre Klasse ein

Logan
quelle
1
Ich kann dies unter XCode 7.1 Beta 3 nicht zum Laufen bringen - ich bin mir nicht sicher, ob es sich um eine Beta-Sache handelt, aber im Grunde habe ich jede Möglichkeit versucht, eine benutzerdefinierte Ansicht direkt von einer Schreibfeder in Swift zu erstellen, und ich erhalte immer das gleiche Ergebnis: die Klasse, die es erstellt ist nicht KVC-konform mit den Steckdosen. Ich bin mir nicht sicher, ob ich etwas falsch mache, aber meine Klasse ist ziemlich einfach und der Besitzer der Datei ist korrekt. Ich habe das die ganze Zeit unter Objective-C gemacht.
Echelon
1
@Logan es hat nicht wirklich mit Ihrem Code zu tun, aber imo benutzerdefinierte Ansichten sollten das Laden von Storyboard / XIB unterstützen. Mein Kommentar war nur eine Benachrichtigung für diejenigen, die solche Ansichten erstellen möchten
Nikita nahm
1
Hinweis Ich habe immer noch ein Problem mit der zweiten Form des Aufrufs dieser Funktion, nämlich let myCustomView = UIView.fromNib() as? CustomView. In diesem Fall T.selfbeschließt, UIViewstatt CustomViewund es nicht um die Spitze zu finden. Ich bin mir nicht sicher, warum dies so ist - vielleicht der abgeleitete Typ für das letMittel, als das die Funktion aufgerufen wird UIView?
Echelon
2
Es ist wichtig darauf hinzuweisen, dass der Versuch, File's Owner zum Anschließen der Steckdosen zu verwenden (wie wir es in den guten alten Tagen getan haben), zum Absturz führen wird. In IB muss der Eigentümer der Datei null / leer sein und die Ausgänge sollten stattdessen mit der Ansicht verbunden sein.
Echelon
1
@Echelon du hast meinen Tag gerettet !!! Ich habe meine Steckdosen mit dem Eigentümer der Datei verbunden und es hat nicht funktioniert. Stattdessen hat die Ansicht funktioniert.
Jan Schlorf
19

Versuchen Sie folgenden Code.

var uiview :UIView?

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

Bearbeiten:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

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


}
Jasmin
quelle
Ich muss diese Methode innerhalb der Objektinitialisierungsmethode aufrufen, die in Swift init () ist.
Bagusflyer
12

Swift 4-Protokollerweiterungen

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

    }

}

Annahme

class MyView: UIView, NibInstantiatable {

}

Diese Implementierung setzt voraus, dass die Nib denselben Namen wie die UIView-Klasse hat. Ex. MyView.xib. Sie können dieses Verhalten ändern, indem Sie nibName () in MyView implementieren, um einen anderen Namen als die Standardimplementierungserweiterung zurückzugeben.

In der xib ist der Dateieigentümer MyView und die Stammansichtsklasse MyView.

Verwendung

let view = MyView.fromNib()
Brody Robertson
quelle
3
Dies ist bei weitem die eleganteste und einfachste Lösung, und ich habe keine Ahnung, warum dies nicht die akzeptierte Antwort ist!
Hufeisen7
@ Horseshoe7, weil es 4 Jahre nach der Frage geschrieben wurde.
Freeubi
11

Ich habe dies mit Swift durch den folgenden Code erreicht:

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

Vergessen Sie nicht , Ihre XIB zu verbinden Ansicht Steckdose Ansicht Steckdose in schnellen definiert. Sie können First Responder auch auf Ihren benutzerdefinierten Klassennamen einstellen, um zusätzliche Steckdosen anzuschließen.

Hoffe das hilft!

Amro Shafie
quelle
10

Getestet in Xcode 7 Beta 4, Swift 2.0 und iOS9 SDK. Der folgende Code weist der uiview xib zu. Sie können diese benutzerdefinierte xib-Ansicht im Storyboard verwenden und auch auf das IBOutlet-Objekt zugreifen.

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);



    }


}

Greifen Sie programmgesteuert auf die benutzerdefinierte Ansicht zu

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

Quellcode - https://github.com/karthikprabhuA/CustomXIBSwift

karthikPrabhu Alagu
quelle
9

Wenn Sie viele benutzerdefinierte Ansichten in Ihrem Projekt haben, können Sie Klassen wie erstellen UIViewFromNib

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

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

Und in jeder Klasse, von der nur geerbt wird UIViewFromNib, können Sie auch die nibNameEigenschaft überschreiben, wenn die .xibDatei einen anderen Namen hat:

class MyCustomClass: UIViewFromNib {

}
ChikabuZ
quelle
8

Aufbauend auf den oben genannten Lösungen.

Dies funktioniert in allen Projektpaketen und erfordert keine Generika, wenn fromNib () aufgerufen wird.

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

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

Kann so verwendet werden:

let someView = SomeView.fromNib()

Oder so:

let someView = SomeView.fromNib("SomeOtherNibFileName")
Genesis
quelle
6

Swift 4

Vergessen Sie nicht, ".first as? CustomView" zu schreiben.

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

Wenn Sie überall verwenden möchten

Die beste Lösung ist die Antwort von 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
    }
}

Dann nenne es so:

let myCustomView: CustomView = UIView.fromNib()
Ali Ihsan URAL
quelle
5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
BaSha
quelle
In init () von Swift gibt es jedoch keinen zurückgegebenen Wert. Ich habe vergessen zu erwähnen, dass ich bei der Initialisierung eines UIView loadNibNamed aufrufen muss.
Bagusflyer
Was meinst du mit "kein Rückgabewert"? selfwird implizit von allen initMethoden zurückgegeben ...
Grimxn
1
Ich meine, ich rufe loadNibNamed innerhalb der init-Methode auf. Das geladene UIView wird in ObjC self zugewiesen. Aber schnell ist es nicht.
Bagusflyer
5

Eine gute Möglichkeit, dies mit Swift zu tun, ist die Verwendung einer Aufzählung.

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

Dann können Sie in Ihrem Code einfach Folgendes verwenden:

let view = Views.view1.getView()
totiG
quelle
2
Beachten Sie, dass, wenn Sie dies mit einer leeren NIB-Datei oder einer NIB-Datei mit einem Nicht-UIView-Stammknoten tun, Sie abstürzen, da Sie die Array-Größe oder das Element in Position 0 nicht ordnungsgemäß überprüfen.
Matthew Cawley
4

Ich bevorzuge diese Lösung (basierend auf der Antwort wenn @ GK100):

  1. Ich habe eine XIB und eine Klasse namens SomeView erstellt (aus Bequemlichkeits- und Lesbarkeitsgründen denselben Namen verwendet). Ich habe beide auf einem UIView basiert.
  2. In der XIB habe ich die Klasse "File's Owner" in SomeView (im Identitätsinspektor) geändert.
  3. Ich habe in SomeView.swift einen UIView-Ausgang erstellt, der mit der Ansicht der obersten Ebene in der XIB-Datei verknüpft ist (der Einfachheit halber "Ansicht" genannt). Ich habe dann nach Bedarf weitere Steuerelemente zu anderen Steuerelementen in der XIB-Datei hinzugefügt.
  4. In SomeView.swift habe ich die XIB in den Initialisierer initoder geladen init:frame: CGRect. Es besteht keine Notwendigkeit, "sich selbst" etwas zuzuweisen. Sobald das XIB geladen ist, sind alle Steckdosen verbunden, einschließlich der Ansicht der obersten Ebene. Das einzige, was fehlt, ist das Hinzufügen der Draufsicht zur Ansichtshierarchie:

    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
      }
    
    
      ...
    }
konfile
quelle
Ich bevorzuge es, Init mit Frame zu verwenden, also habe ich dies entwurzelt! eine Sache zu beachten ... add self.view.frame = frame, wenn die Ansicht mit dem Frame übereinstimmen soll, den Sie übergeben
Mike E
3

Swift 3- Version von Logans Antwort

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
        }
    }
}
Alexey Karetski
quelle
3

Hier ist eine saubere und deklarative Methode zum programmgesteuerten Laden einer Ansicht mithilfe eines Protokolls und einer Protokollerweiterung (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
    }
}

Und Sie können dies so verwenden:

// 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()

Einige zusätzliche Überlegungen :

  1. Stellen Sie sicher, dass in der xib-Datei Ihrer benutzerdefinierten Ansicht die Ansicht Custom Classfestgelegt ist (und die von dort festgelegten Ausgänge / Aktionen) und nicht die des Dateibesitzers .
  2. Sie können dieses Protokoll / diese Erweiterung außerhalb Ihrer benutzerdefinierten oder internen Ansicht verwenden. Möglicherweise möchten Sie es intern verwenden, wenn Sie beim Initialisieren Ihrer Ansicht andere Einrichtungsarbeiten ausführen.
  3. Ihre benutzerdefinierte Ansichtsklasse und Ihre XIB-Datei müssen denselben Namen haben.
Jason z
quelle
2

Ich mache einfach so:

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

In diesem Beispiel wird die erste Ansicht in der Schreibfeder "MyView.xib" im Hauptpaket verwendet. Sie können jedoch entweder den Index, den Nib-Namen oder das Bundle variieren (standardmäßig main).

Früher habe ich Ansichten in die View-Init-Methode geweckt oder generische Methoden wie in den obigen Lösungen erstellt (die übrigens intelligent sind), aber ich mache das nicht mehr.

Auf diese Weise kann ich verschiedene Layouts oder Merkmale verwenden und dabei die gleiche Ansichtslogik und den gleichen Code beibehalten.

Ich finde es einfacher, ein Factory-Objekt (normalerweise den viewController, der die Ansicht verwendet) so zu erstellen, wie es benötigt wird. Manchmal benötigen Sie einen Eigentümer (normalerweise, wenn die erstellte Ansicht eine mit dem Ersteller verbundene Steckdose hat), manchmal nicht.

Das ist wahrscheinlich der Grund, warum Apple keine initFromNibMethode in seine UIView-Klasse aufgenommen hat ...

Um ein Beispiel vom Boden zu nehmen, Sie wissen nicht, wie Sie geboren werden. Du bist gerade geboren. So sind die Ansichten;)

Elch
quelle
2

Sie müssen lediglich die init-Methode in Ihrer UIViewKlasse aufrufen .

Mach es so:

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

Wenn Sie diese Ansicht nun als Unteransicht in View Controller hinzufügen möchten, gehen Sie folgendermaßen in der Datei view controller.swift vor:

self.view.addSubview(className())
Alap Anerao
quelle
Es ist eine großartige Antwort, aber es stimmt etwas nicht. Ich werde es bearbeiten.
C0mrade
So habe ich es umgesetzt. Aber du kannst es improvisieren. Vielen Dank im Voraus @ C0mrade
Alap Anerao
1

Ähnlich wie einige der obigen Antworten, jedoch eine konsistentere Swift3 UIView-Erweiterung:

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

Dies bietet den Komfort, die Klasse von einer selbst benannten Feder, aber auch von anderen Federn / Bundles laden zu können.

Mark Johnson
quelle
1

Sie können dies über das Storyboard tun. Fügen Sie einfach die entsprechenden Einschränkungen für die Ansicht hinzu. Sie können dies einfach tun, indem Sie eine beliebige Ansicht aus Ihrer eigenen Unterklasse unterteilen. Sagen wir BaseView:

Ziel 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

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

Ich biete 2 Varianten an, wie man Einschränkungen hinzufügt - allgemeine und innerhalb der Sprache des visuellen Formats - wählen Sie eine beliebige aus :)

Außerdem wird standardmäßig davon ausgegangen, dass der xibName denselben Namen wie der Name der Implementierungsklasse hat. Wenn nein - einfach ändernxibName Parameter .

Wenn Sie Ihre Ansicht von unterklassifizieren, können BaseViewSie problemlos eine beliebige Ansicht einfügen und eine Klasse in IB angeben.

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

Leistungsstärkere Version basierend auf Logans Antwort

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

Und Sie können wie verwenden

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

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

Die bequemste Implementierung. Hier benötigen Sie zwei Methoden, um direkt zum Objekt Ihrer Klasse zurückzukehren, nicht zu UIView.

  1. viewId als Klasse markiert und ermöglicht das Überschreiben
  2. Ihre .xib kann mehr als eine Ansicht der obersten Ebene enthalten. Diese Situation wird auch korrekt behandelt.

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

Beispiel:

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

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

Wenn Sie möchten, dass die Swift UIView-Unterklasse vollständig in sich geschlossen ist und mit init oder init (frame :) instanziiert werden kann, ohne die Implementierungsdetails der Verwendung einer Nib offenzulegen, können Sie eine Protokollerweiterung verwenden, um dies zu erreichen. Diese Lösung vermeidet die verschachtelte UIView-Hierarchie, wie sie von vielen anderen Lösungen vorgeschlagen wird.

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;
    }
}
zu konfigurieren
quelle
0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)
Divesh singh
quelle
Nur-Code-Antworten werden nicht empfohlen. Bitte fügen Sie eine Erklärung hinzu, wie dies das Problem löst oder wie sich dies von den vorhandenen Antworten unterscheidet. Aus dem Rückblick
Nick
0

Ich bevorzuge die folgende Erweiterung

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

Der Unterschied zwischen dieser und der am häufigsten beantworteten Erweiterung besteht darin, dass Sie keine Konstante oder Variable speichern müssen.

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
iCoder
quelle
Welche Version von Xcode verwenden Sie? Bitte stellen Sie sicher, dass Sie die neueste Version von XCode verwenden. Funktioniert gut mit XCode 11.5 (neueste Version zum aktuellen Datum).
iCoder
0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Pratik
quelle