Jak załadować UIView przy użyciu pliku stalówki utworzonego za pomocą Konstruktora interfejsów

241

Próbuję zrobić coś nieco skomplikowanego, ale coś, co powinno być możliwe. Oto wyzwanie dla was wszystkich ekspertów (to forum jest gromadką was wszystkich :)).

Tworzę „komponent” Kwestionariusza, który chcę załadować na NavigationContoller(mój QuestionManagerViewController). „Składnik” jest „pusty” UIViewController, który może ładować różne widoki w zależności od pytania, na które należy odpowiedzieć.

Tak to robię:

  1. Utwórz obiekt Question1View jako UIViewpodklasę, definiując niektóre IBOutlets.
  2. Utwórz (za pomocą Konstruktora interfejsów) Question1View.xib (TUTAJ JEST GDZIE MOŻLIWY JEST MOJA PROBLEM ). Ustawiłem zarówno te, jak UIViewControlleri UIViewklasy klasy Question1View.
  3. Łączę wyloty z komponentem widoku (używając IB).
  4. Zastępuję initWithNibmoje, QuestionManagerViewControlleraby wyglądać tak:

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

Po uruchomieniu kodu pojawia się następujący błąd:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Kończenie aplikacji z powodu nieprzechwyconego wyjątku NSInternalInconsistencyException”, powód:„ -[UIViewController _loadViewFromNibNamed:bundle:]załadowałem końcówkę „Question1View”, ale ujście widoku nie zostało ustawione ”.

Jestem pewien, że istnieje sposób na załadowanie widoku przy użyciu pliku stalówki, bez potrzeby tworzenia klasy viewController.

Alexander Farber
źródło

Odpowiedzi:

224

Jest również łatwiejszy sposób na dostęp do widoku zamiast zajmowania się stalówką jako tablicą.

1 ) Utwórz niestandardową podklasę widoku z dowolnymi gniazdami, do których chcesz mieć dostęp później. --Mój widok

2 ) w UIViewController, który chcesz załadować i obsługiwać końcówkę, utwórz właściwość IBOutlet, która będzie na przykład przechowywać widok załadowanej końcówki

w MyViewController (podklasa UIViewController)

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

(nie zapomnij go zsyntetyzować i zwolnić w pliku .m)

3) otwórz stalówkę (nazwiemy ją „myViewNib.xib”) w IB, ustaw właściciela pliku na MyViewController

4) teraz podłącz ujście właściciela pliku myViewFromNib do głównego widoku w stalówce.

5) Teraz w MyViewController, napisz następujący wiersz:

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

Gdy tylko to zrobisz, wywołanie Twojej właściwości „self.myViewFromNib” zapewni ci dostęp do widoku ze stalówki!

averydev
źródło
4
Czy będzie działać dla widoku głównego kontrolera (self.view)? Z niektórych powodów muszę załadować widok główny z końcówki po standardowej metodzie inicjowania kontrolera. Powód - złożona struktura sublassowania
Łukasz
@L Łukasz Ile udało ci się to zrobić? Nawet ja szukam czegoś takiego jak ty.
Ajay Sharma
Przepraszam, że mogę być gruby, ale w pierwszej chwili jestem zdezorientowany. Jak utworzyć ujścia w podklasie widoku niestandardowego? Myślałem, że punkty sprzedaży są tylko dla UIViewControllers?
jowie
1
Co z przypadkiem, gdy ten widok pojawia się w wielu widokach nadrzędnych? Czy proponujesz ręcznie edytować xib dla każdego z nich? To ZA ZŁO !!!
user2083364
2
Działa to tylko wtedy, gdy chcesz używać niestandardowej końcówki w jednym kontrolerze widoku. W przypadku, gdy chcesz go używać na różnych kontrolerach widoku, z których każdy tworzy instancję niestandardowej końcówki dynamicznie, to nie zadziała.
thgc
120

Dziękuję wam wszystkim. Znalazłem sposób na robienie tego, co chciałem.

  1. Stwórz swoją UIViewze IBOutlets potrzebne.
  2. Utwórz xib w IB, zaprojektuj go według własnych upodobań i połącz go w następujący sposób: Właściciel pliku jest z klasy UIViewController(bez niestandardowej podklasy, ale „prawdziwa”). Widok właściciela pliku jest połączony z widokiem głównym, a jego klasa jest zadeklarowana jako ta z kroku 1).
  3. Połącz elementy sterujące za pomocą IBOutlets.
  4. DynamicViewControllerMożna uruchomić swoją logikę do decydowania co zobaczyć / XIb do obciążenia. Po podjęciu decyzji w loadViewmetodzie umieść coś takiego:

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

Otóż ​​to!

Metoda głównego pakietu loadNibNamedzajmie się inicjowaniem widoku i tworzeniem połączeń.

Teraz ViewController może wyświetlać widok lub inny w zależności od danych w pamięci, a ekran „macierzysty” nie musi niepokoić się tą logiką.

geedubb
źródło
2
Mam bardzo podobny problem - po prostu chcę załadować UIView z pliku xib. Te kroki nie działają dla mnie - aplikacja zawiesza się przy „złym dostępie” wkrótce po dodaniu załadowanego widoku jako widoku podrzędnego.
Justicle
2
W przypadku iPhone'a 3.0 użyj objectAtIndex: 0, aby uzyskać pierwszy element. To ulega awarii dla mnie dokładnie tak, jak opisano w Justicle. Masz pomysł, dlaczego?
tba
1
+1 zadziałało idealnie dla mnie. Chciałem dodać wiele widoków, które miały kontroler jednego widoku (używam scenariusza widoku odwróconego, w którym obraca się część widoku) Musiałem zrobić indeks na tablicy 0 (iPhone 3.0)
Aran Mulholland
42
To poprawna odpowiedź, jednak kod powinien zostać zmodyfikowany, aby zapętlał się przez nibViews i sprawdzał klasę każdego obiektu, dzięki czemu z pewnością otrzymujesz właściwy obiekt. objectAtIndex: 1 jest niebezpiecznym założeniem.
M. Ryan,
2
Jasconius ma rację. Powinieneś zapętlić się przez tablicę nibViews w następujący sposób: gist.github.com/769539
leviathan
71

Nie jestem pewien, o czym mówią niektóre odpowiedzi, ale muszę tu podać tę odpowiedź, kiedy następnym razem będę wyszukiwać w Google. Słowa kluczowe: „Jak załadować UIView z końcówki” lub „Jak załadować UIView z NSBundle”.

Oto kod prawie 100% od książki Apress Beginning od iPhone'a 3 (strona 247, „Korzystanie z nowej komórki widoku tabeli”):

- (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];
} 

Zakłada się, że masz UIViewpodklasę o nazwie Blahstalówka, Blahktóra zawiera UIViewklasę ustawioną na Blah.


Kategoria: 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

Szybkie rozszerzenie

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
    }
}

I przykład w użyciu:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
Dan Rosenstark
źródło
2
Jeśli używasz, isKindOfjesteś chroniony przed zmianami struktury pakietu.
Dan Rosenstark,
1
assertBędzie prawdopodobnie lepszym rozwiązaniem. W ten sposób ukryjesz tylko problem.
Sulthan
1
@Sulthan twierdzenie jest z natury inne. Chcę obiekt typu Blah, gdziekolwiek będzie na stalówce . Nie obchodzi mnie, czy został awansowany czy zdegradowany. Jednak w odpowiedzi na twój komentarz dodałem twierdzenie, ale nie jest ono w miejscu forpętli. Uzupełnia to.
Dan Rosenstark,
Możesz wykonać loadFromNib bez parametrów: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([klasa własna]) właściciel: opcje własne: zero]; for (identyfikator obiektu w pakiecie) {if ([object isKindOfClass: [self class]]) {return object; }} return zero; }
JVC
22

Dla wszystkich, którzy muszą zarządzać więcej niż jedną instancją widoku niestandardowego, tj. Kolekcją Outlet , połączyłem i dostosowałem odpowiedzi @Gonso, @AVeryDev i @Olie w ten sposób:

  1. Utwórz niestandardowy MyView : UIViewi ustaw go jako „ klasę niestandardową ” katalogu głównego UIVieww żądanym XIB ; klasa niestandardowa

  2. Utwórz wszystkie gniazda, w których potrzebujesz MyView(zrób to teraz, ponieważ po punkcie 3 IB zaproponuje ci podłączenie gniazd do, UIViewControllera nie do niestandardowego widoku, jak chcemy); niestandardowy punkt sprzedaży

  3. Ustaw UIViewControllerjako „ właściciela pliku ” widoku niestandardowego XIB ; wprowadź opis zdjęcia tutaj

  4. W UIViewControllerdodawać nowe UIViewsdla każdej instancji MyViewchcesz, i podłączyć je do UIViewControllerstworzenia kolekcji Outlet : te poglądy będą działać jako widoki „Okładki” Widok niestandardowy przypadkach; wprowadź opis zdjęcia tutaj

  5. Wreszcie, w viewDidLoadswojej UIViewControllerdodać następujące wiersze:

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
źródło
Cześć ... wiesz, jak to zrobić programowo, tj. wcale nie przy użyciu pliku xib?
X-Coder
19

Użyłbym UINib do utworzenia niestandardowego UIView do ponownego użycia

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

Pliki potrzebne w tym przypadku to MyCustomView.xib , MyCustomViewClass.h oraz MyCustomViewClass.m Uwaga, która [UINib instantiateWithOwner]zwraca tablicę, dlatego należy użyć elementu odzwierciedlającego UIView, którego chcesz ponownie użyć. W tym przypadku jest to pierwszy element.

thgc
źródło
1
Czy nie masz na myśli „customNib” w wierszu 2 zamiast „rankingNib”
Danny,
11

Nie powinieneś ustawiać klasy kontrolera widoku jako podklasy UIVieww Konstruktorze interfejsów. To z pewnością przynajmniej część twojego problemu. Pozostaw to jako UIViewControllerczęść podklasy lub inną niestandardową klasę.

Co do ładowania tylko widok z XIb, byłem przy założeniu, że musiał mieć jakiś kontroler widoku (nawet jeśli nie rozciągają UIViewController, co może być zbyt dużej gramaturze do swoich potrzeb) ustawiony jako plik Właścicielem interfejs Konstruktor, jeśli chcesz go użyć do zdefiniowania interfejsu. Zrobiłem też trochę badań, aby to potwierdzić. Wynika to z faktu, że w przeciwnym razie nie byłoby sposobu na uzyskanie dostępu do żadnego z elementów interfejsu UIViewani nie byłoby sposobu na wywołanie własnych metod w kodzie przez zdarzenia.

Jeśli używasz UIViewControllerjako właściciela pliku dla swoich widoków, możesz po prostu użyć, initWithNibName:bundle:aby go załadować i odzyskać obiekt kontrolera widoku. W IB upewnij się, że ustawiłeś viewujście na widok z interfejsem w Xib. Jeśli używasz innego typu obiektu jako plik Właścicielem, trzeba użyć NSBundle„s loadNibNamed:owner:options:metody, aby załadować stalówkę, przekazując instancję pliku Właścicielem metody. Wszystkie jego właściwości zostaną ustawione poprawnie zgodnie z punktami sprzedaży zdefiniowanymi w IB.

Marc W.
źródło
Czytam książkę Apress „Początek rozwoju iPhone'a: ​​eksploracja iPhone SDK” i omawiali tę metodę w rozdziale 9: Kontrolery nawigacji i widoki tabel. To nie wydawało się zbyt skomplikowane.
Lyndsey Ferguson
Byłbym ciekawy, jak mówią, że zostało zrobione. W tej chwili nie mam kopii tej książki.
Marc W
10

Możesz także użyć parametru initWithNibName UIViewController zamiast loadNibNamed. Uważam, że jest to prostsze.

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

Teraz wystarczy utworzyć MySubView.xib i MySubView.h / m. W MySubView.xib ustaw klasę właściciela pliku na UIViewController i wyświetl klasę na MySubView.

Możesz ustawić i rozmiar subviewużywanego nadrzędnego pliku xib.

Ying
źródło
8

To świetne pytanie (+1), a odpowiedzi były prawie pomocne;) Przepraszam chłopaki, ale miałem dość czasu, prześlizgując się przez to, chociaż zarówno Gonso, jak i AVeryDev dali dobre wskazówki. Mam nadzieję, że ta odpowiedź pomoże innym.

MyVC jest kontrolerem widoku przechowującym wszystkie te rzeczy.

MySubview jest widokiem, który chcemy załadować z Xib

  • W MyVC.xib utwórz widok o MySubViewodpowiednim rozmiarze i kształcie oraz w odpowiednim miejscu.
  • W MyVC.h, mieć

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • W MyVC.m @synthesize mySubView;i nie zapomnij go wypuścić dealloc.

  • W MySubview.h, mam gniazdko / właściwość dla UIView *view(może być niepotrzebne, ale działało dla mnie.) Zsyntetyzuj i wypuść w .m
  • W MySubview.xib
    • ustaw typ właściciela pliku na MySubviewi połącz właściwość widoku ze swoim widokiem.
    • Rozłóż wszystkie bity i połącz z nimi IBOutletzgodnie z potrzebami
  • Z powrotem w MyVC.m, mieć

    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];

Problem polegał na tym, że podpowiedzi w innych odpowiedziach załadowały mój widok z Xib, ale NIE zastąpiły widoku w MyVC (duh!) - musiałem to zamienić sam.

Ponadto, aby uzyskać dostęp do mySubviewmetod, viewwłaściwość w pliku .xib musi być ustawiona na MySubview. W przeciwnym razie powraca jako zwykły stary UIView.

Jeśli istnieje sposób na załadowanie mySubviewbezpośrednio z własnego Xiba, to by się zachwiało, ale to doprowadziło mnie tam, gdzie powinienem być.

Olie
źródło
1
Dzięki tej metodzie, dzięki. Jedną zmianą, którą wprowadziłem, aby umożliwić obsługę widoków zagnieżdżonych, była zmiana [ self.view insertSubview: msView aboveSubview: mySubview]; to [ mySubview.superview insertSubview: msView aboveSubview: mySubview];
Jason
8

To powinno być łatwiejsze. Skończyło się na rozszerzeniu UIViewControlleri dodaniu loadNib:inPlaceholder:selektora. Teraz mogę powiedzieć

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

Oto kod kategorii (robi to samo rigamarole, jak opisano przez Gonso):

@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
źródło
7

W szybkim tempie

W rzeczywistości moim rozwiązaniem tego problemu było załadowanie widoku w viewDidLoad w moim CustonViewController, w którym chciałem użyć takiego widoku:

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

Nie ładuj widoku w loadView()metodzie! Metoda loadView służy do ładowania widoku niestandardowego kontrolera ViewController.

kalafun
źródło
6

Ja też chciałem zrobić coś podobnego, oto co znalazłem: (SDK 3.1.3)

Mam kontroler widoku A (sam będący własnością kontrolera Nav), który ładuje VC B po naciśnięciu przycisku:

W AViewController.m

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

Teraz VC B ma interfejs z Bnib, ale po naciśnięciu przycisku chcę przejść do „trybu edycji”, który ma oddzielny interfejs użytkownika z innej końcówki, ale nie chcę nowego VC dla trybu edycji, Chcę, aby nowa końcówka była skojarzona z moim istniejącym B VC.

Tak więc w BViewController.m (w metodzie naciskania przycisków)

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

Następnie na innym przycisku naciśnij (aby wyjść z trybu edycji):

[editView removeFromSuperview];

i wróciłem do mojego oryginalnego Bniba.

Działa to dobrze, ale zauważ, że mój plik EditMode.nib ma tylko 1 obiekt najwyższego poziomu, obiekt UIView obj. Nie ma znaczenia, czy właściciel pliku w tej stalówce jest ustawiony jako BViewController, czy domyślny obiekt NSObject, ALE upewnij się, że widok ujścia właściciela pliku NIE jest ustawiony na cokolwiek. Jeśli tak, to mam awarię exc_bad_access i xcode kontynuuje ładowanie ramek stosu 6677 pokazujących wewnętrzną metodę UIView wielokrotnie wywoływaną ... więc wygląda jak nieskończona pętla. (The View Outlet JEST jednak ustawiony w moim oryginalnym Bnib)

Mam nadzieję że to pomoże.

Brynjar
źródło
Czytanie między wierszami również mi pomogło.
petert
Zgodnie z informacjami w linku nie należy manipulować kontrolerami widoku w ten sposób.
T. Markle,
6

Stworzyłem kategorię, którą lubię:

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

Następnie zadzwoń w następujący sposób:

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

Użyj nazwy stalówki, jeśli nazwa stalówki jest inna niż nazwa twojej klasy.

Aby zastąpić go w podklasach dla dodatkowego zachowania, może wyglądać następująco:

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

Dla użytkownika Swift z opcją do wyboru:

  1. Utwórz niestandardową UIViewpodklasę i pliki xib , które nazwiemy na podstawie własnej nazwy klasy: w naszym przypadku MemeView. W klasie Meme View pamiętaj, aby zdefiniować ją jako możliwą do @IBDesignableprzypisania za pomocą atrybutu przed deklaracją klasy
  2. Pamiętaj, aby ustawić właściciela pliku w xib za pomocą naszej niestandardowej UIViewpodklasy w panelu Inspektora Indetity

    wprowadź opis zdjęcia tutaj

  3. W pliku xib możemy teraz budować nasz interfejs, tworzyć ograniczenia, tworzyć gniazda, akcje itp. wprowadź opis zdjęcia tutaj

  4. Musimy zaimplementować kilka metod w naszej klasie niestandardowej, aby po zainicjowaniu otworzyć xib

    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. W naszej klasie niestandardowej możemy również zdefiniować niektóre właściwości podlegające inspekcji, aby mieć nad nimi pełną kontrolę z poziomu konstruktora interfejsu

    @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!

}

Kilka przykładów:
wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Jeśli musimy dodać więcej informacji, gdy widok jest wyświetlany w serii ujęć lub innym Xib, aby to zrobić, możemy to zaimplementować prepareForInterfaceBuilder(), ta metoda zostanie wykonana tylko podczas otwierania pliku w narzędziu do tworzenia interfejsów. Jeśli zrobiłeś wszystko, co napisałem, ale nic nie działa, jest to sposób na debugowanie głównego widoku poprzez dodanie punktów przerwania w jego implementacji. Oto hierarchia widoków. wprowadź opis zdjęcia tutaj
Zobacz hierarchię

Mam nadzieję, że to pomoże w pobraniu pełnej próbki tutaj

Andrea
źródło
Pełny artykuł można zobaczyć na moim blogu cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah, ale już opublikowałem odpowiednią część.
Andrea,
let nib = loadNib(nibName)to jest instancja XibbedView, jeśli wywołasz `` addSubview (nib) `` `, to dodajesz jedną instancję XibbedView do innej instancji XibbedView?
William Hu
zgadzam się w tej kwestii. Podczas próby zobaczenia go z „Hierarchii widoku debugowania” wydaje się, że XibbedView zawiera XibbedView. Możesz to zobaczyć.
William Hu
@WilliamHu, jeśli nie załadujesz dostarczonego przykładu, zobaczysz, że MemeView.xib jest tylko instancją UIView z wieloma podviewami, uchwytem pliku jest MemeView, który jest podklasą XibbedView. Zatem jedynym wystąpieniem XibbedView jest tylko MemeView, który ładuje plik xib, w którym główny widok jest tylko widokiem
Andrea
No dobrze. Przetestuję to. Dziękuję Ci!
William Hu
4

Uważam, że ten wpis na blogu Aarona Hillegassa (autor, instruktor, ninja Cocoa) jest bardzo pouczający. Nawet jeśli nie zastosujesz jego zmodyfikowanego podejścia do ładowania plików NIB przez wyznaczony inicjator, prawdopodobnie przynajmniej lepiej zrozumiesz trwający proces. Ostatnio stosuję tę metodę, aby odnieść wielki sukces!

Meltemi
źródło
Link nie działa. Wydaje
joelliusp
3

Poprzednia odpowiedź nie uwzględnia zmiany w strukturze NIB (XIB), która nastąpiła między wersją 2.0 a 2.1 zestawu SDK iPhone'a. Treść użytkownika zaczyna się teraz od indeksu 0 zamiast 1.

Możesz użyć makra 2.1, które jest ważne dla wszystkich wersji 2.1 i nowszych (to dwa znaki podkreślenia przed 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;

Używamy techniki podobnej do tej w większości naszych aplikacji.

Barney

Barney Mattox
źródło
2
Barney: „Poprzednia odpowiedź” nie ma znaczenia w przypadku przepełnienia stosu, ponieważ odpowiedzi zmieniają pozycję na podstawie głosowania. Lepiej odnieść się do „odpowiedzi Freda” lub „odpowiedzi Barneya” lub „odpowiedzi Olie”.
Olie,
3

Miałem powód, aby zrobić to samo (programowo ładować widok z pliku XIB), ale musiałem to zrobić całkowicie w kontekście podklasy podklasy a UIView(tj. Bez angażowania kontrolera widoku w jakikolwiek sposób). Aby to zrobić, stworzyłem tę metodę narzędzia:

+ (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;
} 

Następnie wywołuję to z initWithFramemetody mojej podklasy w następujący sposób:

- (id)initWithFrame:(CGRect)frame {

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

Wysłany do ogólnego zainteresowania; jeśli ktoś zauważy jakiekolwiek problemy bez zrobienia tego w ten sposób, proszę dać mi znać.

MusiGenesis
źródło
Wdrożyłem twój kod, ale w chwili inicjalizacji jest on poza zakresem. Zrobiłem twoją metodę statyczną w klasie Utilities. Zrobiłem pliki h / m o nazwie MyView (podklasa z UIView) i xib o tej samej nazwie. W IB ustawiam właściciela plików xib ORAZ widok na „MyView”. W debuggerze jest to poza zakresem. Czy brakuje mi czegoś w IB?
TigerCoding
Proszę spojrzeć na moje nowe pytanie SO tutaj: stackoverflow.com/questions/9224857/…
TigerCoding
self = [Narzędzia initWithNibName: @ "XIB1" withSelf: self]; Przechodzisz na siebie, kiedy nie jest jeszcze ustawiony. Czy ten kod nie jest taki sam jak ten: self = [Narzędzia initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt,
@Javy: mój przykładowy kod może nie działać z ARC - nie testowałem go z włączoną funkcją ARC.
MusiGenesis
3

Oto sposób, aby to zrobić w Swift (obecnie pisząc Swift 2.0 w XCode 7 beta 5).

Z UIViewpodklasy ustawionej jako „Klasa niestandardowa” w Konstruktorze interfejsów utwórz metodę taką jak ta (moja podklasa nazywa się 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
}

Następnie możesz to tak nazwać:

let recordingFooterView = RecordingFooterView.loadFromNib()

Johannes
źródło
2

Żadna z odpowiedzi nie wyjaśnia, jak utworzyć samodzielny XIB, który jest źródłem tego pytania. Nie ma Xcode 4opcji „Utwórz nowy plik XIB”.

Aby to zrobić

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

Może się to wydawać proste, ale może zaoszczędzić kilka minut na szukanie go, ponieważ słowo „XIB” nigdzie się nie pojawia.

Whitneyland
źródło
1

@AVeryDev

6) Aby dołączyć załadowany widok do widoku kontrolera widoku:

[self.view addSubview:myViewFromNib];

Przypuszczalnie konieczne jest usunięcie go z widoku, aby uniknąć wycieków pamięci.

Aby to wyjaśnić: kontroler widoku ma kilka IBOutletów, z których niektóre są połączone z elementami w oryginalnym pliku końcówki (jak zwykle), a niektóre z elementami z załadowanej końcówki. Obie stalówki mają tę samą klasę właściciela. Załadowany widok nakłada się na oryginalny.

Wskazówka: ustaw krycie widoku głównego w załadowanej wkładce na zero, wtedy nie zasłoni elementów z oryginalnej końcówki.

Matt
źródło
warto uważać, ale dodanie widoku jako widoku podrzędnego automatycznie usuwa go z poprzedniego elementu nadrzędnego, więc nie powinien przeciekać.
averydev,
1

Po spędzeniu wielu godzin opracowałem następujące rozwiązanie. Wykonaj następujące kroki, aby utworzyć niestandardowy UIView.

1) Utwórz klasę myCustomView odziedziczoną z UIView .
wprowadź opis zdjęcia tutaj

2) Utwórz .xib o nazwie myCustomView .
wprowadź opis zdjęcia tutaj

3) Zmień klasę UIView w pliku .xib, przypisz tam klasę myCustomView .
wprowadź opis zdjęcia tutaj

4) Utwórz IBOutlety
wprowadź opis zdjęcia tutaj

5) Załaduj .xib do myCustomView * customView . Użyj następującego przykładowego kodu.

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

Uwaga: dla tych, którzy nadal napotykają problemy, mogą komentować, podam im link do przykładowego projektu z myCustomView

Aamir
źródło
1

Najkrótsza wersja:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
Denis Kutlubaev
źródło
0

Mam konwencję nazywania xibów z widokami w nich takimi samymi jak widok. Tak samo jak w przypadku kontrolera widoku. Nie muszę wtedy zapisywać nazw klas w kodzie. Ładuję UIView z pliku stalówki o tej samej nazwie.

Przykład dla klasy o nazwie MyView.

  • Utwórz plik stalówki o nazwie MyView.xib w Konstruktorze interfejsów
  • W Konstruktorze interfejsów dodaj UIView. Ustaw jego klasę na MyView. Dostosuj do treści swojego serca, połącz zmienne instancji MyView z widokami podrzędnymi, do których możesz chcieć uzyskać dostęp później.
  • W swoim kodzie utwórz nowy MyView w następujący sposób:

    MyView * myView = [MyView nib_viewFromNibWithOwner: owner];

Oto kategoria tego:

@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 {

}

Następnie łączyłem przyciski z działaniami z kontrolera, którego używam, i umieszczałem rzeczy na etykietach, używając gniazd w mojej podklasie widoku niestandardowego.

n13
źródło
0

Aby programowo załadować widok ze stalówki / xiba w 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 Cis
źródło
-1

W końcu dodałem kategorię do UIView dla tego:

 #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

wyjaśnienie tutaj: viewcontroller zmniejsza ładowanie widoku w iOS i Mac

gngrwzrd
źródło
Hmm. Co jest z tagiem?
n13
wiedzieć, który obiekt najwyższego poziomu zachować. w tym czasie było to konieczne, ale prawdopodobnie mógłbyś teraz wyjąć środki, aby zastosować się do ARC.
gngrwzrd