Tworzenie UIView wielokrotnego użytku z XIB (i ładowanie ze scenorysu)

81

OK, w StackOverflow są dziesiątki postów na ten temat, ale żaden nie jest szczególnie jasny na temat rozwiązania. Chciałbym utworzyć niestandardowy UIViewz towarzyszącym mu plikiem xib. Wymagania są następujące:

  • Brak osobnych UIViewController- całkowicie samodzielna klasa
  • Gniazda w klasie, aby umożliwić mi ustawienie / pobranie właściwości widoku

Moje obecne podejście do tego jest następujące:

  1. Nadpisanie -(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. Utwórz wystąpienie programowo, używając -(id)initWithFrame:w moim kontrolerze widoku

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

Działa to dobrze (chociaż nigdy nie wywołujesz [super init]i proste ustawienie obiektu przy użyciu zawartości załadowanej stalówki wydaje się nieco podejrzane - jest tutaj rada, aby dodać podwidok w tym przypadku, który również działa dobrze). Chciałbym jednak móc również utworzyć wystąpienie widoku z serii ujęć. Więc mogę:

  1. Umieść UIVieww widoku rodzica w serii ujęć
  2. Ustaw jego klasę niestandardową na MyCustomView
  3. Zastąp -(id)initWithCoder:- kod, który widziałem najczęściej, pasuje do wzorca takiego jak:

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

Oczywiście to nie działa, ponieważ niezależnie od tego, czy używam powyższego podejścia, czy też tworzę wystąpienia programowo, oba kończą się rekurencyjnie wywołaniami -(id)initWithCoder:po wprowadzeniu -(void)initializeSubviewsi załadowaniu nib z pliku.

Kilka innych pytań SO dotyczy tego, na przykład tutaj , tutaj , tutaj i tutaj . Jednak żadna z udzielonych odpowiedzi w sposób zadowalający nie rozwiązuje problemu:

  • Powszechną sugestią wydaje się być osadzenie całej klasy w kontrolerze UIViewController i ładowanie końcówki w tym miejscu, ale wydaje mi się to nieoptymalne, ponieważ wymaga dodania kolejnego pliku jako opakowania

Czy ktokolwiek mógłby udzielić porady, jak rozwiązać ten problem i uzyskać działające gniazdka w niestandardowy sposób UIViewprzy minimalnym zamieszaniu / bez cienkiego opakowania kontrolera? A może istnieje alternatywny, czystszy sposób robienia rzeczy z minimalnym kodem standardowym?

Ken Chatfield
źródło
1
Czy kiedykolwiek otrzymałeś na to satysfakcjonującą odpowiedź? W tej chwili walczę o to. Jak wspomniałeś, wszystkie inne odpowiedzi nie wydają się wystarczająco dobre. Zawsze możesz sam odpowiedzieć na to pytanie, jeśli dowiedziałeś się czegoś w ciągu ostatnich kilku miesięcy.
Mike Meyers
13
Dlaczego tworzenie widoków wielokrotnego użytku w iOS jest tak trudne?
zegarmistrz
1
W rzeczywistości odpowiedź, do której odsyłasz, wykorzystuje dokładnie to samo podejście (chociaż twoja odpowiedź nie zawiera funkcji inicjalizacji z funkcji rect, co oznacza, że ​​można ją zainicjować tylko z storyboardu, a nie programowo)
Ken Chatfield
1
jeśli chodzi o tę bardzo starą kontrolę jakości, Apple w końcu przedstawił ODNIESIENIA STORYBOARDOWE ... developer.apple.com/library/ios/recipes/ ... ... więc to wszystko, uff!
Fattie

Odpowiedzi:

13

Twój problem dzwoni loadNibNamed:z (potomka) initWithCoder:. loadNibNamed:połączenia wewnętrzne initWithCoder:. Jeśli chcesz zastąpić koder scenorysów i zawsze ładować implementację xib, sugeruję następującą technikę. Dodaj właściwość do swojej klasy widoku iw pliku XIB ustaw ją na z góry określoną wartość (w atrybutach czasu wykonywania zdefiniowanych przez użytkownika). Teraz po wywołaniu [super initWithCoder:aDecoder];sprawdź wartość właściwości. Jeśli jest to z góry określona wartość, nie dzwoń [self initializeSubviews];.

Więc coś takiego:

-(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
źródło
Dzięki @LeoNatan! Akceptuję tę odpowiedź, ponieważ jest to najlepsze rozwiązanie problemu, zgodnie z pierwotnym stwierdzeniem. Zwróć jednak uwagę, że nie jest to już możliwe w Swift - dodałem kilka oddzielnych uwag na temat możliwego obejścia w tym przypadku.
Ken Chatfield,
@KenChatfield Zauważyłem to w moim podprojekcie Swift i zirytowało mnie to. Nie jestem pewien, co myślą, ponieważ bez tego wiele wewnętrznej implementacji Cocoa / Cocoa Touch jest niemożliwe w Swift. Założę się, że pojawią się dynamiczne funkcje, kiedy będą mieli czas, aby skupić się na funkcjach, a nie na błędach. Swift w ogóle nie jest gotowy, a narzędzia programistyczne są najgorszymi przestępcami.
Leo Natan,
Tak, masz rację, Swift ma teraz wiele ostrych krawędzi. To powiedziawszy, wygląda na to, że przypisywanie bezpośrednio do siebie było trochę hackem, więc cieszę się, że kompilator ostrzega teraz przed takimi rzeczami (to ściślejsze sprawdzanie typów wydaje się być jedną z fajnych rzeczy w język). Jednak masz rację, że sprawia, że ​​łączenie niestandardowych widoków z xibs jest bardzo niechlujne i trochę niezadowalające. Miejmy nadzieję, mówiąc, że kiedy już skończą rozwiązywać błędy, zobaczymy kilka bardziej dynamicznych funkcji, które pomogą nam trochę więcej z takimi rzeczami!
Ken Chatfield,
To nie jest hack, tak działają klastry klas. I rzeczywiście, nie ma problemu technicznego, który pozwoliłby na zwrócenie zamiast tego obiektu będącego podklasą klasy zwracanej. W tej chwili jeden z kamieni węgielnych Cocoa i Cocoa Touch - klastrów klasowych - jest niemożliwy do zrealizowania. Straszny. Niektórych frameworków, takich jak Core Data, nie można zaimplementować w Swift, co czyni go bezużytecznym w większości moich zastosowań.
Leo Natan
1
Jakoś to nie zadziałało (SDK iOS8.1). Ustawiłem restorationIdentifier w XIB zamiast atrybutu runtime, niż zadziałało. Np. Ustawiłem "MyViewRestorationID" w xib, niż w initWithCoder: Sprawdziłem, że! [[Self restorationIdentifier] isEqualToString: @ "MyViewRestorationID"]
ingaham
26

Zwróć uwagę, że ta kontrola jakości (jak wiele innych) ma tak naprawdę znaczenie historyczne.

Obecnie Od lat w iOS wszystko jest tylko widokiem kontenera. Pełny samouczek tutaj

(Rzeczywiście, Apple w końcu dodało Referencje Storyboard , jakiś czas temu, co znacznie ułatwia.)

Oto typowa plansza z widokami kontenerów na całym świecie. Wszystko jest widokiem kontenera. Po prostu tworzysz aplikacje.

wprowadź opis obrazu tutaj

(Ciekawostką jest, że odpowiedź KenCa pokazuje dokładnie, w jaki sposób ładowano xib do pewnego rodzaju widoku opakowującego, ponieważ tak naprawdę nie można „przypisać do siebie”).

Fattie
źródło
Problem z tym polega na tym, że otrzymasz wiele kontrolerów ViewControllerów dla wszystkich osadzonych widoków treści.
Bogdan Onu
Cześć @BogdanOnu! Powinieneś mieć wiele, wiele, wiele kontrolerów widoku. Z „najmniejszej” rzeczy - powinieneś mieć kontroler widoku.
Fattie
2
Jest to bardzo przydatne - dzięki @JoeBlow. Korzystanie z widoków kontenerów zdecydowanie wydaje się alternatywnym podejściem i prostym sposobem na uniknięcie wszystkich komplikacji związanych z bezpośrednią obsługą xibs. Jednak nie wydaje się w 100% satysfakcjonującej alternatywy dla tworzenia komponentów wielokrotnego użytku do dystrybucji / wykorzystania w projektach, ponieważ wymaga bezpośredniego osadzenia całego projektu interfejsu użytkownika w scenorysie.
Ken Chatfield,
3
Mam mniejszy problem z użyciem dodatkowego ViewControllera w tym przypadku, ponieważ zawierałby on tylko logikę programu, która w innym przypadku należałaby do niestandardowej klasy View w przypadku xib, ale ścisłe powiązanie z storyboardem oznacza, że ​​jestem nie jestem pewien, czy widoki kontenerów mogą całkowicie rozwiązać ten problem. Być może w większości praktycznych sytuacji widoki są specyficzne dla projektu, więc jest to najlepsze i najbardziej „standardowe” rozwiązanie, ale jestem zaskoczony, że nadal nie ma łatwego sposobu pakowania niestandardowych widoków do wykorzystania w scenorysie. Impulsem mojego programisty do dzielenia i podboju na pojedyncze komponenty jest swędzenie;)
Ken Chatfield,
1
Ponadto, wraz z wprowadzeniem renderowania na żywo niestandardowych podklas UIView w Xcode 6, nie jestem pewien, czy kupuję założenie, że tworzenie widoków przy użyciu XIBS w ten sposób jest teraz przestarzałe
Ken Chatfield,
24

Dodam to jako osobny post, aby zaktualizować sytuację w wydaniu Swift. Podejście opisane przez LeoNatana doskonale sprawdza się w Objective-C. Jednak bardziej rygorystyczne kontrole czasu kompilacji zapobiegają selfprzypisywaniu go podczas ładowania z pliku xib w języku Swift.

W rezultacie nie ma innego wyjścia, jak tylko dodać widok załadowany z pliku xib jako widok podklasy niestandardowej podklasy UIView, zamiast całkowicie zastępować siebie. Jest to analogiczne do drugiego podejścia przedstawionego w pierwotnym pytaniu. Ogólny zarys klasy w Swift korzystającej z tego podejścia jest następujący:

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

}

Wadą tego podejścia jest wprowadzenie dodatkowej nadmiarowej warstwy w hierarchii widoków, która nie istnieje w przypadku podejścia nakreślonego przez LeoNatana w Objective-C. Jednak może to być odebrane jako zło konieczne i produkt podstawowego sposobu projektowania rzeczy w Xcode (nadal wydaje mi się szalone, że tak trudno jest połączyć niestandardową klasę UIView z układem interfejsu użytkownika w sposób, który działa konsekwentnie na obu scenorysach iz kodu) - zastępowanie selfhurtowego w inicjatorze nigdy wcześniej nie wydawało się szczególnie interpretowalnym sposobem robienia rzeczy, chociaż zasadniczo posiadanie dwóch klas widoku na widok też nie wydaje się takie świetne.

Niemniej jednak szczęśliwym rezultatem tego podejścia jest to, że nie musimy już ustawiać niestandardowej klasy widoku na nasz plik klasy w konstruktorze interfejsu, aby zapewnić poprawne zachowanie podczas przypisywania do self, więc rekurencyjne wywołanie init(coder aDecoder: NSCoder)przy wydawaniu loadNibNamed()jest zepsute (nie ustawiając niestandardowa klasa w pliku xib, init(coder aDecoder: NSCoder)zamiast tego zostanie wywołana zwykła wanilia UIView, a nie nasza niestandardowa wersja).

Mimo że nie możemy bezpośrednio dostosowywać klas do widoku przechowywanego w xib, nadal jesteśmy w stanie połączyć ten widok z naszą „nadrzędną” podklasą UIView za pomocą gniazd / akcji itp. Po ustawieniu właściciela pliku widoku na naszą niestandardową klasę:

Ustawianie właściwości właściciela pliku widoku niestandardowego

Film demonstrujący implementację takiej klasy widoku krok po kroku przy użyciu tego podejścia można znaleźć na poniższym filmie .

Ken Chatfield
źródło
Cześć Joe - dziękuję za twoje komentarze, to bardzo pogrubione (jak w wielu innych twoich komentarzach!) Jak powiedziałem w odpowiedzi na twoją odpowiedź, zgadzam się, że w większości sytuacji widoki kontenerów są prawdopodobnie najlepszym podejściem, ale w takich przypadkach Tam, gdzie widoki muszą być wykorzystywane w różnych projektach (lub rozpowszechniane), przynajmniej dla mnie ma sens, a innym wydaje się, że istnieje alternatywa. Możesz osobiście uważać, że to zły styl, ale być może możesz po prostu pozwolić wielu innym postom sugerującym, jak można to zrobić w celach informacyjnych, i pozwolić ludziom ocenić samodzielnie. Oba podejścia wydają się przydatne.
Ken Chatfield,
Dzięki za to. Próbowałem wielu różnych podejść w Swift bez powodzenia, dopóki nie posłuchałem twojej rady dotyczącej pozostawienia klasy stalówki jako UIView. Zgadzam się, że to szaleństwo, że Apple nigdy nie ułatwiało tego, a teraz jest to praktycznie niemożliwe. Pojemnik nie zawsze jest odpowiedzią.
Echelon
16

KROK 1. Wymiana selfz Storyboard

Wymiana selfw initWithCoder:sposób nie powiedzie się następujący błąd.

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

Zamiast tego możesz zamienić zdekodowany obiekt na awakeAfterUsingCoder:(nie awakeFromNib). lubić:

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

KROK 2. Zapobieganie wywołaniom rekurencyjnym

Oczywiście powoduje to również problem z połączeniami rekurencyjnymi. (dekodowanie scenorysu -> awakeAfterUsingCoder:-> loadNibNamed:->awakeAfterUsingCoder: -> loadNibNamed:-> ...)
Więc musisz sprawdzić, czy bieżący awakeAfterUsingCoder:jest wywoływany w procesie dekodowania Storyboard lub w procesie dekodowania XIB. Możesz to zrobić na kilka sposobów:

a) Użyj prywatnego @property który jest ustawiony tylko w NIB.

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

i ustaw „User Defined Runtime Attributes” tylko w „MyCustomView.xib”.

Plusy:

  • Żaden

Cons:

  • Po prostu nie działa: setXib:zostanie wywołany PO awakeAfterUsingCoder:

b) Sprawdź, czy self ma jakieś podglądy

Zwykle masz podglądy w xib, ale nie w scenorysie.

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

Plusy:

  • Żadnej sztuczki w Interface Builder.

Cons:

  • Nie możesz mieć podglądów podrzędnych w swoim Storyboard.

c) Ustaw statyczną flagę podczas loadNibNamed:rozmowy

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

Plusy:

  • Prosty
  • Żadnej sztuczki w Interface Builder.

Cons:

  • Niezabezpieczone: statyczna wspólna flaga jest niebezpieczna

d) Użyj prywatnej podklasy w XIB

Na przykład zadeklaruj _NIB_MyCustomViewjako podklasę klasy MyCustomView. I używaj _NIB_MyCustomViewzamiast MyCustomVieww swoim 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

Plusy:

  • Brak wyraźnego ifwMyCustomView

Cons:

  • Poprzedzając _NIB_tricka w XIb konstruktora Interface
  • stosunkowo więcej kodów

e) Użyj podklasy jako symbolu zastępczego w Storyboard

Podobnie jak w przypadku d)użycia podklasy w Storyboard, oryginalnej klasy w XIB.

Tutaj deklarujemy MyCustomViewProtojako podklasę 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

Plusy:

  • Bardzo bezpieczny
  • Czysty; Brak dodatkowego kodu w MyCustomView.
  • Brak wyraźnego ifsprawdzenia tak samo jakd)

Cons:

  • Musisz użyć podklasy w scenorysie.

Myślę, że e)to najbezpieczniejsza i najczystsza strategia. Więc przyjmujemy to tutaj.

KROK 3. Kopiuj właściwości

Po loadNibNamed:w „awakeAfterUsingCoder:” musisz skopiować kilka właściwości, z selfktórych jest zdekodowana instancja Storyboard. framea właściwości autolayout / autoresize są szczególnie ważne.

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

OSTATECZNE ROZWIĄZANIE

Jak widać, jest to fragment kodu standardowego. Możemy je zaimplementować jako „kategorię”. Tutaj rozszerzam powszechnie używany UIView+loadFromNibkod.

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

Korzystając z tego, możesz zadeklarować MyCustomViewProto:

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

Zrzut ekranu XIB

Storyboard:

Storyboard

Wynik:

wprowadź opis obrazu tutaj

rintaro
źródło
3
Rozwiązanie jest bardziej skomplikowane niż pierwotny problem. Aby zatrzymać pętlę cykliczną, wystarczy ustawić obiekt właściciela pliku zamiast deklarować widok zawartości jako typ klasy MyCustomView.
Bogdan Onu
To po prostu kompromis między a) prostym procesem inicjalizacji, ale skomplikowaną hierarchią widoków oraz b) skomplikowanym procesem inicjalizacji, ale prostą hierarchią widoków. n'est-ce pas? ;)
rintaro
czy jest link do pobrania tego projektu?
karthikeyan
13

Nie zapomnij

Dwie ważne kwestie:

  1. Ustaw właściciela pliku .xib na nazwę klasy widoku niestandardowego.
  2. Nie ustawiaj nazwy klasy niestandardowej w IB dla widoku głównego .xib.

Przeszedłem do tej strony z pytaniami i odpowiedziami kilka razy, ucząc się tworzenia widoków wielokrotnego użytku. Zapomnienie o powyższych punktach sprawiło, że zmarnowałem dużo czasu na próbę ustalenia, co powoduje nieskończoną rekurencję. Kwestie te są wspomniane w innych odpowiedziach tutaj i gdzie indziej , ale chcę je tutaj ponownie podkreślić.

Moja pełna odpowiedź Swift z krokami jest tutaj .

Suragch
źródło
2

Istnieje rozwiązanie znacznie czystsze od powyższych: https://www.youtube.com/watch?v=xP7YvdlnHfA

Brak właściwości środowiska wykonawczego, żaden problem z połączeniami rekurencyjnymi. Wypróbowałem to i zadziałało jak urok, używając ze scenorysu i XIB z właściwościami IBOutlet (iOS8.1, XCode6).

Powodzenia w programowaniu!

ingaham
źródło
1
Dzięki @ingaham! Jednak podejście przedstawione w filmie jest identyczne z drugim rozwiązaniem zaproponowanym w pierwotnym pytaniu (kod Swift, na który przedstawiono w mojej odpowiedzi powyżej). Podobnie jak w obu tych przypadkach, wiąże się to z dodaniem widoku podrzędnego do podklasy opakowującej UIView i dlatego nie ma problemu z wywołaniami rekurencyjnymi, nie ma też potrzeby polegania na właściwościach środowiska wykonawczego ani na czymkolwiek innym skomplikowanym. Jak wspomniano, wadą jest to, że do niestandardowej klasy UIView należy dodać zbędny dodatkowy widok podrzędny. Jak już wspomniano, może to być na razie najlepsze i najprostsze rozwiązanie.
Ken Chatfield,
Tak, masz całkowitą rację, to są identyczne rozwiązania. Konieczny jest jednak zbędny widok, ale jest to rozwiązanie najbardziej czyste i łatwe w utrzymaniu. Więc zdecydowałem się użyć tego.
ingaham
Uważam, że niepotrzebny widok jest całkowicie i całkowicie naturalny i nigdy nie ma innego rozwiązania. Zauważ, że mówisz, że „coś” przejdzie „tutaj” ” … że„ tutaj ”jest naturalną istniejącą rzeczą. Po prostu musi tam być „coś”, „miejsce, w którym zamierzasz umieścić rzeczy” - sama definicja tego, czym jest „widok”. I czekaj! „Widok kontenera” Apple'a jest w istocie dokładnie tym … istnieje „ramka”, „widok uchwytu” („widok kontenera”), do którego można coś podpiąć. Rzeczywiście, „nadmiarowe” rozwiązania w zakresie widoku to właśnie ręcznie robiony widok kontenera! Po prostu użyj Apple's.
Fattie