it-swarm.com.de

Wie kann man eine benutzerdefinierte Ansicht Xib in eine Storyboard-Szene einbetten?

Ich bin relativ neu in der XCode/iOS-Welt. Ich habe ein paar anständige, auf Storyboards basierende Apps erstellt, aber ich habe mir nie die ganze Sache mit Nib/Xib geschnitten. Ich möchte dieselben Werkzeuge für Szenen verwenden, um eine wiederverwendbare Ansicht/Kontrolle zu entwerfen. Also habe ich mein erstes Xib für meine View-Unterklasse erstellt und es aufgemalt:

enter image description here

Ich habe meine Verkaufsstellen und Constraints eingerichtet, so wie ich es im Storyboard gewohnt bin. Ich habe die Klasse meines File Owner auf die meiner benutzerdefinierten UIView-Unterklasse gesetzt. Ich gehe also davon aus, dass ich diese Ansichtsunterklasse mit einer API instanziieren kann, und sie wird wie gezeigt konfiguriert/verbunden.

Jetzt wieder in meinem Storyboard, möchte ich dies einbetten/wiederverwenden. Ich mache das in einer Tabellensicht-Prototypzelle:

enter image description here

Ich habe eine Aussicht. Ich habe die Klasse meiner Unterklasse zugewiesen. Ich habe einen Auslass dafür geschaffen, damit ich ihn manipulieren kann.

Die 64-Dollar-Frage lautet: Wo/Wie gebe ich an, dass es nicht ausreicht, eine leere/unkonfigurierte Instanz meiner Ansichtsunterklasse dorthin zu platzieren, sondern die von mir erstellte .xib-Komponente zum Konfigurieren/Instanziieren zu verwenden? Es wäre wirklich cool, wenn ich in XCode6 einfach die XIB-Datei eingeben könnte, die für einen bestimmten UIView verwendet werden soll, aber ich sehe kein Feld dafür, also muss ich irgendwo etwas Code tun.

(Ich sehe andere Fragen wie diese zu SO, habe aber keine Fragen zu diesem Teil des Puzzles oder zu XCode6/2015 erhalten)

Aktualisieren

Ich kann dies zu einer Art Arbeit machen, indem ich awakeFromNib meiner Tabellenzelle wie folgt implementiere:

- (void)awakeFromNib
{
    // gather all of the constraints pointing to the uncofigured instance
    NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
        return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
    }]];
    // fetch the fleshed out variant
    ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
    // ape the current placeholder's frame
    fromXIB.frame = self.progressControl.frame;
    // now swap them
    [UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
    // recreate all of the constraints, but for the new guy
    for (NSLayoutConstraint *each in progressConstraints) {
        id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
        id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
        [self.contentView addConstraint: constraint];
    }
    // update our outlet
    self.progressControl = fromXIB;
}

Ist das so einfach wie es dann geht? Oder arbeite ich zu hart dafür?

41
Travis Griggs

Du bist fast da. Sie müssen initWithCoder in Ihrer benutzerdefinierten Klasse, der Sie die Ansicht zugewiesen haben, überschreiben.

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
    }
    return self; }

Sobald dies erledigt ist, weiß das StoryBoard, dass es die Xib in diesen UIView laden soll.

Hier ist eine detailliertere Erklärung:

So sieht Ihre UIViewController auf Ihrem Storyboard aus: enter image description here

Der blaue Bereich ist im Grunde ein UIView, der Ihre Xib "hält". 

Das ist dein Xib: 

enter image description here

Es gibt eine Aktion, die mit einer Schaltfläche verbunden ist, mit der Text gedruckt wird. 

und das ist das Endergebnis: 

enter image description here

Der Unterschied zwischen dem ersten clickMe und dem zweiten ist, dass der erste mit UIViewController zum StoryBoard hinzugefügt wurde. Die zweite wurde mit Code hinzugefügt.

26
Segev

Sie müssen awakeAfterUsingCoder: in Ihre benutzerdefinierte UIView-Unterklasse implementieren. Mit dieser Methode können Sie das dekodierte Objekt (aus dem Storyboard) mit einem anderen Objekt (aus Ihrem wiederverwendbaren Xib) austauschen, z.

- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
    // without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
    // set the view tag in the MyView xib to be -999 and anything else in the storyboard.
    if ( self.tag == -999 )
    {
        return self;
    }

    // make sure your custom view is the first object in the nib
    MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];

    // copy properties forward from the storyboard-decoded object (self)
    v.frame = self.frame;
    v.autoresizingMask = self.autoresizingMask;
    v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
    v.tag = self.tag;

    // copy any other attribtues you want to set in the storyboard

    // possibly copy any child constraints for width/height

    return v;
}

Es gibt ein ziemlich gutes Schreiben hier , das diese Technik und einige Alternativen diskutiert.

Wenn Sie außerdem IB_DESIGNABLE zu Ihrer @interface-Deklaration hinzufügen und eine initWithFrame:-Methode bereitstellen, können Sie die Entwurfszeitvorschau in IB anzeigen lassen (Xcode 6 erforderlich!):

IB_DESIGNABLE @interface MyView : UIView
@end

@implementation MyView

- (id) initWithFrame: (CGRect) frame
{
    self = [[[UINib nibWithNibName: @"MyView"
                            bundle: [NSBundle bundleForClass: [MyView class]]]

             instantiateWithOwner: nil
             options: nil] firstObject];

    self.frame = frame;

    return self;
}
16
TomSwift

Eine ziemlich coole und wiederverwendbare Möglichkeit, diesen Interface Builder und Swift 4 auszuführen:

  1. Erstellen Sie eine neue Klasse wie folgt:

    import Foundation
    import UIKit
    
    @IBDesignable class XibView: UIView {
    
        @IBInspectable var xibName: String?
    
        override func awakeFromNib() { 
            guard let name = self.xibName, 
                  let xib = Bundle.main.loadNibNamed(name, owner: self), 
                  let view = xib.first as? UIView else { return }    
            self.addSubview(view)
        }
    
    }
    
  2. Fügen Sie in Ihrem Storyboard eine UIView hinzu, die als Container für die Xib dient. Vergeben Sie einen Klassennamen von XibView:

  3. Legen Sie im Eigenschafteninspektor dieser neuen XibView den Namen Ihrer .xib (ohne Dateierweiterung) im Feld IBInspectable fest:

  4. Fügen Sie Ihrem Projekt eine neue Xib-Ansicht hinzu, und legen Sie im Eigenschafteninspektor den "Dateibesitzer" des Xib auf XibView fest (stellen Sie sicher, dass Sie nur den "Dateibesitzer" auf Ihre benutzerdefinierte Klasse festgelegt haben. Unterbrechen Sie NICHT die Inhaltsansicht oder die Unterkategorie.) stürzt ab) und setzt erneut das Feld IBInspectable:

Eines zu beachten: Dies setzt voraus, dass Sie den .xib-Frame mit seinem Container abgleichen. Wenn Sie die Größe nicht ändern müssen oder deren Größe geändert werden muss, müssen Sie einige programmatische Einschränkungen hinzufügen oder den Rahmen der Subansicht entsprechend anpassen. Ich benutze snapkit , um die Sache zu vereinfachen:

xibView.snp_makeConstraints(closure: { (make) -> Void in
    make.edges.equalTo(self)
})

Bonuspunkte

Angeblich können Sie prepareForInterfaceBuilder() verwenden, um diese wiederverwendbaren Ansichten im Interface Builder sichtbar zu machen, aber ich hatte nicht viel Glück. Dieses Blog schlägt vor, eine contentView -Eigenschaft hinzuzufügen und Folgendes aufzurufen:

override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()
    xibSetup()
    contentView?.prepareForInterfaceBuilder()
}
15
brandonscript
  • Xib-Datei erstellen File > New > New File > iOS > User Interface > View
  • Erstellen Sie eine benutzerdefinierte UIView-Klasse File > New > New File > iOS > Source > CocoaTouch
  • Weisen Sie der benutzerdefinierten Ansichtsklasse die Identität der Xib-Datei zu
  • In viewDidLoad des View-Controllers initialisieren Sie xib und die zugehörige Datei mit loadNibNamed: bei NSBundle.mainBundle. Die erste zurückgegebene Ansicht kann als Unteransicht von self.view hinzugefügt werden.
  • Die aus der Spitze geladene benutzerdefinierte Ansicht kann in einer Eigenschaft zum Festlegen des Frames in viewDidLayoutSubviews gespeichert werden. Setzen Sie den Frame einfach auf self.views Frame, sofern Sie ihn nicht kleiner als self.view machen müssen.

    class ViewController: UIViewController {
    
        weak var customView: MyView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView
            self.view.addSubview(customView)
            addButtonHandlerForCustomView()
        }
    
        private func addButtonHandlerForCustomView() {
            customView.buttonHandler = {
                [weak self] (sender:UIButton) in
                guard let welf = self else {
                    return
                }
                welf.buttonTapped(sender)
            }
        }
    
        override func viewDidLayoutSubviews() {
            self.customView.frame = self.view.frame
        }
    
        private func buttonTapped(button:UIButton) {
    
        }
    }
    
  • Wenn Sie von der xib zur UIViewController-Instanz zurückkehren möchten, erstellen Sie eine schwache Eigenschaft für die Klasse der benutzerdefinierten Ansicht.

    class MyView: UIView {
    
        var buttonHandler:((sender:UIButton)->())!
    
        @IBAction func buttonTapped(sender: UIButton) {
            buttonHandler(sender:sender)
        }
    }
    

Hier ist das Projekt auf GitHub

2
smileBot

Sie müssen lediglich UIView in Ihre IB ziehen und dort ablegen und setzen

yourUIViewClass  *yourView =   [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]

enter image description here

Schritt

  1. Neue Datei hinzufügen => Benutzeroberfläche => UIView 
  2. Benutzerdefinierte Klasse festlegen - IhreUIViewClass
  3. Legen Sie die Wiederherstellungs-ID fest - IhreUIViewClass
  4. yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]

Jetzt können Sie die Ansicht nach Ihren Wünschen anpassen.

2
Yuyutsu

Eine etwas schnellere Version von @brandonscript mit früher Rückkehr:

override func awakeFromNib() {

    guard let xibName = xibName,
          let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
          let views = xib as? [UIView] else {
        return
    }

    if views.count > 0 {
        self.addSubview(views[0])
    }

}
1

Ich benutze diesen Code-Ausschnitt seit Jahren. Wenn Sie benutzerdefinierte Klassenansichten in Ihrer XIB planen, legen Sie diese einfach in der .m-Datei Ihrer benutzerdefinierten Klasse ab.

Als Nebeneffekt führt dies zum Aufruf von awakeFromNib, sodass Sie Ihren gesamten Init/Setup-Code dort belassen können.

- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
    if ([[self subviews] count] == 0) {
        UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
        view.frame = self.frame;
        view.autoresizingMask = self.autoresizingMask;
        view.alpha = self.alpha;
        view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
        return view;
    }
    return self;
}
1
alexgophermix

Ich empfehle den Pfad nicht, den Sie gehen, aber Sie können dies tun, indem Sie eine "eingebettete Ansichtssteuerungsansicht" an der Stelle platzieren, an der die Ansicht angezeigt werden soll.

Betten Sie einen View Controller ein, der eine einzelne Ansicht enthält - die Ansicht, die Sie wiederverwenden möchten.

0
Dustin

Es ist schon eine Weile her, und ich habe eine Reihe von Antworten gesehen. Ich habe es kürzlich überarbeitet, weil ich gerade die UIViewController-Einbettung verwendet hatte. Was funktioniert, bis Sie etwas in "ein zur Laufzeit wiederholtes Element" einfügen möchten (z. B. eine UICollectionViewCell oder eine UITableViewCell). Der von @TomSwift bereitgestellte Link führte mich dazu, dem Muster von zu folgen

A) Anstatt die übergeordnete Ansichtsklasse zum benutzerdefinierten Klassentyp zu machen, muss FileOwner die Zielklasse sein (in meinem Beispiel CycleControlsBar).

B) Jede Outlet/Action-Verknüpfung der verschachtelten Widgets geht dahin

C) Implementieren Sie diese einfache Methode in CycleControlsBar:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
        self.addSubview(container)
        container.constrainToSuperview() // left as an excise for the student
    }
}

Funktioniert wie ein Zauber und viel einfacher als die anderen Ansätze.

0
Travis Griggs

Hier ist die Antwort, die Sie schon immer haben wollten. Sie können einfach Ihre Klasse CustomView erstellen und die Master-Instanz davon in einer xib mit allen Unteransichten und Ausgängen haben. Dann können Sie diese Klasse auf alle Instanzen in Ihren Storyboards oder anderen xibs anwenden.

Sie müssen nicht mit dem Eigentümer von File herumspielen, keine 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 einfach folgendes:

  1. Importieren Sie das BFWControls-Framework
  2. Ändere deine Superklasse von UIView zu NibView (oder von UITableViewCell zu NibTableViewCell)

Das ist es!

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

Sie können hier mehr darüber lesen: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

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

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

Tom ????

0
barefeettom