Laden Sie die Ansicht aus einer externen xib-Datei in das Storyboard

131

Ich möchte eine Ansicht in mehreren Viewcontrollern in einem Storyboard verwenden. Daher habe ich darüber nachgedacht, die Ansicht in einer externen xib zu entwerfen, damit Änderungen in jedem Ansichtscontroller berücksichtigt werden. Aber wie kann man eine Ansicht von einem externen xib in ein Storyboard laden und ist das überhaupt möglich? Wenn dies nicht der Fall ist, welche anderen Alternativen stehen zur Verfügung, um der obigen Situation zu entsprechen?

Sebastian Hoffmann
quelle

Antworten:

132

Mein vollständiges Beispiel ist hier , aber ich werde unten eine Zusammenfassung geben.

Layout

Fügen Sie Ihrem Projekt eine .swift- und eine .xib-Datei mit jeweils demselben Namen hinzu. Die .xib-Datei enthält Ihr benutzerdefiniertes Ansichtslayout (vorzugsweise unter Verwendung von Einschränkungen für das automatische Layout).

Machen Sie die schnelle Datei zum Eigentümer der xib-Datei.

Geben Sie hier die Bildbeschreibung ein Code

Fügen Sie der .swift-Datei den folgenden Code hinzu und verbinden Sie die Outlets und Aktionen mit der .xib-Datei.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

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

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Benutze es

Verwenden Sie Ihre benutzerdefinierte Ansicht an einer beliebigen Stelle in Ihrem Storyboard. Fügen Sie einfach ein hinzu UIViewund setzen Sie den Klassennamen auf Ihren benutzerdefinierten Klassennamen.

Geben Sie hier die Bildbeschreibung ein

Suragch
quelle
3
Ruft loadNibNamed nicht init (coder :) auf? Ich habe einen Absturz, der versucht, Ihren Ansatz anzupassen.
Fishman
@Fishman, wenn Sie versuchen, die Ansicht programmgesteuert (anstatt aus dem Storyboard) zu laden, stürzt sie ab, da sie derzeit keine hat init(frame:). Weitere Informationen finden Sie in diesem Tutorial .
Suragch
7
Eine weitere häufige Ursache für Abstürze ist, dass die benutzerdefinierte Ansicht nicht auf den Eigentümer der Datei festgelegt wird . Siehe den roten Kreis in meiner Antwort.
Suragch
5
Ja, ich hatte die Klasse der Stammansicht anstelle des Dateibesitzers festgelegt und es kam zu einer Endlosschleife.
Devios1
2
Nach dem Hinzufügen der Zeile view.autoresizingMask = [.flexibleWidth, .flexibleHeight] vor self.addSubview (view) funktioniert es einwandfrei.
Pramod Tapaniya
69

Für eine Weile war Christopher Swaseys Ansatz der beste, den ich gefunden hatte. Ich habe ein paar leitende Entwickler in meinem Team danach gefragt und einer von ihnen hatte die perfekte Lösung ! Es erfüllt alle Bedenken, die Christopher Swasey so eloquent angesprochen hat, und es erfordert keinen Code für die Unterklasse des Boilerplates (mein Hauptanliegen bei seinem Ansatz). Es gibt ein Gotcha , aber ansonsten ist es ziemlich intuitiv und einfach zu implementieren.

  1. Erstellen Sie eine benutzerdefinierte UIView-Klasse in einer .swift-Datei, um Ihre xib zu steuern. dhMyCustomClass.swift
  2. Erstellen Sie eine .xib-Datei und formatieren Sie sie nach Ihren Wünschen. dhMyCustomClass.xib
  3. Stellen Sie die File's Ownervon der .xib Datei Ihre benutzerdefinierte Klasse zu sein ( MyCustomClass)
  4. GOTCHA: Lassen Sie den classWert (unter identity Inspector) für Ihre benutzerdefinierte Ansicht in der .xib-Datei leer. Ihre benutzerdefinierte Ansicht hat also keine angegebene Klasse, aber den Eigentümer einer bestimmten Datei.
  5. Schließen Sie Ihre Steckdosen wie gewohnt an Assistant Editor.
    • HINWEIS: Wenn Sie sich das ansehen, werden Connections InspectorSie feststellen, dass Ihre Referenzierungs-Outlets nicht auf Ihre benutzerdefinierte Klasse (dh MyCustomClass) verweisen, sondern auf diese File's Owner. Da dies File's Ownerals Ihre benutzerdefinierte Klasse angegeben ist, werden die Steckdosen angeschlossen und funktionieren ordnungsgemäß.
  6. Stellen Sie sicher, dass Ihre benutzerdefinierte Klasse vor der Klassenanweisung @IBDesignable hat.
  7. Stellen Sie sicher, dass Ihre benutzerdefinierte Klasse dem NibLoadableunten angegebenen Protokoll entspricht.
    • ANMERKUNG: Wenn sich der .swiftName Ihrer benutzerdefinierten Klassendatei von Ihrem .xibDateinamen unterscheidet, legen Sie die nibNameEigenschaft als Namen Ihrer .xibDatei fest.
  8. Implementieren required init?(coder aDecoder: NSCoder)und override init(frame: CGRect)aufrufen setupFromNib()wie im folgenden Beispiel.
  9. Fügen Sie Ihrem gewünschten Storyboard eine UIView hinzu und legen Sie die Klasse als Ihren benutzerdefinierten Klassennamen (dh MyCustomClass) fest.
  10. Beobachten Sie IBDesignable in Aktion, während es Ihre .xib mit all seiner Ehrfurcht und seinem Staunen in das Storyboard zeichnet.

Hier ist das Protokoll, auf das Sie verweisen möchten:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

Und hier ist ein Beispiel dafür MyCustomClass, wie das Protokoll implementiert wird (wobei die .xib-Datei benannt wird MyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

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

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

}

HINWEIS: Wenn Sie das Gotcha verpassen und den classWert in Ihrer .xib-Datei als benutzerdefinierte Klasse festlegen, wird es nicht im Storyboard gezeichnet und Sie erhalten eine EXC_BAD_ACCESSFehlermeldung, wenn Sie die App ausführen, da sie in einer Endlosschleife von stecken bleibt versuchen, die Klasse von der Schreibfeder aus mit der init?(coder aDecoder: NSCoder)Methode zu initialisieren , die dann aufruft Self.nib.instantiateund die initerneut aufruft .

Ben Patch
quelle
2
Hier ist ein weiterer großartiger Ansatz, aber ich denke
Ben Patch
4
Der oben von Ihnen erwähnte Ansatz funktioniert perfekt und wendet die Live-Vorschau direkt im Storyboard an. Es ist absolut praktisch und großartig, großartig!
Igor Leonovich
1
Zu Ihrer Information: Diese Lösung, die die Einschränkungsdefinition in verwendet setupFromNib(), scheint bestimmte seltsame Probleme beim automatischen Layout mit Tabellenzellen mit automatischer Größenanpassung zu beheben, die XIB-erstellte Ansichten enthalten.
Gary
1
Mit Abstand die beste Lösung! Ich liebe @IBDesignableKompatibilität. Ich kann nicht glauben, warum Xcode oder UIKit beim Hinzufügen einer UIView-Datei nicht standardmäßig so etwas bereitstellen.
fl034
1
Ich kann nicht scheinen, dass dies funktioniert. Jedes Mal, wenn ich den Dateibesitzer in meiner .xib-Datei einstelle, wird AUCH eine benutzerdefinierte Klasse festgelegt.
Zeichner
32

Angenommen, Sie haben eine xib erstellt, die Sie verwenden möchten:

1) Erstellen Sie eine benutzerdefinierte Unterklasse von UIView (Sie können zu Datei -> Neu -> Datei ... -> Cocoa Touch-Klasse gehen. Stellen Sie sicher, dass "Unterklasse von:" "UIView" ist).

2) Fügen Sie dieser Ansicht bei der Initialisierung eine Ansicht hinzu, die auf der xib als Unteransicht basiert.

In Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

In Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

In Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) Wenn Sie es in Ihrem Storyboard verwenden möchten, fügen Sie wie gewohnt eine UIView hinzu, wählen Sie die neu hinzugefügte Ansicht aus und rufen Sie den Identitätsinspektor auf (das dritte Symbol oben rechts, das wie ein Rechteck mit Linien darin aussieht). und geben Sie den Namen Ihrer Unterklasse als "Klasse" unter "Benutzerdefinierte Klasse" ein.

user1021430
quelle
xibView.frame = self.frame;sollte sein xibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);, sonst hat xibView einen Versatz, wenn die Ansicht zum Storyboard hinzugefügt wird.
BabyPanda
spät zur Party, aber es scheint, dass er es in xibView.frame = self.bounds geändert hat, was ein Frame ohne Offset ist
Heavy_Bullets
20
Führt zu einem Absturz aufgrund unendlicher Rekursion. Durch das Laden der Schreibfeder wird eine weitere Instanz der Unterklasse erstellt.
David
2
Die Klasse der xib-Ansicht sollte nicht mit dieser neuen Unterklasse identisch sein. Wenn die xib MyClass ist, können Sie diese neue Klasse MyClassContainer erstellen.
user1021430
Die obige Lösung stürzt in unendliche Rekursion ab.
JBarros35
27

Ich fand die Lösung "Als Unteransicht hinzufügen" immer unbefriedigend, da sie mit (1) Autolayout-, (2) @IBInspectableund (3) Steckdosen verschraubt ist. Lassen Sie mich Ihnen stattdessen die Magie awakeAfter:einer NSObjectMethode vorstellen .

awakeAfterMit dieser Option können Sie das tatsächlich von einer NIB / Storyboard aufgeweckte Objekt gegen ein völlig anderes Objekt austauschen. Dieses Objekt wird dann dem Hydratationsprozess unterzogen, hat es awakeFromNibaufgerufen, wird als Ansicht hinzugefügt usw.

Wir können dies in einer Unterklasse "Pappausschnitt" unserer Ansicht verwenden, deren einziger Zweck darin besteht, die Ansicht von der NIB zu laden und zur Verwendung im Storyboard zurückzugeben. Die einbettbare Unterklasse wird dann im Identitätsinspektor der Storyboard-Ansicht und nicht in der ursprünglichen Klasse angegeben. Es muss eigentlich keine Unterklasse sein, damit dies funktioniert, aber wenn Sie es zu einer Unterklasse machen, kann IB alle IBInspectable / IBOutlet-Eigenschaften anzeigen.

Diese zusätzliche Boilerplate mag suboptimal erscheinen - und in gewissem Sinne, weil sie dies im Idealfall UIStoryboardnahtlos handhaben würde -, hat jedoch den Vorteil, dass die ursprüngliche NIB und UIViewUnterklasse vollständig unverändert bleiben. Die Rolle, die es spielt, ist im Grunde die eines Adapters oder einer Bridge-Klasse und ist in Bezug auf das Design als zusätzliche Klasse absolut gültig, auch wenn es bedauerlich ist. Auf der anderen Seite, wenn Sie es vorziehen, mit Ihren Klassen sparsam umzugehen, implementiert die Lösung von @ BenPatch ein Protokoll mit einigen anderen geringfügigen Änderungen. Die Frage, welche Lösung besser ist, hängt vom Programmierstil ab: ob man die Objektzusammensetzung oder die Mehrfachvererbung bevorzugt.

Hinweis: Die in der Ansicht in der NIB-Datei festgelegte Klasse bleibt unverändert. Die einbettbare Unterklasse wird nur im Storyboard verwendet. Die Unterklasse kann nicht zum Instanziieren der Ansicht im Code verwendet werden, daher sollte sie selbst keine zusätzliche Logik haben. Es sollte nur den awakeAfterHaken enthalten .

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

⚠️ Der einzige wesentliche Nachteil hierbei ist, dass wenn Sie im Storyboard Einschränkungen für Breite, Höhe oder Seitenverhältnis definieren, die sich nicht auf eine andere Ansicht beziehen, diese manuell kopiert werden müssen. Einschränkungen, die zwei Ansichten betreffen, werden auf dem nächsten gemeinsamen Vorfahren installiert, und Ansichten werden von innen nach außen aus dem Storyboard geweckt. Wenn diese Einschränkungen in der Übersicht hydratisiert sind, ist der Austausch bereits erfolgt. Einschränkungen, die nur die betreffende Ansicht betreffen, werden direkt in dieser Ansicht installiert und werden daher beim Auslagern verworfen, sofern sie nicht kopiert werden.

Beachten Sie, dass hier Einschränkungen in der Ansicht im Storyboard installiert werden, die in die neu instanziierte Ansicht kopiert werden , für die möglicherweise bereits eigene Einschränkungen in der NIB-Datei definiert sind. Diese sind nicht betroffen.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNibist eine typsichere Erweiterung von UIView. Es werden lediglich die Objekte der NIB durchlaufen, bis eines gefunden wird, das dem Typ entspricht. Beachten Sie, dass der generische Typ der Rückgabewert ist, daher muss der Typ am Aufrufstandort angegeben werden.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
Christopher Swasey
quelle
Das ist umwerfend. Wenn ich mich nicht irre, funktioniert dies nur "auf dem Storyboard". Wenn Sie versuchen, eine solche Klasse zur Laufzeit im Code zu erstellen, funktioniert dies meiner Meinung nach nicht. Ich glaube.
Fattie
Die Unterklasse sollte in jeder Hinsicht genauso gut im Code funktionieren wie die ursprüngliche Klasse. Wenn Sie die Ansicht von einer Schreibfeder im Code laden möchten, instanziieren Sie sie einfach direkt mit derselben Technik. Die Unterklasse nimmt lediglich den Code, um die Ansicht von einer Schreibfeder aus zu instanziieren, und steckt ihn in einen Haken, damit das Storyboard ihn verwenden kann.
Christopher Swasey
Eigentlich habe ich mich geirrt - es würde genauso gut funktionieren, wenn Sie es instanziieren könnten, aber Sie können es nicht, weil die Ansicht in der NIB die Oberklasse als Typ hat und daher instantiateViewFromNibnichts zurückgibt. IMO ist keine große Sache, die Unterklasse ist nur eine Erfindung, um sich in das Storyboard einzuhängen. Der gesamte Code sollte sich in der ursprünglichen Klasse befinden.
Christopher Swasey
1
Toll! Eine Sache hat mich gestolpert, weil ich wenig Erfahrung mit xibs habe (ich habe immer nur mit Storyboards und programmatischen Ansätzen gearbeitet). Dies hier zu belassen, falls es jemandem hilft: In der .xib-Datei müssen Sie die Ansicht der obersten Ebene auswählen und festlegen sein Klassentyp zu MyCustomView. In meinem xib fehlte standardmäßig die linke innere Seitenleiste. Zum Einschalten befindet sich neben der Merkmalssteuerung "Anzeigen als: iPhone 7" unten / links eine Schaltfläche.
Xaphod
5
Es bremst Einschränkungen, wenn es durch ein anderes Objekt ersetzt wird. :(
Invoodoo
6

Die derzeit beste Lösung besteht darin, nur einen benutzerdefinierten Ansichts-Controller zu verwenden, dessen Ansicht in einer XIB definiert ist, und einfach die Eigenschaft "Ansicht" zu löschen, die Xcode im Storyboard erstellt, wenn der Ansichts-Controller hinzugefügt wird (vergessen Sie nicht, den Namen von festzulegen die benutzerdefinierte Klasse allerdings).

Dadurch sucht die Laufzeit automatisch nach der xib und lädt sie. Sie können diesen Trick für jede Art von Containeransicht oder Inhaltsansicht verwenden.

Ben G.
quelle
5

Ich denke über die alternativefür die Verwendung XIB viewszu verwenden View Controller in getrennter Storyboard .

Dann im Hauptstoryboard anstelle von benutzerdefinierter Ansicht Verwendung container viewmit Embed Segueund haben StoryboardReferenceauf diese benutzerdefinierten Ansicht Controller die Ansicht sollte innerhalb anderer Ansicht in Hauptstoryboard platziert werden.

Anschließend können wir die Delegierung und Kommunikation zwischen diesem eingebetteten ViewController und dem Hauptansichts-Controller einrichten, indem wir uns auf den Übergang vorbereiten . Dieser Ansatz unterscheidet sich von der Anzeige von UIView, kann jedoch viel einfacher und effizienter (aus Programmiersicht) verwendet werden, um dasselbe Ziel zu erreichen, dh eine wiederverwendbare benutzerdefinierte Ansicht, die im Haupt-Storyboard sichtbar ist

Der zusätzliche Vorteil besteht darin, dass Sie Ihre Logik in der CustomViewController-Klasse implementieren und dort die gesamte Delegierung und Ansichtsvorbereitung einrichten können, ohne separate (im Projekt schwerer zu findende) Controller-Klassen zu erstellen und ohne mithilfe von Component Boilerplate-Code im Haupt-UIViewController zu platzieren. Ich denke, das ist gut für wiederverwendbare Komponenten, z. Music Player-Komponente (Widget-ähnlich), die in andere Ansichten eingebettet werden kann.

Michał Ziobro
quelle
In der Tat leider viel einfacher als die Verwendung der benutzerdefinierten Ansicht xib :(
Wilson
1
Nachdem ich mich mit der xib-Lebenszyklus-Erstellungskette von Apple in benutzerdefiniertem UIView im Kreis bewegt hatte, ging ich auf diese Weise vor.
LightningStryk
Wenn Sie die benutzerdefinierte Ansicht dynamisch hinzufügen möchten, müssen Sie immer noch einen separaten Ansichts-Controller (am besten XIB) verwenden
Satyam
5

Obwohl die beliebtesten Antworten gut funktionieren, sind sie konzeptionell falsch. Sie alle werden File's ownerals Verbindung zwischen den Outlets der Klasse und den UI-Komponenten verwendet. File's ownersoll nur für Objekte der obersten Ebene verwendet werden, nicht für UIViews. Lesen Sie das Apple-Entwicklerdokument . UIView als File's ownerführt zu diesen unerwünschten Konsequenzen.

  1. Sie sind gezwungen zu verwenden, contentViewwo Sie verwenden sollen self. Es ist nicht nur hässlich, sondern auch strukturell falsch, da die Zwischenansicht verhindert, dass die Datenstruktur die UI-Struktur vermittelt. Es ist wie das Gegenteil von deklarativer Benutzeroberfläche.
  2. Sie können nur eine UIView pro Xib haben. Ein Xib soll mehrere UIViews haben.

Es gibt eine elegante Möglichkeit, dies ohne Verwendung zu tun File's owner. Bitte überprüfen Sie diesen Blog-Beitrag . Es erklärt, wie man es richtig macht.

Ingun 전인 건
quelle
Es spielt überhaupt keine Rolle, du hast nicht erklärt, warum es schlecht ist. Ich kann nicht verstehen warum. Es gibt keine Nebenwirkungen.
JBarros35
1

Hier ist die Antwort, die Sie sich schon immer gewünscht haben. Sie können einfach Ihre CustomViewKlasse erstellen und die Master-Instanz davon in einer XIB mit allen Unteransichten und Ausgängen haben. Anschließend können Sie diese Klasse auf alle Instanzen in Ihren Storyboards oder anderen xibs anwenden.

Sie müssen nicht mit dem Eigentümer der Datei herumspielen oder Outlets mit einem Proxy verbinden oder die xib auf besondere Weise ändern oder eine Instanz Ihrer benutzerdefinierten Ansicht als Unteransicht von sich selbst hinzufügen.

Mach das einfach:

  1. Importieren Sie das BFWControls-Framework
  2. Ändern Sie Ihre Oberklasse von UIViewnach NibView(oder von UITableViewCellnach NibTableViewCell)

Das ist es!

Es funktioniert sogar mit IBDesignable, um Ihre benutzerdefinierte Ansicht (einschließlich der Unteransichten von xib) zur Entwurfszeit im Storyboard zu referenzieren.

Weitere Informationen finden Sie hier: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

Das Open Source BFWControls-Framework erhalten Sie hier: https://github.com/BareFeetWare/BFWControls

Und hier ist ein einfacher Auszug des NibReplaceableCodes, der ihn antreibt, falls Sie neugierig sind:
 https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom 👣

barfuß
quelle
Gute Lösung, danke!
Avee
Ich möchte kein Framework installieren, wenn es nativ bereitgestellt werden soll.
JBarros35
0

Diese Lösung kann auch verwendet werden, wenn Ihre Klasse nicht denselben Namen wie die XIB hat. Wenn Sie beispielsweise eine Basisansicht-Controller-Klasse controllerA mit dem XIB-Namen controllerA.xib haben und diese mit controllerB unterklassifiziert haben und eine Instanz von controllerB in einem Storyboard erstellen möchten, können Sie:

  • Erstellen Sie den View Controller im Storyboard
  • Setzen Sie die Klasse des Controllers auf ControllerB
  • Löschen Sie die Ansicht des ControllersB im Storyboard
  • Lastansicht in ControllerA überschreiben, um:

* *

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}
otusweb
quelle
0

Lösung für Objective-C gemäß den in der Antwort von Ben Patch beschriebenen Schritten .

Verwenden Sie die Erweiterung für UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

Erstellen von Dateien MyView.h, MyView.mund MyView.xib.

Bereiten Sie zuerst Ihre Antwort vor , MyView.xibwie in der Antwort von Ben Patch angegeben, und legen Sie die Klasse MyViewfür den Eigentümer der Datei fest, anstatt die Hauptansicht in diesem XIB.

MyView.h::

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m::

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

Und später erstellen Sie einfach Ihre Ansicht programmgesteuert:

MyView* view = [[MyView alloc] init];

Warnung! Die Vorschau dieser Ansicht wird im Storyboard nicht angezeigt, wenn Sie die WatchKit-Erweiterung aufgrund dieses Fehlers in Xcode> = 9.2 verwenden: https://forums.developer.apple.com/thread/95616

Ariel Bogdziewicz
quelle
Sollte Swift-Version zur Verfügung gestellt haben. Sehr wenige verwenden ObjC möglicherweise noch zu diesem Zeitpunkt
Satyam,