Erstellen eines wiederverwendbaren UIView mit xib (und Laden aus dem Storyboard)

81

OK, es gibt Dutzende von Posts auf StackOverflow, aber keiner ist besonders klar über die Lösung. Ich möchte eine benutzerdefinierte UIViewDatei mit einer zugehörigen xib-Datei erstellen. Die Anforderungen sind:

  • Keine separate UIViewController- eine völlig eigenständige Klasse
  • Outlets in der Klasse, damit ich Eigenschaften der Ansicht festlegen / abrufen kann

Mein aktueller Ansatz dazu ist:

  1. Überschreiben -(id)initWithFrame:

    -(id)initWithFrame:(CGRect)frame {
        self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:self
                                            options:nil] objectAtIndex:0];
        self.frame = frame;
        return self;
    }
    
  2. Programmgesteuert mit -(id)initWithFrame:meinem View Controller instanziieren

    MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    [self.view insertSubview:myCustomView atIndex:0];
    

Dies funktioniert einwandfrei (obwohl das Aufrufen [super init]und einfache Festlegen des Objekts mithilfe des Inhalts der geladenen Schreibfeder etwas verdächtig erscheint - hier wird empfohlen, in diesem Fall eine Unteransicht hinzuzufügen, die ebenfalls einwandfrei funktioniert). Ich möchte jedoch auch die Ansicht vom Storyboard aus instanziieren können. Also kann ich:

  1. Platzieren Sie eine UIViewübergeordnete Ansicht im Storyboard
  2. Setzen Sie die benutzerdefinierte Klasse auf MyCustomView
  3. Überschreiben -(id)initWithCoder:- Der Code, den ich am häufigsten gesehen habe, passt zu einem Muster wie dem folgenden:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(void)initializeSubviews {
        typeof(view) view = [[[NSBundle mainBundle]
                             loadNibNamed:NSStringFromClass([self class])
                                    owner:self
                                  options:nil] objectAtIndex:0];
        [self addSubview:view];
    }
    

Dies funktioniert natürlich nicht. Unabhängig davon, ob ich den obigen Ansatz verwende oder programmgesteuert instanziiere, werden beide -(id)initWithCoder:beim Eingeben -(void)initializeSubviewsund Laden der Schreibfeder aus der Datei rekursiv aufgerufen.

Einige andere SO-Fragen befassen sich damit, wie hier , hier , hier und hier . Keine der gegebenen Antworten behebt das Problem jedoch zufriedenstellend:

  • Ein häufiger Vorschlag scheint darin zu bestehen, die gesamte Klasse in einen UIViewController einzubetten und dort die Schreibfeder zu laden. Dies scheint mir jedoch nicht optimal zu sein, da eine weitere Datei nur als Wrapper hinzugefügt werden muss

Könnte jemand Ratschläge geben, wie man dieses Problem löst und Arbeitssteckdosen in einem benutzerdefinierten UIViewSystem mit minimalem Aufwand / ohne Thin Controller-Wrapper erhält ? Oder gibt es eine alternative, sauberere Methode, um Dinge mit minimalem Boilerplate-Code zu erledigen?

Ken Chatfield
quelle
1
Haben Sie jemals eine zufriedenstellende Antwort darauf erhalten? Ich kämpfe im Moment darum. Alle anderen Antworten scheinen nicht gut genug zu sein, wie Sie erwähnen. Sie können die Frage jederzeit selbst beantworten, wenn Sie in den letzten Monaten etwas herausgefunden haben.
Mike Meyers
13
Warum ist es so schwierig, wiederverwendbare Ansichten in iOS zu erstellen?
Uhrmacher
1
Tatsächlich verwendet die Antwort, auf die Sie verlinken, genau den gleichen Ansatz (obwohl Ihre Antwort keine Init-from-Rect-Funktion enthält, was bedeutet, dass sie nur über das Storyboard und nicht programmgesteuert initialisiert werden kann)
Ken Chatfield
1
In Bezug auf diese sehr alte Qualitätssicherung hat Apple endlich STORYBOARD REFERENCES eingeführt ... developer.apple.com/library/ios/recipes/… ... das ist es also, Puh!
Fattie

Antworten:

13

Ihr Problem ist das Anrufen loadNibNamed:von (ein Nachkomme von) initWithCoder:. loadNibNamed:intern anruft initWithCoder:. Wenn Sie den Storyboard-Codierer überschreiben und Ihre xib-Implementierung immer laden möchten, empfehle ich die folgende Technik. Fügen Sie Ihrer Ansichtsklasse eine Eigenschaft hinzu, und setzen Sie sie in der xib-Datei auf einen vorgegebenen Wert (unter Benutzerdefinierte Laufzeitattribute). [super initWithCoder:aDecoder];Überprüfen Sie nun nach dem Aufruf den Wert der Eigenschaft. Wenn es sich um den vorgegebenen Wert handelt, rufen Sie nicht an [self initializeSubviews];.

Also so etwas:

-(instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];

    if (self && self._xibProperty != 666)
    {
        //We are in the storyboard code path. Initialize from the xib.
        self = [self initializeSubviews];

        //Here, you can load properties that you wish to expose to the user to set in a storyboard; e.g.:
        //self.backgroundColor = [aDecoder decodeObjectOfClass:[UIColor class] forKey:@"backgroundColor"];
    }

    return self;
}

-(instancetype)initializeSubviews {
    id view =   [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];

    return view;
}
Leo Natan
quelle
Danke @LeoNatan! Ich akzeptiere diese Antwort, da sie die beste Lösung für das ursprünglich angegebene Problem ist. Beachten Sie jedoch, dass dies in Swift nicht mehr möglich ist. Ich habe einige separate Hinweise zu einer möglichen Umgehung in diesem Fall hinzugefügt.
Ken Chatfield
@ KenChatfield Ich habe das in meinem Swift-Unterprojekt bemerkt und mich darüber geärgert. Ich bin mir nicht sicher, was sie denken, denn ohne dies ist ein Großteil der internen Implementierung von Cocoa / Cocoa Touch in Swift nicht möglich. Ich wette, es wird einige dynamische Funktionen geben, wenn sie tatsächlich Zeit haben, sich auf Funktionen anstatt auf Fehler zu konzentrieren. Swift ist überhaupt nicht bereit und die schlimmsten Straftäter sind die Entwicklungswerkzeuge.
Leo Natan
Ja, Sie haben Recht, Swift hat momentan viele Ecken und Kanten. Trotzdem scheint es, dass das Zuweisen direkt zu sich selbst ein bisschen wie ein Hack war, daher bin ich in gewisser Weise glücklich, dass der Compiler jetzt vor solchen Dingen warnt (diese strengere Typprüfung scheint eines der schönen Dinge an der zu sein Sprache). Sie haben jedoch Recht, dass die Verknüpfung von benutzerdefinierten Ansichten mit xibs dadurch sehr unübersichtlich und ein wenig unbefriedigend wird. Hoffen wir, dass Sie, sobald Sie die Fehler behoben haben, einige dynamischere Funktionen sehen werden, die uns bei solchen Dingen ein bisschen mehr helfen!
Ken Chatfield
Es ist kein Hack, so funktionieren Klassencluster. Tatsächlich gibt es kein technisches Problem, das die Rückgabe eines Objekts ermöglicht, das eine Unterklasse der Rückgabeklasse ist. So wie es ist, ist es unmöglich, einen der Eckpfeiler von Cocoa und Cocoa Touch - Klassencluster - zu implementieren. Furchtbar. Einige Frameworks, wie z. B. Core Data, können in Swift nicht implementiert werden, was es für die meisten meiner Anwendungen zu einer nutzlosen Sprache macht.
Leo Natan
1
Irgendwie hat es bei mir nicht funktioniert (iOS8.1 SDK). Ich habe einen RestorationIdentifier in der XIB anstelle eines Laufzeitattributs festgelegt, als es funktioniert hat. ZB habe ich die "MyViewRestorationID" in der xib festgelegt, als im initWithCoder: Ich habe überprüft, ob der! [[Self restoreIdentifier] isEqualToString: @ "MyViewRestorationID"]
ingaham
26

Beachten Sie, dass diese Qualitätssicherung (wie viele andere auch) nur von historischem Interesse ist.

Heutzutage Seit Jahren ist in iOS alles nur noch eine Containeransicht. Vollständiges Tutorial hier

(In der Tat hat Apple vor einiger Zeit endlich Storyboard-Referenzen hinzugefügt , was es weitaus einfacher macht.)

Hier ist ein typisches Storyboard mit Containeransichten überall. Alles ist eine Containeransicht. So erstellen Sie Apps.

Geben Sie hier die Bildbeschreibung ein

(Aus Neugier zeigt die Antwort von KenC genau, wie es früher gemacht wurde, eine xib in eine Art Wrapper-Ansicht zu laden, da man sich nicht wirklich "selbst zuweisen" kann.)

Fattie
quelle
Das Problem dabei ist, dass Sie am Ende viele ViewController für alle eingebetteten Inhaltsansichten haben.
Bogdan Onu
Hallo @BogdanOnu! Sie sollten viele, viele, viele Ansichts-Controller haben. Für die "kleinste" Sache sollten Sie einen View Controller haben.
Fattie
2
Das ist sehr nützlich - danke @JoeBlow. Die Verwendung von Containeransichten scheint definitiv ein alternativer Ansatz zu sein und eine einfache Möglichkeit, alle Komplikationen des direkten Umgangs mit xibs zu vermeiden. Es scheint jedoch keine 100% zufriedenstellende Alternative für die Erstellung wiederverwendbarer Komponenten für die Verteilung / Verwendung über Projekte hinweg zu sein, da das gesamte UI-Design direkt in das Storyboard eingebettet werden muss.
Ken Chatfield
3
Ich habe in diesem Fall weniger Probleme mit der Verwendung eines zusätzlichen ViewControllers, da dieser nur die Programmlogik enthält, die sonst in die benutzerdefinierte View-Klasse im xib-Fall gehört, aber die enge Kopplung mit dem Storyboard bedeutet, dass ich es bin Ich bin mir nicht sicher, ob Containeransichten dieses Problem vollständig lösen können. Vielleicht sind Ansichten in den meisten praktischen Situationen projektspezifisch, und daher ist dies die beste und "Standard" -Lösung, aber ich bin überrascht, dass es immer noch keine einfache Möglichkeit gibt, benutzerdefinierte Ansichten für die Verwendung im Storyboard zu verpacken. Der Impuls meines Programmierers, sich zu teilen und in isolierte Komponenten zu erobern, juckt;)
Ken Chatfield
1
Mit der Einführung des Live-Renderings von benutzerdefinierten UIView-Unterklassen in Xcode 6 bin ich mir auch nicht sicher, ob ich die Prämisse kaufe, dass das Erstellen von Ansichten mit xibs auf diese Weise jetzt veraltet ist
Ken Chatfield,
24

Ich füge dies als separaten Beitrag hinzu, um die Situation mit der Veröffentlichung von Swift zu aktualisieren. Der von LeoNatan beschriebene Ansatz funktioniert in Objective-C perfekt. Die strengeren Überprüfungen der Kompilierungszeit verhindern jedoch, selfdass sie beim Laden aus der xib-Datei in Swift zugewiesen werden.

Daher bleibt keine andere Wahl, als die aus der xib-Datei geladene Ansicht als Unteransicht der benutzerdefinierten UIView-Unterklasse hinzuzufügen, anstatt self vollständig zu ersetzen. Dies ist analog zu dem zweiten Ansatz, der in der ursprünglichen Frage beschrieben wurde. Ein grober Überblick über eine Klasse in Swift, die diesen Ansatz verwendet, lautet wie folgt:

@IBDesignable // <- to optionally enable live rendering in IB
class ExampleView: UIView {

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

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

    func initializeSubviews() {
        // below doesn't work as returned class name is normally in project module scope
        /*let viewName = NSStringFromClass(self.classForCoder)*/
        let viewName = "ExampleView"
        let view: UIView = NSBundle.mainBundle().loadNibNamed(viewName,
                               owner: self, options: nil)[0] as! UIView
        self.addSubview(view)
        view.frame = self.bounds
    }

}

Der Nachteil dieses Ansatzes ist die Einführung einer zusätzlichen redundanten Schicht in der Ansichtshierarchie, die bei Verwendung des von LeoNatan in Objective-C beschriebenen Ansatzes nicht vorhanden ist. Dies könnte jedoch als notwendiges Übel und als Produkt der grundlegenden Art und Weise angesehen werden, wie Dinge in Xcode entworfen werden (es scheint mir immer noch verrückt zu sein, dass es so schwierig ist, eine benutzerdefinierte UIView-Klasse mit einem UI-Layout auf eine Weise zu verknüpfen, die konsistent funktioniert über beide Storyboards und aus Code) - das Ersetzen des selfGroßhandels im Initialisierer zuvor schien nie eine besonders interpretierbare Methode zu sein, obwohl es auch nicht so toll erscheint, im Wesentlichen zwei Ansichtsklassen pro Ansicht zu haben.

Ein erfreuliches Ergebnis dieses Ansatzes ist jedoch, dass wir die benutzerdefinierte Klasse der Ansicht im Interface Builder nicht mehr auf unsere Klassendatei festlegen müssen, um ein korrektes Verhalten bei der Zuweisung sicherzustellen. Daher wird selfder rekursive Aufruf init(coder aDecoder: NSCoder)bei der Ausgabe loadNibNamed()unterbrochen (indem Sie die nicht festlegen Die benutzerdefinierte Klasse in der xib-Datei, die init(coder aDecoder: NSCoder)von einfachem Vanille-UIView anstelle unserer benutzerdefinierten Version verwendet wird, wird stattdessen aufgerufen.

Auch wenn wir keine Klassenanpassungen an der in xib direkt gespeicherten Ansicht vornehmen können, können wir die Ansicht mithilfe von Outlets / Aktionen usw. mit unserer übergeordneten UIView-Unterklasse verknüpfen, nachdem der Dateieigentümer der Ansicht auf unsere benutzerdefinierte Klasse festgelegt wurde:

Festlegen der Dateieigentümereigenschaft der benutzerdefinierten Ansicht

Ein Video, das die Implementierung einer solchen Ansichtsklasse Schritt für Schritt unter Verwendung dieses Ansatzes demonstriert, finden Sie im folgenden Video .

Ken Chatfield
quelle
Hallo Joe, danke für deine Kommentare, das ist sehr fett (wie in vielen deiner anderen Kommentare!). Wie ich als Antwort auf deine Antwort sagte, stimme ich zu, dass Containeransichten in den meisten Situationen wahrscheinlich der beste Ansatz sind, aber in diesen Fällen Wenn Ansichten projektübergreifend (oder verteilt) verwendet werden müssen, ist es zumindest für mich und anscheinend auch für andere sinnvoll, eine Alternative zu haben. Sie mögen persönlich glauben, dass es ein schlechter Stil ist, aber vielleicht könnten Sie einfach die vielen anderen Beiträge hier vorschlagen lassen, wie dies als Referenz gemacht werden könnte, und die Leute selbst beurteilen lassen. Beide Ansätze scheinen nützlich zu sein.
Ken Chatfield
Danke dafür. Ich habe in Swift verschiedene Ansätze ausprobiert, bis ich Ihren Rat befolgt habe, die Klasse der Feder als zu verlassen UIView. Ich stimme zu, es ist verrückt, dass Apple dies noch nie so einfach gemacht hat, und jetzt ist es praktisch unmöglich. Ein Container ist nicht immer die Antwort.
Echelon
16

SCHRITT 1. Ersetzen selfaus dem Storyboard

Ersetzen selfin initWithCoder:Methode fehl mit folgendem Fehler.

'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'

Stattdessen können Sie dekodierte Objekte durch awakeAfterUsingCoder:(nicht awakeFromNib) ersetzen . mögen:

@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

SCHRITT 2. Rekursiven Aufruf verhindern

Dies verursacht natürlich auch ein Problem mit rekursiven Aufrufen. (Storyboard-Dekodierung -> awakeAfterUsingCoder:-> loadNibNamed:-> awakeAfterUsingCoder:-> loadNibNamed:-> ...)
Sie müssen also überprüfen, ob der aktuelle awakeAfterUsingCoder:Wert im Storyboard-Dekodierungsprozess oder im XIB-Dekodierungsprozess aufgerufen wird. Sie haben mehrere Möglichkeiten, dies zu tun:

a) Verwenden Sie private, @propertydie nur in der NIB festgelegt ist.

@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end

und setzen Sie "Benutzerdefinierte Laufzeitattribute" nur in "MyCustomView.xib".

Vorteile:

  • Keiner

Nachteile:

  • Funktioniert einfach nicht: setXib:wird NACHher aufgerufen awakeAfterUsingCoder:

b) Überprüfen Sie, ob selfUnteransichten vorhanden sind

Normalerweise haben Sie Unteransichten in der xib, aber nicht im Storyboard.

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    if(self.subviews.count > 0) {
        // loading xib
        return self;
    }
    else {
        // loading storyboard
        return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:nil
                                            options:nil] objectAtIndex:0];
    }
}

Vorteile:

  • Kein Trick im Interface Builder.

Nachteile:

  • Sie können keine Unteransichten in Ihrem Storyboard haben.

c) Setzen Sie während des loadNibNamed:Anrufs ein statisches Flag

static BOOL _loadingXib = NO;

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    if(_loadingXib) {
        // xib
        return self;
    }
    else {
        // storyboard
        _loadingXib = YES;
        typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                                           owner:nil
                                                         options:nil] objectAtIndex:0];
        _loadingXib = NO;
        return view;
    }
}

Vorteile:

  • Einfach
  • Kein Trick im Interface Builder.

Nachteile:

  • Nicht sicher: Statisches Shared Flag ist gefährlich

d) Verwenden Sie eine private Unterklasse in XIB

Deklarieren Sie beispielsweise _NIB_MyCustomViewals Unterklasse von MyCustomView. Und verwenden Sie _NIB_MyCustomViewstatt MyCustomViewnur in Ihrem XIB.

MyCustomView.h:

@interface MyCustomView : UIView
@end

MyCustomView.m:

#import "MyCustomView.h"

@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In Storyboard decoding path.
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

@interface _NIB_MyCustomView : MyCustomView
@end

@implementation _NIB_MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In XIB decoding path.
    // Block recursive call.
    return self;
}
@end

Vorteile:

  • Keine explizite ifinMyCustomView

Nachteile:

  • Prefixing _NIB_Trick in xib Interface Builder
  • relativ mehr Codes

e) Verwenden Sie die Unterklasse als Platzhalter im Storyboard

Ähnlich, d)aber Unterklasse in Storyboard verwenden, Originalklasse in XIB.

Hier deklarieren wir MyCustomViewProtoals Unterklasse von MyCustomView.

@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In storyboard decoding
    // Returns MyCustomView loaded from NIB.
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self superclass])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

Vorteile:

  • Sehr sicher
  • Sauber; Kein zusätzlicher Code in MyCustomView.
  • Keine explizite ifPrüfung wied)

Nachteile:

  • Unterklasse muss im Storyboard verwendet werden.

Ich denke, das e)ist die sicherste und sauberste Strategie. Also übernehmen wir das hier.

SCHRITT 3. Eigenschaften kopieren

Nach loadNibNamed:'awakeAfterUsingCoder:' müssen Sie mehrere Eigenschaften kopieren, aus selfdenen die Instanz des Storyboards dekodiert wird. frameund Autolayout / Autoresize-Eigenschaften sind besonders wichtig.

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                                       owner:nil
                                                     options:nil] objectAtIndex:0];
    // copy layout properities.
    view.frame = self.frame;
    view.autoresizingMask = self.autoresizingMask;
    view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;

    // copy autolayout constraints
    NSMutableArray *constraints = [NSMutableArray array];
    for(NSLayoutConstraint *constraint in self.constraints) {
        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;
        if(firstItem == self) firstItem = view;
        if(secondItem == self) secondItem = view;
        [constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
                                                            attribute:constraint.firstAttribute
                                                            relatedBy:constraint.relation
                                                               toItem:secondItem
                                                            attribute:constraint.secondAttribute
                                                           multiplier:constraint.multiplier
                                                             constant:constraint.constant]];
    }

    // move subviews
    for(UIView *subview in self.subviews) {
        [view addSubview:subview];
    }
    [view addConstraints:constraints];

    // Copy more properties you like to expose in Storyboard.

    return view;
}

ENDGÜLTIGE LÖSUNG

Wie Sie sehen können, ist dies ein bisschen Boilerplate-Code. Wir können sie als "Kategorie" implementieren. Hier erweitere ich häufig verwendeten UIView+loadFromNibCode.

#import <UIKit/UIKit.h>

@interface UIView (loadFromNib)
@end

@implementation UIView (loadFromNib)

+ (id)loadFromNib {
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self)
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}

- (void)copyPropertiesFromPrototype:(UIView *)proto {
    self.frame = proto.frame;
    self.autoresizingMask = proto.autoresizingMask;
    self.translatesAutoresizingMaskIntoConstraints = proto.translatesAutoresizingMaskIntoConstraints;
    NSMutableArray *constraints = [NSMutableArray array];
    for(NSLayoutConstraint *constraint in proto.constraints) {
        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;
        if(firstItem == proto) firstItem = self;
        if(secondItem == proto) secondItem = self;
        [constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
                                                            attribute:constraint.firstAttribute
                                                            relatedBy:constraint.relation
                                                               toItem:secondItem
                                                            attribute:constraint.secondAttribute
                                                           multiplier:constraint.multiplier
                                                             constant:constraint.constant]];
    }
    for(UIView *subview in proto.subviews) {
        [self addSubview:subview];
    }
    [self addConstraints:constraints];
}

Mit diesem können Sie MyCustomViewProtowie folgt deklarieren :

@interface MyCustomViewProto : MyCustomView
@end

@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    MyCustomView *view = [MyCustomView loadFromNib];
    [view copyPropertiesFromPrototype:self];

    // copy additional properties as you like.

    return view;
}
@end

XIB:

XIB-Screenshot

Storyboard:

Storyboard

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Rintaro
quelle
3
Die Lösung ist komplizierter als das ursprüngliche Problem. Um die rekursive Schleife zu stoppen, müssen Sie nur das Eigentümerobjekt der Datei festlegen, anstatt die Inhaltsansicht als Klassentyp vom Typ MyCustomView zu deklarieren.
Bogdan Onu
Es ist nur ein Kompromiss zwischen a) einfachem Initialisierungsprozess, aber komplizierter Ansichtshierarchie und b) kompliziertem Initialisierungsprozess, aber einfacher Ansichtshierarchie. n'est-ce pas? ;)
Rintaro
Gibt es einen Download-Link für dieses Projekt?
Karthikeyan
13

Vergiss nicht

Zwei wichtige Punkte:

  1. Setzen Sie den Eigentümer der Datei der .xib auf den Klassennamen Ihrer benutzerdefinierten Ansicht.
  2. Legen Sie den benutzerdefinierten Klassennamen in IB nicht für die Stammansicht der .xib fest.

Ich bin mehrmals auf diese Q & A-Seite gekommen, als ich gelernt habe, wiederverwendbare Ansichten zu erstellen. Wenn ich die oben genannten Punkte vergaß, verschwendete ich viel Zeit damit, herauszufinden, was zu einer unendlichen Rekursion führte. Diese Punkte werden in anderen Antworten hier und anderswo erwähnt , aber ich möchte sie hier nur noch einmal hervorheben.

Meine vollständige schnelle Antwort mit Schritten ist hier .

Suragch
quelle
2

Es gibt eine Lösung, die viel sauberer ist als die oben genannten: https://www.youtube.com/watch?v=xP7YvdlnHfA

Keine Runtime-Eigenschaften, überhaupt kein Problem mit rekursiven Aufrufen. Ich habe es versucht und es hat wie ein Zauber funktioniert, wenn ich Storyboard und XIB mit IBOutlet-Eigenschaften (iOS8.1, XCode6) verwendet habe.

Viel Glück beim Codieren!

Ingaham
quelle
1
Danke @ingaham! Der im Video beschriebene Ansatz ist jedoch identisch mit der zweiten Lösung, die in der ursprünglichen Frage vorgeschlagen wurde (Swift-Code, für den ich in meiner obigen Antwort dargestellt bin). Wie in beiden Fällen müssen Sie einer Wrapper-UIView-Unterklasse eine Unteransicht hinzufügen. Aus diesem Grund gibt es keine Probleme mit rekursiven Aufrufen und Sie müssen sich auch nicht auf Laufzeiteigenschaften oder andere komplizierte Elemente verlassen. Wie bereits erwähnt, besteht der Nachteil darin, dass der benutzerdefinierten UIView-Klasse eine redundante zusätzliche untergeordnete Ansicht hinzugefügt werden muss. Wie bereits erwähnt, ist dies möglicherweise die beste und einfachste Lösung für den Moment.
Ken Chatfield
Ja, Sie haben vollkommen recht, dies sind identische Lösungen. Eine redundante Ansicht ist zwar erforderlich, aber die sauberste und am besten zu wartende Lösung. Also habe ich mich für diesen entschieden.
Ingaham
Ich glaube, eine redundante Sichtweise ist völlig natürlich und es kann nie eine andere Lösung geben. Beachten Sie, dass Sie sagen "etwas wird hier gehen" ... dass "hier" eine natürlich existierende Sache ist. Es muss einfach "etwas" da sein, einen "Ort, an den man Dinge stellen wird" - genau die Definition dessen, was eine "Ansicht" ist. Und warte! Apples "Container View" Zeug ist in der Tat genau das ... es gibt einen "Frame", eine "Holder View" (die "Container View"), in die Sie etwas einordnen. In der Tat sind die "redundanten" Ansichtslösungen genau eine handgemachte Containeransicht! Verwenden Sie einfach Apples.
Fattie