Ponownie użyj uiview xib w scenorysie

81

Zazwyczaj lubię tworzyć i projektować uiviews w konstruktorze interfejsów. Czasami potrzebuję utworzyć pojedynczy widok w xib, który można ponownie wykorzystać w wielu kontrolerach widoku w scenorysie.

Garfbargle
źródło

Odpowiedzi:

132

Ponownie użyj i renderuj xib w scenorysie.

Przetestowano w Swift 2.2 i Xcode 7.3.1

1 ---- Utwórz nowy UIView o nazwie „DesignableXibView”

  • Plik> Nowy> Plik> Źródło> Klasa Cocoa Touch> UIView

2 ---- Utwórz pasujący plik xib o nazwie „DesignableXibView”

  • Plik> Nowy> Plik> Interfejs użytkownika> Widok

3 ---- Ustaw właściciela pliku xib

  1. wybierz xib
  2. wybierz właściciela pliku
  3. ustaw klasę niestandardową na „DesignableXibView” w Inspektorze tożsamości.

Ustawianie właściciela Xib na niestandardową klasę

  • Uwaga: nie ustawiaj niestandardowej klasy widoku na xib. Tylko właściciel pliku!

4 ---- Implementacja DesignableXibView

//  DesignableXibView.swift

import UIKit

@IBDesignable

class DesignableXibView: UIView {

    var contentView : UIView?

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

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

    func xibSetup() {
        contentView = loadViewFromNib()

        // use bounds not frame or it'll be offset
        contentView!.frame = bounds

        // Make the view stretch with containing view
        contentView!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]

        // Adding custom subview on top of our view (over any custom drawing > see note below)
        addSubview(contentView!)
    }

    func loadViewFromNib() -> UIView! {

        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView

        return view
    }

}

5 ---- Przetestuj widok wielokrotnego użytku w scenorysie

  1. Otwórz scenorys
  2. Dodaj widok
  3. Ustaw klasę niestandardową tego widoku
  4. poczekaj chwilę ... BUM !!

Renderowanie Xib w kontrolerze widoku scenorysu

Garfbargle
źródło
1
Dzięki! Możesz chcieć dodać przykład używając ograniczeń w Swift, tak jak zrobiłeś to w swojej odpowiedzi Obj-C (z i bez VFL).
Evan R,
3
Odpowiedziałem na własne pytanie, wygląda na to, że skonfigurowałem wyjścia PRZED przeczytaniem tych artykułów SO mówiących o ustawieniu właściciela pliku, a nie widoku. Zadziałało po tym, jak odłączyłem wszystkie gniazda, upewniłem się, że właściciel pliku jest poprawny, a następnie ponownie dodałem wszystkie gniazda.
SuperDuperTango
4
Jaka jest różnica między ustawieniem właściciela pliku a niestandardową klasą?
Paul Brewczyński
9
Nie widzę też podglądu w narzędziu do tworzenia interfejsów. Xcode 9
Jaap Weijland
3
Kod działa po uruchomieniu, ale w moim Xcode 9 pokazuje
`` Niepowodzenie kompilacji projektowanej
76

NOWY! zaktualizowana odpowiedź z możliwością renderowania bezpośrednio w scenorysie (i szybko!)

Działa w Xcode 6.3.1

Utwórz nowy UIView o nazwie „ReuseableView”

  • Plik> Nowy> Plik> Źródło> Klasa Cocoa Touch> UIView

Utwórz pasujący plik XIB o nazwie „ReuseableView”

  • Plik> Nowy> Plik> Interfejs użytkownika> Widok

Ustaw właściciela pliku xib

  1. wybierz xib
  2. wybierz właściciela pliku
  3. ustaw klasę niestandardową na „ReusableView” w Inspektorze tożsamości. wprowadź opis obrazu tutaj

    • Uwaga: nie ustawiaj niestandardowej klasy widoku na xib. Tylko właściciel pliku!

Utwórz gniazdko z widoku w ReuseableView.xib do interfejsu ReuseableView.h

  1. Otwórz Edytor Asystenta
  2. Control + przeciągnij z widoku do interfejsu

wprowadź opis obrazu tutaj

Dodaj implementację initWithCoder, aby załadować widok i dodać jako widok podrzędny.

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

        // 1. load the interface
        [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
        // 2. add as subview
        [self addSubview:self.view];
        // 3. allow for autolayout
        self.view.translatesAutoresizingMaskIntoConstraints = NO;
        // 4. add constraints to span entire view
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];

    }
    return self;
}

wprowadź opis obrazu tutaj

Przetestuj widok wielokrotnego użytku w scenorysie

  1. Otwórz scenorys
  2. Dodaj widok
  3. Ustaw klasę niestandardową tego widoku

wprowadź opis obrazu tutaj

Biegnij i obserwuj! wprowadź opis obrazu tutaj

Garfbargle
źródło
@Garfbargle Dodanie do tego ... czy można wyświetlić w scenorysie podelementy xib? Na ostatnim etapie dodany widok wygląda jak szary kwadrat. Czy można to naprawić? Dzięki!
Andres C
2
@ andres.cianio Tak, jednak musisz przyjąć zupełnie inne podejście. Musisz programowo zbudować widok i użyć dyrektywy IBDesignable. Było to jednak specjalnie dla osób, które chciały wizualnie budować widoki. Nie znam sposobu na wizualne zbudowanie widoku w xib i renderowanie go w scenorysie. Nie zdziwiłbym się, gdyby było to możliwe bardzo szybko, ale jeśli jeszcze nie jest.
Garfbargle
@Garfbargle thanks ... To uratowało mi życie, ale renderowanie go w SB podczas wstawiania widoku byłoby piękne;)
Andres C
2
Świetne wyjaśnienie! Ale mam dla ciebie wskazówkę: użyj UINib(nibName: nibName, bundle: nil).instantiateWithOwner(nil, options: nil)szybszego niż NSBundle-Version.
blackjacx
@Garfbargle Wydaje się, że spełnia to wymaganie, abyś mógł zaprojektować w IB i wyświetlić go w scenorysie. Zasadniczo robisz dokładnie to, co zrobiłeś dla XIB i umieściłeś @IBDesignable on the class.również zaimplementować init(frame:)- ale poza tym działa całkiem dobrze! supereasyapps.com/blog/2014/12/15/…
wyu
53

Swift 3 i 4 Zaktualizuj zaakceptowaną odpowiedź

1. Utwórz nowy UIView o nazwie „DesignableXibView”

  • Plik> Nowy> Plik> Źródło> Klasa Cocoa Touch> UIView

2. Utwórz pasujący plik xib o nazwie „DesignableXibView”

  • Plik> Nowy> Plik> Interfejs użytkownika> Widok

3. Ustaw właściciela pliku xib

Wybierz „DesignableXibView.xib”> „Właściciel pliku”> ustaw „Niestandardową klasę” na „DesignableXibView” w Inspektorze tożsamości.

  • Uwaga: nie ustawiaj niestandardowej klasy widoku na xib. Tylko właściciel pliku!

4. Implementacja DesignableXibView

    import UIKit

@IBDesignable

class DesignableXibView: UIView {

    var contentView : UIView!

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

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

    func xibSetup() {
        contentView = loadViewFromNib()

        // use bounds not frame or it'll be offset
        contentView.frame = bounds

        // Make the view stretch with containing view
        contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]

        // Adding custom subview on top of our view 
        addSubview(contentView)
    }

    func loadViewFromNib() -> UIView! {

        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return view
    }
} 

5 Przetestuj widok wielokrotnego użytku w serii ujęć

  • Otwórz scenorys

  • Dodaj widok

  • Ustaw klasę niestandardową tego widoku

harsh_v
źródło
2
Nie mogę zmusić go do pracy w Xcode8 / Swift3. Działa dobrze podczas uruchamiania aplikacji, ale nie wyświetla się w scenorysie.
shallowThought
U mnie działa dobrze. Jeśli próbujesz wyświetlić zmiany w xib, to się nie pojawi. Dodaj widok wewnątrz innego kontrolera, a następnie ustaw klasę tego widoku na DesignableXibView. Zbuduj projekt, aby wyświetlić zmiany.
harsh_v
1
Dla mnie też zadziałał. Wystarczy dodać DesignableXibView do mojego obecnego storyboardu, jak opisano w kroku 5 postu Garfbargle
Tinkerbell
Po prostu otrzymuję komunikat „Nie można załadować NIB w pakiecie” w czasie wykonywania?
Jonny
1
@Jonny Myślę, że String(describing: type(of: self))powinienem to zrobić bezpiecznie
harsh_v
11

Funkcja initWithCoder w swift 2, jeśli ktoś ma problem z jej przetłumaczeniem:

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        UINib(nibName: String(self.dynamicType), bundle: NSBundle.mainBundle()).instantiateWithOwner(self, options: nil)
        self.addSubview(view)
        self.view.translatesAutoresizingMaskIntoConstraints = false
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterY , metrics: nil, views: ["view": self.view]))
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterX , metrics: nil, views: ["view": self.view]))
    }
Ankit Goel
źródło
Dzięki! Możesz dodać przykład nie używający VFL, dla tych, którzy nie chcą go używać.
Evan R,
3

Dla każdego, kto próbuje dostosować zaakceptowaną odpowiedź (autorstwa @Garfbargle) do Objective-C

Samo przekonwertowanie Swiftna Objective-Cnie wystarczy, aby to działało. Trudno mi było zezwolić na renderowanie na żywo w Storyboard.

Po przetłumaczeniu całego kodu widok jest dobrze ładowany, gdy działa na urządzeniu (lub symulatorze), ale renderowanie na żywo w Storyboard nie działa. Powodem tego jest to, że użyłem, [NSBundle mainBundle]podczas gdy Interface Builder nie ma dostępu do mainBundle. Zamiast tego musisz użyć[NSBundle bundleForClass:self.classForCoder] . BOOM, renderowanie na żywo działa teraz!

Uwaga: jeśli masz problemy z Auto Layout, spróbuj wyłączyć Safe Area Layout Guidesw Xib.

Dla Twojej wygody zostawiam tutaj cały mój kod, abyś musiał tylko skopiować / wkleić (dla całego procesu postępuj zgodnie z oryginalną odpowiedzią ):

BottomBarView.h

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface BottomBarView : UIView

@end

BottomBarView.m

#import "BottomBarView.h"

@interface BottomBarView() {
    UIView *contentView;
}

@end


@implementation BottomBarView

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

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

-(void) xibSetup {
    contentView = [self loadViewFromNib];

    contentView.frame = self.bounds;

    contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self addSubview:contentView];
}

-(UIView*) loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:self.classForCoder]; //this is the important line for view to render in IB
    UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:bundle];
    UIView *view = [nib instantiateWithOwner:self options:nil][0];

    return view;
}

@end

Powiedz mi, jeśli napotkasz jakieś problemy, ale powinno prawie działać po wyjęciu z pudełka :)

AnthoPak
źródło
0

Jeśli ktoś jest zainteresowany, oto Xamarin.iOS wersja kodu Krok 4 @Garfbargle

public partial class CustomView : UIView
    {
        public ErrorView(IntPtr handle) : base(handle)
        {

        }

        [Export("awakeFromNib")]
        public override void AwakeFromNib()
        {
            var nibObjects = NSBundle.MainBundle.LoadNib("CustomView", this, null);
            var view = (UIView)Runtime.GetNSObject(nibObjects.ValueAt(0));
            view.Frame = Bounds;
            view.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;

            AddSubview(rootView);
        }
    }
Yohan Dahmani
źródło
0

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 renderować niestandardowy widok (w tym podglądy z xib) w czasie projektowania w scenorysie.

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

Tom 👣

barefeettom
źródło