Richtige Methode zum Laden einer Schreibfeder für eine UIView-Unterklasse

98

Ich bin mir bewusst, dass diese Frage schon einmal gestellt wurde, aber die Antworten sind widersprüchlich und ich bin verwirrt. Bitte flammen Sie mich nicht an.

Ich möchte eine wiederverwendbare UIViewUnterklasse in meiner App haben. Ich möchte die Schnittstelle mit einer NIB-Datei beschreiben.

Angenommen, es handelt sich um eine Ladeanzeige mit einem Aktivitätsindikator. Ich möchte in einem bestimmten Fall diese Ansicht instanziieren und in die Ansicht eines Ansichtscontrollers animieren. Ich könnte die Benutzeroberfläche der Ansicht problemlos programmgesteuert beschreiben, die Elemente programmgesteuert erstellen und ihren Frame innerhalb einer Init-Methode usw. festlegen.

Wie kann ich das mit einer Feder machen? Beibehalten der im Interface Builder angegebenen Größe, ohne einen Frame festlegen zu müssen.

Ich habe es so gemacht, aber ich bin mir sicher, dass es falsch ist (es ist nur eine Ansicht mit einem Picker):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

Aber ich überschreibe mich selbst und wenn ich es instanziiere:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

Ich muss den Rahmen manuell einstellen, anstatt ihn von IB abholen zu lassen.

BEARBEITEN: Ich möchte diese benutzerdefinierte Ansicht in einem Ansichts-Controller erstellen und Zugriff auf die Elemente der Ansicht haben. Ich möchte keinen neuen View Controller.

Vielen Dank

EDIT: Ich weiß nicht, ob dies die beste Vorgehensweise ist, ich bin mir sicher, dass dies nicht der Fall ist, aber so habe ich es gemacht:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

Richtige Übung noch gewünscht

Sollte dies mit einem View Controller und Init mit dem Nib-Namen geschehen sein? Sollte ich die Schreibfeder in einer UIView-Initialisierungsmethode im Code gesetzt haben? Oder ist das, was ich getan habe, in Ordnung?

Adam Waite
quelle
Aber ist die erste Codezeile nicht fast dieselbe wie die, in der Sie in der ursprünglichen Frage verwendet haben initWithDataSource? Auf jeden Fall funktioniert dies auch dann, wenn Sie den Eigentümer als "Null" angeben.
Rakesh
Es ist erwähnenswert, dass heutzutage in 99,99999% der Fälle nur Containeransichten verwendet werden , die jetzt überall und immer in iOS verwendet werden. How-to-Artikel
Fattie
Randnotiz, dass Sie [NSString stringWithFormat: @ "% @", [FSASelectView-Klasse]] durch NSStringFromClass ([FSASelectView-Klasse])
ersetzen können

Antworten:

132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

Ich verwende dies, um die wiederverwendbaren benutzerdefinierten Ansichten zu initialisieren, die ich habe.


Beachten Sie, dass Sie dort am Ende "firstObject" verwenden können, es ist etwas sauberer. "firstObject" ist eine praktische Methode für NSArray und NSMutableArray.

Hier ist ein typisches Beispiel für das Laden einer xib zur Verwendung als Tabellenkopf. In Ihrer Datei YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normalerweise TopArea.xibklicken Sie in der Datei auf Dateibesitzer und setzen den Dateieigentümer auf YourClass . Dann hätten Sie tatsächlich in YourClass.h IBOutlet-Eigenschaften. In TopArea.xibkönnen Sie Steuerelemente in diese Ausgänge ziehen.

Vergessen Sie nicht, dass TopArea.xibSie möglicherweise auf die Ansicht selbst klicken und diese auf eine Steckdose ziehen müssen, damit Sie bei Bedarf die Kontrolle darüber haben. (Ein sehr lohnender Tipp ist, dass Sie dies unbedingt für Tabellenzellenzeilen tun müssen - Sie müssen die Ansicht selbst mit der entsprechenden Eigenschaft in Ihrem Code verbinden.)

Premsuraj
quelle
Es gibt keine initWithNibName: Methode für eine UIView, es ist nur für UIViewController
Meronix
Das ist für einen View-Controller. Ich möchte ein UIView verwenden und den Controller haben, der es erstellt, um es zu besitzen.
Adam Waite
Es tut mir leid, ich habe in meiner Eile den falschen Code kopiert. Ich habe die Antwort aktualisiert. Bitte schauen Sie.
Premsuraj
Ich werde das richtig markieren. Ich weiß nicht, ob es die beste Vorgehensweise ist, aber es funktioniert.
Adam Waite
@ Joe Blow Entschuldigen Sie die Beule, aber warum sollte man das tun müssen: "Vergessen Sie nicht, dass Sie in TopArea.xib möglicherweise auf die Ansicht selbst klicken und diese in eine Steckdose ziehen müssen, damit Sie die Kontrolle darüber haben , Falls benötigt." Könnten Sie stattdessen einfach myViewObject verwenden, um auf die zugrunde liegende Ansicht zuzugreifen, da es sich um eine UIView selbst handelt? Warum wird eine Immobilie benötigt?
user1686342
39

Wenn Sie Ihr CustomViewund sein xibVerhalten unabhängig halten möchten File's Owner, befolgen Sie diese Schritte

  • Lass das File's OwnerFeld leer.
  • Klicken Sie auf die aktuelle Ansicht in xibIhrer Datei CustomViewund legen Sie sie Custom Classals CustomView(Name Ihrer benutzerdefinierten Ansichtsklasse) fest.
  • Fügen Sie IBOutletin .hDatei Ihrer benutzerdefinierten Ansicht.
  • .xibKlicken Sie in der Datei Ihrer benutzerdefinierten Ansicht auf Ansicht und gehen Sie hinein Connection Inspector. Hier finden Sie alle Ihre IBOutlets, die Sie in einer .hDatei definieren
  • Verbinden Sie sie mit ihrer jeweiligen Ansicht.

Überschreiben Sie in der .mDatei Ihrer CustomViewKlasse die initMethode wie folgt

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Wenn Sie jetzt Ihre laden möchten CustomView, verwenden Sie die folgende Codezeile [[CustomView alloc] init];

Ans
quelle
1
Ab iOS 9 ist dies die einzige auf dieser Seite aufgeführte Lösung, die tatsächlich für mich funktioniert (die anderen Lösungen verursachen Abstürze).
Lebensfreude
Beachten Sie nur, dass dieser Ansatz nicht mit dem Laden Ihrer benutzerdefinierten Ansicht von einem vorhandenen XIB kompatibel ist, da dieser nicht aufgerufen wird -init. Stattdessen wird initWithFrame:und aufgerufen -awakeFromNib. Wenn Sie zum Laden Ihrer Ansicht denselben Code schreiben wie in der -initMethode, geben Sie eine Endlosschleife ein.
Dustt
25

Befolgen Sie die folgenden Schritte

  1. Erstellen Sie eine Klasse mit dem Namen MyView .h / .m vom Typ UIView.
  2. Erstellen Sie eine gleichnamige xib MyView.xib.
  3. Nun die Datei Besitzer Klasse ändern , um UIViewControllervon NSObjectin xib. Siehe das Bild unten Geben Sie hier die Bildbeschreibung ein
  4. Verbinden Sie die Dateibesitzer-Ansicht mit Ihrer Ansicht. Siehe das Bild unten Geben Sie hier die Bildbeschreibung ein

  5. Ändern Sie die Klasse Ihrer Ansicht in MyView. Gleich wie 3.

  6. Platzsteuerelemente erstellen IBOutlets.

Hier ist der Code zum Laden der Ansicht:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

Ich hoffe es hilft.

Klarstellung :

UIViewControllerwird verwendet, um Ihre xib zu laden und die Ansicht, die die UIViewControllerhat, ist tatsächlich MyViewdie, die Sie in der MyView xib zugewiesen haben.

Demo Ich habe eine Demo greifen gemacht hier

iphonisch
quelle
Warum stimmen die Leute falsch ab? Ich habe es noch nicht versucht, aber es sieht so aus, als würde es nicht tun, was ich will.
Adam Waite
Ich hatte abgelehnt, aber das liegt daran, dass ich Ihre Antwort falsch gelesen habe (ich dachte, Sie weisen eine UIView-Unterklasse als Eigentümer der Datei zu). Das tut mir leid.
Rakesh
2
Ignorieren Sie in Ihrer xib-Datei den Dateieigentümer. Dann können Sie vermeiden, dass Sie einen UIViewController erstellen müssen, indem Sie einfach MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject] ausführen.
LightningStryk
1
@ LightningStryk Ja natürlich, aber es ist nur eine andere Möglichkeit, es zu tun.
Iphonic
1
Sie müssen eine wiederverwendbare UIView-Unterklasse erstellen und keinen UIViewController für diesen Zweck verwenden.
Arielyz
11

Beantwortung meiner eigenen Frage über 2 oder etwas Jahre später hier aber ...

Es wird eine Protokollerweiterung verwendet, sodass Sie für alle Klassen ohne zusätzlichen Code arbeiten können.

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()
Adam Waite
quelle
8

In Swift:

Der Name Ihrer benutzerdefinierten Klasse lautet beispielsweise InfoView

Zuerst erstellen Sie Dateien InfoView.xibund InfoView.swiftso:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Dann setzen Sie File's Ownerauf UIViewControllerdiese mögen:

Geben Sie hier die Bildbeschreibung ein

Benennen Sie Ihre um Viewin InfoView:

Geben Sie hier die Bildbeschreibung ein

Klicken File's OwnerSie viewmit der rechten Maustaste auf und verbinden Sie Ihr Feld mit InfoView:

Geben Sie hier die Bildbeschreibung ein

Stellen Sie sicher, dass der Klassenname lautet InfoView:

Geben Sie hier die Bildbeschreibung ein

Danach können Sie die Schaltfläche "Aktion" in Ihrer benutzerdefinierten Klasse problemlos hinzufügen:

Geben Sie hier die Bildbeschreibung ein

Und Verwendung dieser benutzerdefinierten Klasse in Ihrem MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
vkalit
quelle
2

Nun, Sie können entweder die xib mit einem View-Controller initialisieren und viewController.view verwenden. oder mach es so wie du es gemacht hast. Nur eine UIViewUnterklasse als Controller zu erstellen, UIViewist eine schlechte Idee.

Wenn Sie in Ihrer benutzerdefinierten Ansicht keine Steckdosen haben, können Sie diese direkt mit einer UIViewControllerKlasse initialisieren.

Update: In Ihrem Fall:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

Andernfalls müssen Sie eine benutzerdefinierte UIViewControllerDatei erstellen (um sie als Eigentümer der Datei festzulegen, damit die Steckdosen ordnungsgemäß verkabelt sind).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Wo immer Sie die Ansicht hinzufügen möchten, können Sie sie verwenden.

[parentView addSubview:yCustCon.view];

Es ist jedoch keine gute Idee, beim Laden der xib einen anderen Ansichts-Controller (der bereits für eine andere Ansicht verwendet wird) als Eigentümer zu übergeben, da die Ansichtseigenschaft des Controllers geändert wird und Sie nicht auf die ursprüngliche Ansicht zugreifen möchten habe einen Verweis darauf.

BEARBEITEN: Dieses Problem tritt auf, wenn Sie Ihre neue xib mit dem Eigentümer der Datei als derselben Hauptklasse eingerichtet UIViewControllerund die view-Eigenschaft mit der neuen xib-Ansicht verknüpft haben.

dh;

  • YourMainViewController - verwaltet - mainView
  • CustomView - muss bei Bedarf aus xib geladen werden.

Der folgende Code wird später Verwirrung stiften, wenn Sie ihn in die Ansicht schreiben, die geladen wurde YourMainViewController. self.viewDies liegt daran, dass ab diesem Zeitpunkt auf Ihre benutzerdefinierte Ansicht verwiesen wird

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Rakesh
quelle
Ok, im Grunde genommen besagt Best Practice, dass jedem UIView ein Controller zugeordnet sein sollte?
Adam Waite
Nicht unbedingt. Wenn Sie jedoch eine Ansicht von einer separaten xib laden, ist dies der problemlose Weg. Laut Apple Docs vor IOS5 sollte ein Ansichts-Controller nicht zur Steuerung von mehr als einer Szene (xib) verwendet werden und umgekehrt.
Rakesh
Die xib hat eigentlich nur die Größe einer Alarmansicht
Adam Waite
Ich persönlich bin der Meinung, dass es von der Komplexität von Code / XIB abhängt, ob Sie einen neuen Controller erstellen müssen oder nicht. Wenn Sie die verursachten Probleme erfolgreich bewältigen können und zukünftige Änderungen nicht in Betracht ziehen, ist dies kein Problem. Das Erstellen eines separaten Ansichts-Controllers würde jedoch viele Probleme für Sie lösen. Und da es in Ihrem Fall nur ein Aktivitätsindikator ist, können Sie ein UIViewController-Objekt (wie in der Antwort erwähnt) problemlos verwenden. Ich werde meine Antwort entsprechend aktualisieren.
Rakesh
1
So kann ich die Schnittstelle im Interface Builder zeichnen, anstatt sie programmgesteuert auszuführen. Es ist kein Problem, es programmgesteuert zu machen, ich möchte nur wissen, wie man eine UIView unterordnet und ihr eine Feder gibt! Das sollte nicht schwer sein.
Adam Waite