Prawidłowy sposób ładowania końcówki dla podklasy UIView

98

Zdaję sobie sprawę, że to pytanie zostało już zadane, ale odpowiedzi są sprzeczne i jestem zdezorientowany, więc proszę, nie pal mnie.

Chcę mieć UIViewpodklasę wielokrotnego użytku w całej mojej aplikacji. Chcę opisać interfejs za pomocą pliku nib.

Teraz powiedzmy, że jest to widok wskaźnika ładowania ze wskaźnikiem aktywności. Chciałbym na jakimś zdarzeniu utworzyć wystąpienie tego widoku i animować go do widoku kontrolera widoku. Mógłbym opisać interfejs widoku bez problemu programowo, tworząc elementy programowo i ustawiając ich ramkę wewnątrz metody init itp.

Jak mogę to zrobić za pomocą stalówki? Utrzymanie rozmiaru podanego w konstruktorze interfejsu bez konieczności ustawiania ramki.

Udało mi się to zrobić w ten sposób, ale jestem pewien, że jest źle (to tylko widok z selektorem):

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

Ale nadpisuję siebie, a kiedy go utworzę:

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

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

Muszę ręcznie ustawić ramę, zamiast odbierać ją od IB.

EDYCJA: Chcę utworzyć ten niestandardowy widok w kontrolerze widoku i mieć dostęp do sterowania elementami widoku. Nie chcę nowego kontrolera widoku.

Dzięki

EDYCJA: Nie wiem, czy to najlepsza praktyka, na pewno nie, ale tak to zrobiłem:

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) {
                        }];

Nadal potrzebna jest właściwa praktyka

Czy powinno to być zrobione przy użyciu kontrolera widoku i inicjalizacji z nazwą końcówki? Czy powinienem ustawić końcówkę w jakiejś metodzie inicjalizacji UIView w kodzie? A może to, co zrobiłem, jest w porządku?

Adam Waite
źródło
Ale czy pierwsza linia kodu nie jest taka sama jak ta, której użyłeś w pierwotnym pytaniu initWithDataSource? W każdym razie to zadziała, nawet jeśli podasz właściciela jako „Zero”.
Rakesh
Warto zauważyć, że obecnie w 99,99999% przypadków można by używać widoków kontenerów , które są teraz używane wszędzie i zawsze w iOS. artykuł
instruktażowy
uwaga boczna, że ​​możesz zamienić [NSString stringWithFormat: @ "% @", [FSASelectView class]] na NSStringFromClass ([FSASelectView klasa])
naomimichiko

Odpowiedzi:

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

Używam tego do inicjalizacji widoków niestandardowych wielokrotnego użytku, które mam.


Zauważ, że możesz użyć "firstObject" na końcu, jest trochę czystszy. „firstObject” to poręczna metoda dla NSArray i NSMutableArray.

Oto typowy przykład ładowania xib do użycia jako nagłówek tabeli. W Twoim pliku YourClass.m

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

Zwykle w programie TopArea.xibnależy kliknąć opcję Właściciel pliku i ustawić właściciela pliku na YourClass . Wtedy faktycznie w YourClass.h miałbyś właściwości IBOutlet. W programie TopArea.xibmożesz przeciągać kontrolki do tych gniazd.

Nie zapominaj, że w programie TopArea.xibmoże być konieczne kliknięcie samego widoku i przeciągnięcie go do jakiegoś gniazdka, aby w razie potrzeby mieć nad nim kontrolę. (Bardzo cenną wskazówką jest to, że robiąc to dla wierszy komórek tabeli, absolutnie musisz to zrobić - musisz połączyć sam widok z odpowiednią właściwością w kodzie.)

Premsuraj
źródło
nie ma metody initWithNibName: dla UIView, jest tylko dla UIViewController
meronix
To dla kontrolera widoku, chcę użyć UIView i mieć kontroler, który go tworzy, aby był jego właścicielem.
Adam Waite,
Przepraszam, w pośpiechu skopiowałem zły kod, zaktualizowałem odpowiedź, zobacz.
Premsuraj,
Zaznaczę to jako poprawne. Nie wiem, czy to najlepsza praktyka, ale działa.
Adam Waite,
@Joe Blow Przepraszamy za nierówność, ale po co miałoby to robić: „Nie zapominaj, że w TopArea.xib może być konieczne kliknięcie samego widoku i przeciągnięcie go do jakiegoś gniazdka, aby mieć nad nim kontrolę , Jeśli to konieczne." Czy możesz zamiast tego po prostu użyć myViewObject, aby uzyskać dostęp do podstawowego widoku, ponieważ jest to sam UIView? Dlaczego potrzebna jest nieruchomość?
user1686342
39

Jeśli chcesz zachować swoją CustomViewi jego xibniezależność File's Owner, wykonaj następujące kroki

  • Pozostaw File's Ownerpole puste.
  • Kliknij rzeczywisty widok w swoim xibpliku CustomViewi ustaw go Custom Classjako CustomView(nazwa własnej klasy widoku niestandardowego)
  • Dodaj IBOutletw .hpliku swojego widoku niestandardowego.
  • W .xibpliku widoku niestandardowego kliknij widok i wejdź Connection Inspector. Tutaj znajdziesz wszystkie swoje IBOutlety, które zdefiniujesz w .hpliku
  • Połącz je z odpowiednim widokiem.

w .mpliku swojej CustomViewklasy nadpisz initmetodę w następujący sposób

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

Teraz, gdy chcesz załadować swój CustomView, użyj następującego wiersza kodu [[CustomView alloc] init];

Ans
źródło
1
Od iOS 9 jest to jedyne rozwiązanie wymienione na tej stronie, które faktycznie działa dla mnie (inne rozwiązania powodują awarię).
lifjoy
Zwróć uwagę, że to podejście nie jest kompatybilne z ładowaniem niestandardowego widoku z istniejącego XIB, ponieważ nie wywołuje -init. Zamiast tego zadzwoni initWithFrame:i -awakeFromNib. Jeśli napiszesz ten sam kod, aby załadować swój widok, jak w -initmetodzie, wejdziesz w nieskończoną pętlę.
Dustt
25

Wykonaj następujące kroki

  1. Utwórz klasę o nazwie MyView .h / .m typu UIView.
  2. Utwórz xib o tej samej nazwie MyView.xib.
  3. Teraz zmień klasę File Owner na UIViewControllerfrom NSObjectin xib. Zobacz obrazek poniżej wprowadź opis obrazu tutaj
  4. Połącz widok właściciela pliku ze swoim widokiem. Zobacz obrazek poniżej wprowadź opis obrazu tutaj

  5. Zmień klasę swojego widoku na MyView. Tak samo jak 3.

  6. Kontrolki miejsca tworzą IBOutlets.

Oto kod do załadowania widoku:

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

Mam nadzieję, że to pomoże.

Wyjaśnienie :

UIViewControllersłuży do ładowania twojego xib i widoku, który UIViewControllerma, jest faktycznie MyViewprzypisany w MyView xib ..

Demo Zrobiłem chwycić demo tutaj

iphonic
źródło
Dlaczego ludzie nie głosują, źle? Jeszcze nie próbowałem, ale wygląda na to, że nie zrobi tego, co chcę.
Adam Waite,
Głosowałem w dół, ale to dlatego, że źle odczytałem twoją odpowiedź (sądziłem, że przypisujesz podklasę UIView jako właściciela pliku). Przepraszam za to.
Rakesh
2
W swoim pliku xib zignoruj ​​właściciela plików. Dzięki temu możesz uniknąć konieczności tworzenia kontrolera UIViewController, wykonując po prostu MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk
1
@LightningStryk Tak, oczywiście, ale to po prostu inny sposób.
iphonic
1
Ma na celu utworzenie podklasy UIView wielokrotnego użytku, a nie użycie UIViewController do tego celu.
arielyz
11

Odpowiadając na moje własne pytanie o 2 lub coś później tutaj, ale ...

Używa rozszerzenia protokołu, więc możesz to zrobić bez dodatkowego kodu dla wszystkich klas.

/*

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

W Swift:

Na przykład nazwa Twojej klasy niestandardowej to InfoView

Najpierw tworzysz pliki InfoView.xibi InfoView.swifttak:

import Foundation
import UIKit

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

Następnie ustaw File's Ownersię UIViewControllerpodoba:

wprowadź opis obrazu tutaj

Zmień nazwę Viewna InfoView:

wprowadź opis obrazu tutaj

Kliknij prawym przyciskiem myszy File's Owneri połącz swoje viewpole z InfoView:

wprowadź opis obrazu tutaj

Upewnij się, że nazwa zajęć to InfoView:

wprowadź opis obrazu tutaj

Po tym możesz bez problemu dodać akcję do przycisku w swojej niestandardowej klasie:

wprowadź opis obrazu tutaj

I użycie tej niestandardowej klasy w MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
vkalit
źródło
2

Cóż, możesz albo zainicjować xib używając kontrolera widoku i użyć viewController.view. lub zrób to tak, jak to zrobiłeś. Jedynie tworzenie UIViewpodklasy jako kontrolera UIViewjest złym pomysłem.

Jeśli nie masz żadnych gniazd z widoku niestandardowego, możesz bezpośrednio użyć UIViewControllerklasy, aby ją zainicjować.

Aktualizacja: W twoim przypadku:

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

W przeciwnym razie musisz zrobić niestandardowy UIViewController(aby uczynić go właścicielem pliku, aby gniazda były odpowiednio podłączone).

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

Gdziekolwiek chcesz dodać widok, możesz użyć.

[parentView addSubview:yCustCon.view];

Jednak przekazanie innego kontrolera widoku (już używanego dla innego widoku) jako właściciela podczas ładowania xib nie jest dobrym pomysłem, ponieważ właściwość widoku kontrolera zostanie zmieniona, a gdy chcesz uzyskać dostęp do oryginalnego widoku, nie mieć do niej odniesienie.

EDYCJA: Napotkasz ten problem, jeśli ustawiłeś swój nowy xib z właścicielem pliku jako tę samą główną UIViewControllerklasę i powiązałeś właściwość widoku z nowym widokiem xib.

to znaczy;

  • YourMainViewController - zarządza - mainView
  • CustomView - musi ładować z xib, gdy jest to wymagane.

Poniższy kod spowoduje później zamieszanie, jeśli napiszesz go w środku, widok został załadowany YourMainViewController. self.viewDzieje się tak, ponieważ od tego momentu będzie odnosić się do Twojego widoku niestandardowego

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Rakesh
źródło
Ok, więc w zasadzie najlepsza praktyka mówi, że każdy UIView powinien mieć powiązany kontroler?
Adam Waite,
Niekoniecznie. Ale ładowanie widoku z osobnego xib byłoby bezproblemowe. Dokumentacja Apple sprzed IOS5 mówi, że jeden kontroler widoku nie powinien być używany do sterowania więcej niż jedną sceną (xib) i odwrotnie.
Rakesh
XIB ma właściwie tylko rozmiar widoku alarmowego
Adam Waite,
Osobiście uważam, że od złożoności kodu / xib zależy, czy musisz utworzyć nowy kontroler, czy nie. Jeśli potrafisz pomyślnie rozwiązać spowodowane problemy i nie patrzysz na przyszłe modyfikacje, nie stanowi to problemu. Ale utworzenie osobnego kontrolera widoku rozwiązałoby wiele problemów. A ponieważ w twoim przypadku jest to tylko wskaźnik aktywności, możesz łatwo użyć obiektu UIViewController (jak wspomniano w odpowiedzi). Zaktualizuję swoją odpowiedź, aby to zrobić odpowiednio.
Rakesh
1
Więc mogę narysować interfejs w konstruktorze interfejsów, zamiast robić to programowo. Nie ma problemu z robieniem tego programowo, chcę tylko wiedzieć, jak podklasować UIView i nadać mu stalówkę! To nie powinno być trudne.
Adam Waite,