So laden Sie eine UIView mithilfe einer mit Interface Builder erstellten NIB-Datei

241

Ich versuche etwas Aufwändiges zu tun, aber etwas, das möglich sein sollte. Hier ist also eine Herausforderung für alle Experten da draußen (dieses Forum ist eine Menge von euch :)).

Ich erstelle eine Fragebogen- "Komponente", die ich auf eine NavigationContoller(meine QuestionManagerViewController) laden möchte . Die "Komponente" ist eine "leere" Komponente UIViewController, die je nach der zu beantwortenden Frage unterschiedliche Ansichten laden kann.

Ich mache es so:

  1. Erstellen Sie das Question1View-Objekt als UIViewUnterklasse und definieren Sie einige IBOutlets.
  2. Erstellen Sie (mit dem Interface Builder) das Question1View.xib (HIER IST, WO MEIN PROBLEM MÖGLICHERWEISE IST ). Ich habe sowohl das UIViewControllerals auch das UIViewder Klasse Question1View festgelegt.
  3. Ich verbinde die Steckdosen mit der Ansichtskomponente (mit IB).
  4. Ich überschreibe das initWithNibvon mir QuestionManagerViewController, um so auszusehen:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }

Wenn ich den Code ausführe, wird folgende Fehlermeldung angezeigt:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Beenden der App aufgrund einer nicht erfassten Ausnahme ' NSInternalInconsistencyException', Grund: ' -[UIViewController _loadViewFromNibNamed:bundle:]Die Schreibfeder "Question1View" wurde geladen, aber der Ansichtsausgang wurde nicht festgelegt.'

Ich bin sicher, dass es eine Möglichkeit gibt, die Ansicht mithilfe der NIB-Datei zu laden, ohne eine viewController-Klasse erstellen zu müssen.

Alexander Farber
quelle

Antworten:

224

Es gibt auch eine einfachere Möglichkeit, auf die Ansicht zuzugreifen, als die Schreibfeder als Array zu behandeln.

1 ) Erstellen Sie eine benutzerdefinierte Ansichtsunterklasse mit allen Verkaufsstellen, auf die Sie später zugreifen möchten. --Meine Sicht

2 ) Erstellen Sie in dem UIViewController, den Sie laden und mit der Schreibfeder umgehen möchten, eine IBOutlet-Eigenschaft, die beispielsweise die Ansicht der geladenen Schreibfeder enthält

in MyViewController (eine UIViewController-Unterklasse)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(Vergessen Sie nicht, es zu synthetisieren und in Ihrer .m-Datei freizugeben.)

3) Öffnen Sie Ihre Schreibfeder (wir nennen sie 'myViewNib.xib') in IB und setzen Sie den Eigentümer Ihrer Datei auf MyViewController

4) Verbinden Sie nun den Owner Outlet myViewFromNib Ihrer Datei mit der Hauptansicht in der Feder.

5) Schreiben Sie nun in MyViewController die folgende Zeile:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Sobald Sie dies tun, können Sie durch Aufrufen Ihrer Eigenschaft "self.myViewFromNib" von Ihrer Feder aus auf die Ansicht zugreifen!

averydev
quelle
4
Funktioniert es für die Haupt-Controller-Ansicht (Selbstansicht)? Aus einigen Gründen muss ich die Hauptansicht von der Feder nach der Standard-Controller-Init-Methode laden. Grund - komplexe Sublassing-Struktur
Lukasz
@ Lukasas Wie viel gelingt es dir? Auch ich suche so etwas wie dich.
Ajay Sharma
Es tut mir leid, dass ich dick bin, aber ich bin beim ersten Punkt verwirrt. Wie erstelle ich Outlets in einer benutzerdefinierten Ansichtsunterklasse? Ich dachte, Outlets wären nur für UIViewController?
Jowie
1
Was ist mit einem Fall, in dem diese Ansicht in mehreren übergeordneten Ansichten angezeigt wird? Schlagen Sie also vor, xib für jeden von ihnen manuell zu bearbeiten? Es ist zu schlecht !!!
user2083364
2
Dies funktioniert nur, wenn Sie die benutzerdefinierte Schreibfeder in einem einzelnen Ansichtscontroller verwenden möchten. In dem Fall, in dem Sie es auf verschiedenen Ansichtscontrollern verwenden möchten, die jeweils dynamisch eine Instanz der benutzerdefinierten Schreibfeder erstellen, funktioniert dies nicht.
thgc
120

Danke euch allen. Ich habe einen Weg gefunden, das zu tun, was ich wollte.

  1. Erstelle dein UIView mit den IBOutlets, die Sie brauchen.
  2. Erstellen Sie die xib in IB, gestalten Sie sie nach Ihren Wünschen und verknüpfen Sie sie wie folgt: Der Eigentümer der Datei ist von Klasse UIViewController (keine benutzerdefinierte Unterklasse, sondern die "echte"). Die Ansicht des Dateibesitzers ist mit der Hauptansicht verbunden und ihre Klasse wird als die aus Schritt 1) ​​deklariert.
  3. Verbinden Sie Ihre Steuerungen mit dem IBOutlet s.
  4. Das DynamicViewControllerkann seine Logik ausführen, um zu entscheiden, welche Ansicht / xib geladen werden soll. Sobald es die Entscheidung getroffen hat, setzen Sie in die loadViewMethode so etwas ein:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;

Das ist es!

Die loadNibNamedMethode des Hauptpakets sorgt dafür, dass die Ansicht initialisiert und die Verbindungen erstellt werden.

Jetzt kann der ViewController abhängig von den gespeicherten Daten eine oder eine andere Ansicht anzeigen, und der "übergeordnete" Bildschirm muss sich nicht mehr mit dieser Logik befassen.

geedubb
quelle
2
Ich habe ein sehr ähnliches Problem - ich möchte einfach eine UIView aus einer xib-Datei laden. Diese Schritte funktionieren bei mir nicht - die App stürzt kurz nach dem Hinzufügen der geladenen Ansicht als Unteransicht mit "schlechtem Zugriff" ab.
Justicle
2
Verwenden Sie für iPhone 3.0 objectAtIndex: 0, um das erste Element abzurufen. Dies stürzt für mich genau wie von Justicle beschrieben ab. Irgendeine Idee warum?
tba
1
+1 es hat perfekt für mich funktioniert. Ich wollte mehrere Ansichten hinzufügen, die den einen Ansichts-Controller hatten (ich verwende ein Flip-View-Szenario, in dem sich ein Abschnitt der Ansicht dreht). Ich musste den Index für das Array 0 (iPhone 3.0)
erstellen
42
Dies ist die richtige Antwort. Der Code sollte jedoch so geändert werden, dass er nibViews durchläuft und für jedes Objekt eine Klassenprüfung durchführt, sodass Sie auf jeden Fall das richtige Objekt erhalten. objectAtIndex: 1 ist eine gefährliche Annahme.
M. Ryan
2
Jasconius hat recht. Sie sollten das nibViews-Array wie folgt durchlaufen
Leviathan
71

Ich bin nicht sicher, worüber einige der Antworten sprechen, aber ich muss diese Antwort hier einfügen, wenn ich das nächste Mal in Google suche. Schlüsselwörter: "Laden einer UIView von einer Schreibfeder" oder "Laden einer UIView von einer NSBundle".

Hier ist der Code fast 100% direkt von der Presse Beginning iPhone 3- Buch (Seite 247, "Verwenden der neuen Tabellenansichtszelle"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Dies setzt voraus, dass Sie eine UIViewUnterklasse namens haben Blah, eine Feder namens, Blahdie a enthältUIView Klasse die ihre Klasse gesetzt ist Blah.


Kategorie: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Schnelle Erweiterung

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

Und ein Beispiel im Gebrauch:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
Dan Rosenstark
quelle
2
Wenn Sie das verwenden isKindOf, sind Sie vor Änderungen der Bundle-Struktur geschützt.
Dan Rosenstark
1
Eine assertwäre wahrscheinlich eine bessere Lösung. Auf diese Weise verbergen Sie nur das Problem.
Sulthan
1
@ Sulthan eine Behauptung ist von Natur aus anders. Ich möchte das Objekt vom Typ Blah überall dort, wo es sich in der Feder befindet . Es ist mir egal, ob es befördert oder herabgestuft wurde. Als Antwort auf Ihren Kommentar habe ich jedoch eine Zusicherung hinzugefügt, die jedoch nicht die forSchleife ersetzt. Es ergänzt es.
Dan Rosenstark
Sie können die loadFromNib ohne Parameter ausführen: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([self class]) owner: self options: nil]; for (ID-Objekt im Bundle) {if ([Objekt isKindOfClass: [Selbstklasse]]) {Rückgabeobjekt; }} return nil; }
JVC
22

Für alle, die mehr als eine Instanz der benutzerdefinierten Ansicht verwalten müssen, dh eine Outlet-Sammlung , habe ich die Antworten @Gonso, @AVeryDev und @Olie auf folgende Weise zusammengeführt und angepasst:

  1. Erstellen Sie eine benutzerdefinierte MyView : UIViewund legen Sie sie als " benutzerdefinierte Klasse " des Stamms UIViewin der gewünschten fest XIB fest . benutzerdefinierte Klasse

  2. Erstellen Sie alle Steckdosen, die Sie benötigen MyView(tun Sie dies jetzt, da der IB Ihnen nach Punkt 3 vorschlägt, Steckdosen UIViewControllermit der benutzerdefinierten Ansicht und nicht wie gewünscht mit der benutzerdefinierten Ansicht zu verbinden ). benutzerdefinierte Klasse Steckdose

  3. Legen Sie Ihre UIViewControllerals " Dateibesitzer " der benutzerdefinierten Ansicht XIB fest . Geben Sie hier die Bildbeschreibung ein

  4. In der UIViewControllerfügen Sie eine neue UIViewsfür jede Instanz die MyViewSie wollen, und schließen Sie sie an UIViewControllereine Schaffung Outlet Kollektion : diese Ansichten als „Wrapper“ Ansichten für die benutzerdefinierte Ansicht Instanzen handeln; Geben Sie hier die Bildbeschreibung ein

  5. Zum Schluss fügen Sie in viewDidLoadIhrem UIViewControllerFeld die folgenden Zeilen hinzu:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
Francesco Vadicamo
quelle
Hallo ... du weißt, wie man das alles programmatisch macht, dh. überhaupt nicht mit xib-Datei?
Der X-Coder
19

Ich würde UINib verwenden, um eine benutzerdefinierte UIView zu instanziieren, die wiederverwendet werden soll

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

In diesem Fall werden folgende Dateien benötigt: MyCustomView.xib , MyCustomViewClass.h und MyCustomViewClass.m Beachten Sie, dass [UINib instantiateWithOwner]ein Array zurückgegeben wird. Verwenden Sie daher das Element, das die UIView widerspiegelt, die Sie wiederverwenden möchten. In diesem Fall ist es das erste Element.

thgc
quelle
1
Meinen Sie nicht "customNib" in Zeile 2 anstelle von "rankingNib"
Danny
11

Sie sollten die Klasse Ihres View Controllers nicht als Unterklasse UIViewin Interface Builder festlegen. Das ist definitiv zumindest ein Teil Ihres Problems. Lassen Sie das entweder als UIViewControllereine Unterklasse davon oder eine andere benutzerdefinierte Klasse, die Sie haben.

Was das Laden nur einer Ansicht von einer xib betrifft, ging ich davon aus, dass Sie eine Art Ansichtscontroller (auch wenn dieser nicht erweitert werden muss UIViewController, was für Ihre Anforderungen möglicherweise zu schwer ist ) als Eigentümer der Datei in der Benutzeroberfläche festlegen müssen Builder, wenn Sie ihn zum Definieren Ihrer Schnittstelle verwenden möchten. Ich habe ein wenig recherchiert, um dies ebenfalls zu bestätigen. Dies liegt daran, dass es sonst keine Möglichkeit gibt, auf eines der Schnittstellenelemente in der zuzugreifenUIView , und es auch keine Möglichkeit gibt, Ihre eigenen Methoden im Code durch Ereignisse auszulösen.

Wenn Sie a UIViewControllerals Eigentümer Ihrer Datei für Ihre Ansichten verwenden, können Sie es einfach initWithNibName:bundle:laden und das View Controller-Objekt zurückerhalten. viewStellen Sie in IB sicher, dass Sie den Ausgang auf die Ansicht mit Ihrer Schnittstelle in der xib einstellen. Wenn Sie eine andere Art von Objekt als Datei - Besitzer verwenden, müssen Sie verwenden NSBundle‚s loadNibNamed:owner:options:Methode , um die Spitze zu laden, eine Instanz von Datei - Besitzer auf die Methode übergeben. Alle seine Eigenschaften werden entsprechend den in IB definierten Steckdosen richtig eingestellt.

Marc W.
quelle
Ich lese das Apress-Buch "Beginn der iPhone-Entwicklung: Erkunden des iPhone SDK" und sie haben diese Methode in Kapitel 9: Navigationssteuerungen und Tabellenansichten besprochen. Es schien nicht zu kompliziert.
Lyndsey Ferguson
Ich wäre gespannt, wie sie sagen, dass es fertig ist. Ich habe momentan keine Kopie dieses Buches.
Marc W
10

Sie können auch initWithNibName von UIViewController anstelle von loadNibNamed verwenden. Es ist einfacher, finde ich.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Jetzt müssen Sie nur noch MySubView.xib und MySubView.h / m erstellen. Setzen Sie in MySubView.xib die Owner-Klasse der Datei auf UIViewController und die View-Klasse auf MySubView.

Sie können die Position und Größe subviewder übergeordneten xib-Datei festlegen.

Ying
quelle
8

Dies ist eine großartige Frage (+1) und die Antworten waren fast hilfreich;) Sorry Leute, aber ich hatte verdammt viel Zeit damit, obwohl sowohl Gonso als auch AVeryDev gute Hinweise gaben. Hoffentlich hilft diese Antwort anderen.

MyVC ist der View Controller, der all dieses Zeug enthält.

MySubview ist die Ansicht, die wir von einer xib laden wollen

  • Erstellen Sie in MyVC.xib eine Ansicht des Typs MySubView, die die richtige Größe und Form hat und an der gewünschten Stelle positioniert ist.
  • In MyVC.h haben

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • Vergessen @synthesize mySubView;Sie nicht, es in MyVC.m freizugeben dealloc.

  • Haben Sie in MySubview.h ein Outlet / eine Eigenschaft für UIView *view(möglicherweise unnötig, aber für mich funktioniert). Synthetisieren und veröffentlichen Sie es in .m
  • In MySubview.xib
    • Setzen Sie den Dateibesitzertyp auf MySubviewund verknüpfen Sie die Ansichtseigenschaft mit Ihrer Ansicht.
    • Legen Sie alle Bits aus und verbinden Sie sie IBOutletwie gewünscht mit den
  • Zurück in MyVC.m, haben

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

Das Knifflige für mich war: Die Hinweise in den anderen Antworten luden meine Ansicht aus der xib, ersetzten aber NICHT die Ansicht in MyVC (duh!) - ich musste das selbst austauschen.

Um Zugriff auf mySubviewdie Methoden von zu erhalten, muss die viewEigenschaft in der .xib-Datei auf festgelegt werden MySubview. Ansonsten kommt es als schlichtes altes zurück UIView.

Wenn es eine Möglichkeit gibt, mySubviewdirekt von seinem eigenen xib zu laden , würde das rocken, aber das brachte mich dahin, wo ich sein musste.

Olie
quelle
1
Ich habe diese Methode angewendet, danke. Eine Änderung, die ich vorgenommen habe, um die Unterstützung verschachtelter Ansichten zu ermöglichen, war die Änderung von [ self.view insertSubview: msView AboveSubview: mySubview]; zu [ mySubview.superview insertSubview: msView AboveSubview: mySubview];
Jason
8

Das sollte einfacher sein. Am Ende habe ich UIViewControllereinen loadNib:inPlaceholder:Selektor erweitert und hinzugefügt . Jetzt kann ich sagen

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Hier ist der Code für die Kategorie (er macht die gleiche Rigamarole wie von Gonso beschrieben):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end
skot
quelle
7

In kurzer Zeit

Eigentlich war meine Lösung für dieses Problem, die Ansicht in eine viewDidLoad in meinem CustonViewController zu laden, wo ich die Ansicht wie folgt verwenden wollte:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Laden Sie die Ansicht nicht in eine loadView()Methode! Die loadView-Methode dient zum Laden der Ansicht für Ihren benutzerdefinierten ViewController.

Kalafun
quelle
6

Auch ich wollte etwas Ähnliches machen, das habe ich gefunden: (SDK 3.1.3)

Ich habe einen View Controller A (der selbst einem Nav-Controller gehört), der VC B per Knopfdruck lädt:

In AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Jetzt hat VC B seine Schnittstelle von Bnib, aber wenn eine Taste gedrückt wird, möchte ich in einen 'Bearbeitungsmodus' wechseln, der eine separate Benutzeroberfläche von einer anderen Feder hat, aber ich möchte keine neue VC für den Bearbeitungsmodus. Ich möchte, dass die neue Feder meinem vorhandenen B VC zugeordnet wird.

Also, in BViewController.m (in Knopfdruckmethode)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Drücken Sie dann auf einer anderen Taste (um den Bearbeitungsmodus zu verlassen):

[editView removeFromSuperview];

und ich bin zurück zu meinem ursprünglichen Bnib.

Dies funktioniert gut, aber beachten Sie, dass meine EditMode.nib nur 1 Objekt der obersten Ebene enthält, ein UIView-Objekt. Es spielt keine Rolle, ob der Eigentümer der Datei in dieser Schreibfeder als BViewController oder als Standard-NSObject festgelegt ist, ABER stellen Sie sicher, dass das View Outlet im Eigentümer der Datei NICHT auf irgendetwas festgelegt ist. Wenn dies der Fall ist, erhalte ich einen Absturz von exc_bad_access und xcode lädt 6677-Stack-Frames, die eine interne UIView-Methode zeigen, die wiederholt aufgerufen wird. Sieht also wie eine Endlosschleife aus. (Das View Outlet ist jedoch in meinem ursprünglichen Bnib eingestellt)

Hoffe das hilft.

Brynjar
quelle
Das Lesen zwischen den Zeilen hat mir auch geholfen.
Petert
Gemäß den Informationen unter Link sollten Sie View Controller nicht auf diese Weise manipulieren.
T. Markle
6

Ich habe eine Kategorie erstellt, die mir gefällt:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Rufen Sie dann so an:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Verwenden Sie einen Federnamen, wenn Ihre Feder einen anderen Namen als den Namen Ihrer Klasse hat.

Um es in Ihren Unterklassen für zusätzliches Verhalten zu überschreiben, könnte es folgendermaßen aussehen:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}
Logan
quelle
5

Für Swift-Benutzer mit gestaltbarer Option:

  1. Erstellen Sie eine benutzerdefinierte UIViewUnterklasse und eine xib-Datei , die wir nach unserem eigenen Klassennamen benennen: in unserem Fall MemeView. Denken Sie innerhalb der Meme View-Klasse daran, sie als mit der designierbar zu definieren@IBDesignable vor der Klassendeklaration Attribut
  2. Denken Sie daran, den Eigentümer der Datei in der xib mit unserer benutzerdefinierten UIViewUnterklasse im Indetity Inspector-Bedienfeld festzulegen

    Geben Sie hier die Bildbeschreibung ein

  3. In der xib-Datei können wir jetzt unsere Schnittstelle erstellen, Einschränkungen vornehmen, Outlets, Aktionen usw. erstellen. Geben Sie hier die Bildbeschreibung ein

  4. Wir müssen einige Methoden in unsere benutzerdefinierte Klasse implementieren, um die xib nach der Initialisierung zu öffnen

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }}

  5. In unserer benutzerdefinierten Klasse können wir auch einige überprüfbare Eigenschaften definieren, um die vollständige Kontrolle über sie vom Interface Builder aus zu haben

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}}

Einige Beispiele:
Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Wenn wir weitere Informationen hinzufügen müssen, während die Ansicht in einem Storyboard oder einer anderen xib angezeigt wird prepareForInterfaceBuilder(), wird diese Methode nur beim Öffnen der Datei im Interface Builder ausgeführt. Wenn Sie alles getan haben, was ich geschrieben habe, aber nichts funktioniert, können Sie eine Sigle-Ansicht durch Hinzufügen von Haltepunkten in ihrer Implementierung debuggen. Hier ist die Ansichtshierarchie. Geben Sie hier die Bildbeschreibung ein
Hiearchy anzeigen

Ich hoffe, dies hilft, ein vollständiges Beispiel kann hier heruntergeladen werden

Andrea
quelle
Den vollständigen Artikel finden Sie in meinem Blog cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah, aber ich habe den entsprechenden Teil bereits veröffentlicht.
Andrea
let nib = loadNib(nibName)Dies ist eine XibbedView-Instanz. Wenn Sie "addSubview (nib)" aufrufen, fügen Sie eine XibbedView-Instanz einer anderen XibbedView-Instanz hinzu.
William Hu
In diesem Punkt stimme ich zu. Wenn Sie versuchen, es aus "Debug View Hierarchy" zu sehen, scheint XibbedView eine XibbedView zu enthalten. Du kannst es sehen.
William Hu
@WilliamHu Wenn Sie das bereitgestellte Beispiel nicht herunterladen, können Sie sehen, dass die Datei MemeView.xib nur eine UIView-Instanz mit einer Reihe von Unteransichten ist. Der Dateihalter ist eine MemeView, die eine Unterklasse von XibbedView ist. Daher ist die einzige Instanz von XibbedView nur MemeView, die eine xib-Datei lädt, in der die Hauptansicht nur eine Ansicht ist
Andrea
Oh, na dann. Ich werde es testen. Danke dir!
William Hu
4

Ich fand diesen Blogeintrag von Aaron Hillegass (Autor, Ausbilder, Cocoa Ninja) sehr aufschlussreich. Selbst wenn Sie seinen modifizierten Ansatz zum Laden von NIB-Dateien über einen bestimmten Initialisierer nicht anwenden, werden Sie wahrscheinlich zumindest ein besseres Verständnis des laufenden Prozesses erhalten. Ich habe diese Methode in letzter Zeit mit großem Erfolg angewendet!

Meltemi
quelle
Link ist tot. Ich glaube, es befindet sich jetzt unter bignerdranch.com/blog/…
joelliusp
3

Die vorherige Antwort berücksichtigt keine Änderung der NIB (XIB) -Struktur, die zwischen 2.0 und 2.1 des iPhone SDK aufgetreten ist. Benutzerinhalte beginnen jetzt bei Index 0 anstelle von 1.

Sie können das 2.1-Makro verwenden, das für alle Versionen 2.1 und höher gültig ist (das sind zwei Unterstriche vor IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

Für die meisten unserer Anwendungen verwenden wir eine ähnliche Technik.

Barney

Barney Mattox
quelle
2
Barney: "Die vorherige Antwort" ist beim Stapelüberlauf bedeutungslos, da die Antworten aufgrund der Abstimmung ihre Position verschieben. Beziehen Sie sich besser auf "Freds Antwort" oder "Barneys Antwort" oder "Olies Antwort".
Olie
3

Ich hatte Grund, dasselbe zu tun (programmgesteuertes Laden einer Ansicht aus einer XIB-Datei), aber ich musste dies vollständig aus dem Kontext einer Unterklasse einer Unterklasse von a heraus tun UIView(dh ohne den Ansichts-Controller in irgendeiner Weise einzubeziehen). Zu diesem Zweck habe ich diese Dienstprogrammmethode erstellt:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Dann rufe ich es aus der initWithFrameMethode meiner Unterklasse folgendermaßen auf :

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Gepostet für allgemeines Interesse; Wenn jemand Probleme sieht, ohne dies zu tun, lassen Sie es mich bitte wissen.

MusiGenesis
quelle
Ich habe Ihren Code implementiert, aber er geht bei init immer wieder aus dem Rahmen. Ich habe Ihre statische Methode in einer Utilities-Klasse erstellt. Ich habe h / m-Dateien mit dem Namen MyView (von UIView untergeordnet) und eine xib mit dem gleichen Namen erstellt. In IB habe ich den Eigentümer der xib-Dateien UND die Ansicht auf "MyView" gesetzt. Im Debugger ist es außerhalb des Gültigkeitsbereichs. Vermisse ich etwas in IB?
TigerCoding
Bitte werfen Sie einen Blick auf meine neue SO-Frage hier: stackoverflow.com/questions/9224857/…
TigerCoding
self = [Dienstprogramme initWithNibName: @ "XIB1" withSelf: self]; Du gehst an dir vorbei, wenn es noch nicht eingestellt ist. Ist dieser Code nicht der gleiche wie dieser: self = [Dienstprogramme initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt
@Javy: Mein Codebeispiel hier funktioniert möglicherweise nicht mit ARC - ich habe es nicht mit aktiviertem ARC getestet.
MusiGenesis
3

Hier ist eine Möglichkeit, dies in Swift zu tun (derzeit wird Swift 2.0 in XCode 7 Beta 5 geschrieben).

UIViewErstellen Sie aus Ihrer Unterklasse, die Sie im Interface Builder als "Benutzerdefinierte Klasse" festgelegt haben, eine Methode wie diese (meine Unterklasse heißt RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Dann können Sie es einfach so nennen:

let recordingFooterView = RecordingFooterView.loadFromNib()

Johannes
quelle
2

Keine der Antworten erklärt, wie das eigenständige XIB erstellt wird, das die Wurzel dieser Frage ist. Es gibt keinXcode 4 Option zum "Erstellen einer neuen XIB-Datei".

Um dies zu tun

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Dies mag einfach erscheinen, erspart Ihnen jedoch einige Minuten beim Stöbern, da das Wort "XIB" nirgendwo vorkommt.

Whitneyland
quelle
1

@ AVeryDev

6) So hängen Sie die geladene Ansicht an die Ansicht Ihres View Controllers an:

[self.view addSubview:myViewFromNib];

Vermutlich ist es notwendig, es aus der Ansicht zu entfernen, um Speicherlecks zu vermeiden.

Zur Verdeutlichung: Der View Controller verfügt über mehrere IBOutlets, von denen einige (wie üblich) mit Elementen in der ursprünglichen Nib-Datei und einige mit Elementen in der geladenen Nib verbunden sind. Beide Federn haben die gleiche Besitzerklasse. Die geladene Ansicht überlagert die ursprüngliche.

Tipp: Setzen Sie die Deckkraft der Hauptansicht in der geladenen Feder auf Null, damit die Elemente der ursprünglichen Feder nicht verdeckt werden.

Matt
quelle
Es ist gut, darauf zu achten, aber wenn Sie eine Ansicht als Unteransicht hinzufügen, wird sie automatisch von ihrem vorherigen übergeordneten Element entfernt, damit sie nicht ausläuft.
averydev
1

Nachdem ich viele Stunden verbracht hatte, schmiedete ich folgende Lösung. Befolgen Sie diese Schritte, um benutzerdefinierte zu erstellen UIView.

1) Erstellen Sie die von UIView geerbte Klasse myCustomView .
Geben Sie hier die Bildbeschreibung ein

2) Erstellen .xib mit dem Namen myCustomView .
Geben Sie hier die Bildbeschreibung ein

3) Klasse von ändern UIView in Ihrer .xib Datei, assign myCustomView Klasse gibt.
Geben Sie hier die Bildbeschreibung ein

4) Erstellen Sie IBOutlets
Geben Sie hier die Bildbeschreibung ein

5) Laden Sie .xib in myCustomView * customView . Verwenden Sie den folgenden Beispielcode.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Hinweis: Für diejenigen, die immer noch Probleme haben , kann ich einen Link zum Beispielprojekt mit myCustomView bereitstellen

Aamir
quelle
1

Kürzeste Version:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
Denis Kutlubaev
quelle
0

Ich habe die Konvention, xibs mit Ansichten darin zu benennen, die mit der Ansicht identisch sind. Gleich wie bei einem View Controller. Dann muss ich keine Klassennamen in Code schreiben. Ich lade ein UIView aus einer gleichnamigen NIB-Datei.

Beispiel für eine Klasse namens MyView.

  • Erstellen Sie im Interface Builder eine NIB-Datei mit dem Namen MyView.xib
  • Fügen Sie im Interface Builder eine UIView hinzu. Setzen Sie die Klasse auf MyView. Passen Sie die Instanzvariablen von MyView nach Herzenslust an Unteransichten an, auf die Sie möglicherweise später zugreifen möchten.
  • Erstellen Sie in Ihrem Code eine neue MyView wie folgt:

    MyView * myView = [MyView nib_viewFromNibWithOwner: owner];

Hier ist die Kategorie dafür:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Ich würde dann Schaltflächen mit Aktionen des Controllers verbinden, den ich verwende, und Dinge auf Etiketten festlegen, indem ich die Ausgänge in meiner Unterklasse für benutzerdefinierte Ansichten verwende.

n13
quelle
0

So laden Sie programmgesteuert eine Ansicht von einer Feder / XIB in Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}
Eng Eibe
quelle
-1

Am Ende habe ich UIView eine Kategorie hinzugefügt:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

Erklärung hier: Viewcontroller lädt weniger Views in iOS & Mac

gngrwzrd
quelle
Hrm. Was ist mit dem Tag?
n13
um zu wissen, welches Objekt der obersten Ebene beibehalten werden soll. Zu der Zeit war es notwendig, aber Sie könnten wahrscheinlich die Aufbewahrungen herausnehmen, um ARC jetzt zu erfüllen.
Gngrwzrd