Wczytaj widok z zewnętrznego pliku XIB w serii ujęć

134

Chcę użyć widoku w wielu kontrolerach widoku w scenorysie. Dlatego pomyślałem o zaprojektowaniu widoku w zewnętrznym xib, aby zmiany były odzwierciedlane w każdym kontrolerze widoku. Ale jak można załadować widok z zewnętrznego XIB w scenorysie i czy jest to w ogóle możliwe? Jeśli tak nie jest, jakie inne alternatywy są dostępne, aby dopasować się do powyższej sytuacji?

Sebastian Hoffmann
źródło

Odpowiedzi:

134

Mój pełny przykład jest tutaj , ale poniżej przedstawię podsumowanie.

Układ

Dodaj do projektu pliki .swift i .xib o tej samej nazwie. Plik .xib zawiera niestandardowy układ widoku (najlepiej przy użyciu ograniczeń automatycznego układu).

Spraw, aby plik Swift był właścicielem pliku xib.

wprowadź opis obrazu tutaj Kod

Dodaj następujący kod do pliku .swift i podłącz wyjścia i akcje z pliku .xib.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Użyj tego

Użyj swojego niestandardowego widoku w dowolnym miejscu w swojej serii ujęć. Po prostu dodaj UIViewi ustaw nazwę klasy na własną nazwę klasy.

wprowadź opis obrazu tutaj

Suragch
źródło
4
Czy to nie jest tak, że loadNibNamed wywołuje init (koder :)? Mam awarię podczas próby dostosowania twojego podejścia.
Fishman
@ Fishman, jeśli spróbujesz załadować widok programowo (zamiast z serii ujęć), nastąpi awaria, ponieważ obecnie nie ma pliku init(frame:). Więcej informacji znajdziesz w tym samouczku .
Suragch
7
Inną częstą przyczyną awarii jest ustawienie widoku niestandardowego dla właściciela pliku . Zobacz czerwone kółko w mojej odpowiedzi.
Suragch
6
Tak, ustawiłem klasę widoku głównego zamiast właściciela pliku i powodowało to nieskończoną pętlę.
devios1
2
Po dodaniu wiersza view.autoresizingMask = [.f flexibleWidth, .f flexibleHeight] przed self.addSubview (view) działa idealnie.
Pramod Tapaniya
73

Przez chwilę podejście Christophera Swaseya było najlepszym, jakie znalazłem. Zapytałem o to kilku starszych programistów z mojego zespołu i jeden z nich znalazł idealne rozwiązanie ! Spełnia wszystkie obawy, do których tak elokwentnie odniósł się Christopher Swasey, i nie wymaga standardowego kodu podklasy (mój główny problem związany z jego podejściem). Jest jedna gotcha , ale poza tym jest dość intuicyjna i łatwa do wdrożenia.

  1. Utwórz niestandardową klasę UIView w pliku .swift, aby kontrolować swój xib. to znaczyMyCustomClass.swift
  2. Utwórz plik .xib i nadaj mu styl, jak chcesz. to znaczyMyCustomClass.xib
  3. Ustaw File's Ownerplik .xib jako klasę niestandardową ( MyCustomClass)
  4. GOTCHA: pozostaw classwartość (pod identity Inspector) dla własnego widoku w pliku .xib pustą. Twój widok niestandardowy nie będzie miał określonej klasy, ale będzie miał określonego właściciela pliku.
  5. Podłącz gniazda w zwykły sposób, korzystając z Assistant Editor.
    • UWAGA: Jeśli spojrzysz na to Connections Inspector, zauważysz, że twoje referencyjne punkty sprzedaży nie odnoszą się do twojej niestandardowej klasy (tj. MyCustomClass), Ale raczej do odniesienia File's Owner. Ponieważ File's Ownerjest określona jako klasa niestandardowa, gniazda będą się podłączać i działać prawidłowo.
  6. Upewnij się, że Twoja klasa niestandardowa ma @IBDesignable przed instrukcją class.
  7. Spraw, aby Twoja klasa niestandardowa była zgodna z NibLoadableprotokołem wymienionym poniżej.
    • UWAGA: Jeśli .swiftnazwa pliku klasy niestandardowej różni się od .xibnazwy pliku, ustaw nibNamewłaściwość na nazwę .xibpliku.
  8. Zaimplementuj required init?(coder aDecoder: NSCoder)i override init(frame: CGRect)zadzwoń setupFromNib()jak w przykładzie poniżej.
  9. Dodaj UIView do wybranej planszy i ustaw klasę jako niestandardową nazwę klasy (tj MyCustomClass.).
  10. Zobacz, jak IBDesignable w akcji rysuje Twój plik .xib w scenorysie z zachwytem i zachwytem.

Oto protokół, do którego chcesz się odwołać:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

A oto przykład MyCustomClassimplementacji protokołu (z nazwą pliku .xib MyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

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

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

}

UWAGA: Jeśli przegapisz Gotcha i ustawisz classwartość w pliku .xib jako klasę niestandardową, nie będzie ona rysowana w scenorysie i pojawi się EXC_BAD_ACCESSbłąd po uruchomieniu aplikacji, ponieważ utknie w nieskończonej pętli próbuje zainicjować klasę z nib przy użyciu init?(coder aDecoder: NSCoder)metody, która następnie wywołuje Self.nib.instantiatei initponownie wywołuje .

Ben Patch
źródło
2
Oto kolejne świetne podejście, ale wydaje mi się, że powyższe jest jeszcze lepsze: medium.com/zenchef-tech-and-product/ ...
Ben Patch
4
Podejście wspomniane powyżej działa doskonale i stosuje podgląd na żywo bezpośrednio w scenorysie. Jest absolutnie poręczny i niesamowity, duży!
Igor Leonovich
1
FYI: to rozwiązanie, używając definicji ograniczeń w setupFromNib(), wydaje się naprawiać pewne dziwne problemy z automatycznym układem z automatycznym dopasowywaniem rozmiaru komórek widoku tabeli zawierających widoki utworzone przez XIB.
Gary
1
Zdecydowanie najlepsze rozwiązanie! Uwielbiam @IBDesignablekompatybilność. Nie mogę uwierzyć, dlaczego Xcode lub UIKit nie zapewniają czegoś takiego domyślnie podczas dodawania pliku UIView.
fl034
1
nie wydaje się, aby ten działał. Za każdym razem, gdy ustawiam właściciela pliku w moim pliku .xib, ustawia RÓWNIEŻ niestandardową klasę.
drewster
32

Zakładając, że utworzyłeś xib, którego chcesz użyć:

1) Utwórz niestandardową podklasę UIView (możesz przejść do Plik -> Nowy -> Plik ... -> Klasa Cocoa Touch. Upewnij się, że „Podklasa:” to „UIView”).

2) Dodaj widok oparty na xib jako podwidok do tego widoku podczas inicjalizacji.

W Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

W Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

W Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) Gdziekolwiek chcesz go użyć w swoim storyboardzie, dodaj UIView jak zwykle, wybierz nowo dodany widok, przejdź do Inspektora tożsamości (trzecia ikona w prawym górnym rogu, która wygląda jak prostokąt z liniami), i wprowadź nazwę swojej podklasy w polu „Klasa” w sekcji „Klasa niestandardowa”.

user1021430
źródło
xibView.frame = self.frame;powinien być xibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);, w przeciwnym razie xibView będzie miał przesunięcie, gdy widok zostanie dodany do scenorysu.
BabyPanda
spóźnił się na imprezę, ale wygląda na to, że zmienił to na xibView.frame = self.bounds, czyli klatkę bez przesunięcia
Heavy_Bullets
21
Powoduje awarię z powodu nieskończonej rekurencji. Załadowanie końcówki tworzy kolejną instancję podklasy.
David
2
Klasa widoku xib nie powinna być taka sama, jak ta nowa podklasa. Jeśli xib to MyClass, możesz utworzyć tę nową klasę MyClassContainer.
user1021430
1
powyższe rozwiązanie spowoduje awarię do nieskończonej rekurencji.
JBarros35
28

Zawsze uważałem, że rozwiązanie „dodaj to jako podwidok” nie jest satysfakcjonujące, biorąc pod uwagę, że wkręca się w (1) autoukładanie, (2) @IBInspectablei (3) gniazda. Zamiast tego, pozwól mi przedstawić Wam magię awakeAfter:, o NSObjectmetodzie.

awakeAfterpozwala całkowicie zamienić obiekt faktycznie obudzony z NIB / Storyboard na inny obiekt. Ten obiekt jest następnie poddawany procesowi hydratacji, awakeFromNibwywoływany, dodawany jako widok itp.

Możemy to wykorzystać w podklasie "wycinanej tektury" naszego widoku, której jedynym celem będzie załadowanie widoku z NIB i zwrócenie go do użycia w Storyboard. Podklasa do osadzania jest następnie określana w inspektorze tożsamości widoku Storyboard, a nie w oryginalnej klasie. W rzeczywistości nie musi to być podklasa, aby to zadziałało, ale uczynienie z niej podklasy jest tym, co pozwala IB zobaczyć wszystkie właściwości IBInspectable / IBOutlet.

Ten dodatkowy szablon może wydawać się nieoptymalny - iw pewnym sensie tak jest, ponieważ idealnie UIStoryboardby sobie z tym poradził - ale ma tę zaletę, że pozostawia oryginalny NIB i UIViewpodklasę całkowicie niezmienioną. Rola, jaką odgrywa, jest zasadniczo rolą klasy adaptera lub mostka i jest całkowicie słuszna, jeśli chodzi o projekt, jako klasa dodatkowa, nawet jeśli jest to godne pożałowania. Z drugiej strony, jeśli wolisz być oszczędny w swoich klasach, rozwiązanie @ BenPatch działa poprzez implementację protokołu z kilkoma innymi drobnymi zmianami. Pytanie, które rozwiązanie jest lepsze, sprowadza się do kwestii stylu programisty: czy preferuje się kompozycję obiektów, czy wielokrotne dziedziczenie.

Uwaga: klasa ustawiona w widoku w pliku NIB pozostaje taka sama. Podklasa osadzalna jest używana tylko w scenorysie. Podklasy nie można użyć do utworzenia wystąpienia widoku w kodzie, więc sama nie powinna mieć żadnej dodatkowej logiki. Powinien zawierać tylkoawakeAfter haczyk.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

⚠️ Jedyną istotną wadą jest to, że jeśli zdefiniujesz ograniczenia szerokości, wysokości lub proporcji w scenorysie, które nie odnoszą się do innego widoku, musisz je ręcznie skopiować. Ograniczenia, które odnoszą się do dwóch widoków, są instalowane na najbliższym wspólnym przodku, a widoki są budzone z serii ujęć od wewnątrz na zewnątrz, więc zanim te ograniczenia zostaną uwodnione w nadzorze, zamiana już nastąpiła. Ograniczenia, które dotyczą tylko danego widoku, są instalowane bezpośrednio w tym widoku, a zatem są odrzucane, gdy nastąpi zamiana, chyba że zostaną skopiowane.

Zauważ, że to, co się tutaj dzieje, to ograniczenia zainstalowane w widoku w scenorysie są kopiowane do nowo utworzonego widoku , który może już mieć własne ograniczenia, zdefiniowane w pliku końcówki. Te są nienaruszone.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNibjest bezpiecznym rozszerzeniem do UIView. Wszystko, co robi, to pętla po obiektach NIB, dopóki nie znajdzie takiego, który pasuje do typu. Zauważ, że typ rodzajowy jest powrót wartość, więc typ musi być określona w miejscu wywołania.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
Christopher Swasey
źródło
To jest oszałamiające. Jeśli się jednak nie mylę, to działa tylko "na scenorysie" - jeśli spróbujesz stworzyć taką klasę w kodzie w czasie wykonywania, nie sądzę, żeby to działało. Wierzę.
Fattie
Podklasa powinna działać w kodzie tak samo dobrze jak oryginalna klasa dla wszystkich intencji i celów. Jeśli chcesz załadować widok z końcówki w kodzie, po prostu utworzysz go bezpośrednio przy użyciu tej samej techniki. Wszystko, co robi podklasa, to pobranie kodu do utworzenia wystąpienia widoku ze stalówki i umieszczenie go w zaczepie do użycia w scenorysie.
Christopher Swasey
Właściwie nie miałem racji, to będzie działać tak samo dobrze, jeśli można go instancji, ale nie można, ponieważ widok w NIB będą mieli superklasę jako jego typ tak instantiateViewFromNibnic nie wróci. W każdym razie nie jest to wielka sprawa IMO, podklasa to tylko urządzenie do podłączenia do scenorysu, cały kod powinien znajdować się w oryginalnej klasie.
Christopher Swasey
1
Świetny! Jedna rzecz mnie zaskoczyła, ponieważ mam niewielkie doświadczenie z xibs (pracowałem tylko z storyboardami i podejściami programowymi), zostawiając to tutaj na wypadek, gdyby to komuś pomogło: w pliku .xib musisz wybrać widok najwyższego poziomu i ustawić jego typ klasy na MyCustomView. W moim xib domyślnie brakowało lewego wewnętrznego paska bocznego; aby go włączyć, znajduje się przycisk obok opcji „Wyświetl jako: iPhone 7” w pobliżu dolnej / lewej strony.
xaphod
5
Łamie ograniczenia, gdy jest zastępowany innym obiektem. :(
invoodoo
6

Myślę o alternativeza korzystanie XIB viewsaby używać View Controller w osobnej serii ujęć .

Następnie w głównym ujęć zamiast widoku niestandardowego użycia container viewz Embed Seguei mają StoryboardReferencedo tego niestandardowego widoku kontrolera , który widok powinien być umieszczony wewnątrz innego widoku w głównym ujęć.

Następnie możemy skonfigurować delegowanie i komunikację między tym osadzonym ViewController i głównym kontrolerem widoku poprzez przygotowanie do płynności . To podejście różni się od wyświetlania UIView, ale znacznie prostsze i wydajniejsze (z punktu widzenia programowania) można wykorzystać do osiągnięcia tego samego celu, tj. Uzyskania widoku niestandardowego wielokrotnego użytku, który jest widoczny w głównym scenariuszu

Dodatkową zaletą jest to, że można zaimplementować logikę w klasie CustomViewController i tam skonfigurować całe delegowanie i przygotowanie widoku bez tworzenia oddzielnych (trudniejszych do znalezienia w projekcie) klas kontrolera i bez umieszczania standardowego kodu w głównym UIViewController przy użyciu Component. Myślę, że jest to dobre dla komponentów wielokrotnego użytku, np. Komponent Music Player (podobny do widżetu), który można osadzać w innych widokach.

Michał Ziobro
źródło
Rzeczywiście, niestety znacznie prostsze rozwiązanie niż użycie niestandardowego widoku xib :(
Wilson
1
po kręceniu się w kółko z łańcuchem tworzenia cyklu życia xib firmy Apple na niestandardowym UIView, tak właśnie skończyłem.
LightningStryk
W przypadku, gdy chcesz dynamicznie dodać niestandardowy widok, nadal musimy korzystać z oddzielnego kontrolera widoku (najlepiej XIB)
Satyam
6

Obecnie najlepszym rozwiązaniem jest użycie niestandardowego kontrolera widoku z jego widokiem zdefiniowanym w xib i po prostu usunięcie właściwości „widoku”, którą Xcode tworzy wewnątrz serii ujęć podczas dodawania do niego kontrolera widoku (nie zapomnij ustawić nazwy klasa niestandardowa).

Spowoduje to, że środowisko wykonawcze automatycznie wyszuka plik xib i załaduje go. Możesz użyć tej sztuczki do dowolnego widoku kontenera lub widoku zawartości.

Ben G.
źródło
6

Chociaż najpopularniejsze odpowiedzi działają dobrze, są koncepcyjnie błędne. Wszystkie używają File's ownerjako połączenia między gniazdami klasy a komponentami interfejsu użytkownika. File's ownerma być używany tylko dla obiektów najwyższego poziomu, UIViewa nie s. Sprawdź dokument programisty Apple . Posiadanie UIView jako File's ownerprowadzi do tych niepożądanych konsekwencji.

  1. Jesteś zmuszony używać contentViewtam, gdzie powinieneś używać self. Jest nie tylko brzydki, ale także strukturalnie niepoprawny, ponieważ widok pośredni uniemożliwia strukturze danych przekazywanie struktury interfejsu użytkownika. To jest jak przeciwieństwo deklaratywnego interfejsu użytkownika.
  2. Możesz mieć tylko jeden UIView na Xib. Xib powinien mieć wiele widoków UIV.

Jest elegancki sposób na zrobienie tego bez użycia File's owner. Sprawdź ten wpis na blogu . Wyjaśnia, jak to zrobić we właściwy sposób.

Ingun 전인 건
źródło
to nie ma znaczenia, nie wyjaśniłeś, dlaczego jest źle. Nie rozumiem dlaczego. nie ma żadnych skutków ubocznych.
JBarros35
1

Rozwiązanie dla celu C zgodnie z krokami opisanymi w odpowiedzi Bena Patcha .

Użyj rozszerzenia dla UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

Tworzenie plików MyView.h, MyView.ma MyView.xib.

Najpierw przygotuj się MyView.xibtak, jak mówi odpowiedź Bena Patcha, więc ustaw klasę MyViewdla właściciela pliku zamiast głównego widoku wewnątrz tego XIB.

MyView.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m:

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

A później po prostu utwórz widok programowo:

MyView* view = [[MyView alloc] init];

Ostrzeżenie! Podgląd tego widoku nie będzie wyświetlany w Storyboard, jeśli używasz rozszerzenia WatchKit z powodu tego błędu w Xcode> = 9.2: https://forums.developer.apple.com/thread/95616

Ariel Bogdziewicz
źródło
1

Oto odpowiedź, której szukałeś przez cały czas. Możesz po prostu utworzyć swoją CustomViewklasę, umieścić jej główną instancję w pliku xib ze wszystkimi widokami podrzędnymi i gniazdami. Następnie możesz zastosować tę klasę do dowolnych wystąpień w swoich scenorysach lub innych plikach xib.

Nie trzeba majstrować przy właścicielu pliku, podłączać gniazd do serwera proxy lub modyfikować xib w szczególny sposób ani dodawać instancji własnego widoku jako widoku podrzędnego samego siebie.

Po prostu zrób to:

  1. Importuj framework BFWControls
  2. Zmień swoją superklasę z UIViewna NibView(lub z UITableViewCellna NibTableViewCell)

Otóż ​​to!

Działa nawet z IBDesignable, aby odnieść swój niestandardowy widok (w tym podglądy z xib) w czasie projektowania do scenorysu.

Możesz przeczytać więcej na ten temat tutaj: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

Możesz pobrać platformę BFWControls typu open source tutaj: https://github.com/BareFeetWare/BFWControls

A oto prosty fragment NibReplaceablekodu, który go napędza, na wypadek, gdybyś był ciekawy:
 https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom 👣

barefeettom
źródło
Nie chcę instalować frameworka, gdy powinien być dostarczony natywnie.
JBarros35
0

To rozwiązanie może być użyte, nawet jeśli twoja klasa nie ma takiej samej nazwy jak XIB. Na przykład, jeśli masz kontroler klasy kontrolera A widoku bazowego, który ma nazwę kontrolera XIB controllerA.xib i podklasowałeś to z kontroleremB i chcesz utworzyć wystąpienie kontroleraB w scenorysie, możesz:

  • utwórz kontroler widoku w serii ujęć
  • ustaw klasę kontrolera na kontrolerB
  • usuń widok kontroleraB w serii ujęć
  • nadpisać widok obciążenia w kontrolerze A na:

*

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}
otusweb
źródło