Załaduj UIView ze stalówki w Swift

148

Oto mój kod Objective-C, którego używam do ładowania stalówki dla mojego dostosowanego UIView :

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}

Jaki jest odpowiednik kodu w Swift?

Bagusflyer
źródło

Odpowiedzi:

172

Oryginalne rozwiązanie

  1. Stworzyłem XIB i klasę o nazwie SomeView (dla wygody i czytelności użyłem tej samej nazwy). Oparłem oba na UIView.
  2. W XIB zmieniłem klasę „Właściciel pliku” na SomeView (w inspektorze tożsamości).
  3. Utworzyłem punkt sprzedaży UIView w SomeView.swift, łącząc go z widokiem najwyższego poziomu w pliku XIB (dla wygody nazwałem go „widokiem”). Następnie w razie potrzeby dodałem inne wyjścia do innych formantów w pliku XIB.
  4. w SomeView.swift załadowałem XIB wewnątrz inicjatora "init with code". Nie ma potrzeby przypisywania czegokolwiek do „siebie”. Zaraz po załadowaniu XIB wszystkie gniazda są podłączone, w tym widok z góry. Jedyne, czego brakuje, to dodać widok z góry do hierarchii widoków:

.

class SomeView: UIView {
   required init(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
      self.addSubview(self.view);    // adding the top level view to the view hierarchy
   }
   ...
}

Zauważ, że w ten sposób otrzymuję klasę, która ładuje się ze stalówki. Mógłbym wtedy użyć SomeView jako klasy, ilekroć UIView może być użyty w projekcie (w konstruktorze interfejsu lub programowo).

Aktualizacja - przy użyciu składni Swift 3

Ładowanie xib w następującym rozszerzeniu jest zapisywane jako metoda instancji, która może być następnie używana przez inicjator, taki jak powyższy:

extension UIView {

    @discardableResult   // 1
    func fromNib<T : UIView>() -> T? {   // 2
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {    // 3
            // xib not loaded, or its top view is of the wrong type
            return nil
        }
        self.addSubview(contentView)     // 4
        contentView.translatesAutoresizingMaskIntoConstraints = false   // 5 
        contentView.layoutAttachAll(to: self)   // 6 
        return contentView   // 7
    }
}
  1. Używanie odrzucalnej wartości zwracanej, ponieważ zwracany widok nie jest w większości interesujący dla dzwoniącego, gdy wszystkie gniazda są już połączone.
  2. Jest to metoda ogólna, która zwraca opcjonalny obiekt typu UIView. Jeśli załadowanie widoku nie powiedzie się, zwraca nil.
  3. Próba załadowania pliku XIB o tej samej nazwie, co bieżąca instancja klasy. Jeśli to się nie powiedzie, zwracane jest nil.
  4. Dodawanie widoku najwyższego poziomu do hierarchii widoków.
  5. Ta linia zakłada, że ​​używamy ograniczeń do rozmieszczenia widoku.
  6. Ta metoda dodaje górne, dolne, wiodące i końcowe ograniczenia - dołączanie widoku do „siebie” ze wszystkich stron (szczegóły: https://stackoverflow.com/a/46279424/2274829 )
  7. Wracam do widoku najwyższego poziomu

A metoda wywołująca może wyglądać następująco:

final class SomeView: UIView {   // 1.
   required init?(coder aDecoder: NSCoder) {   // 2 - storyboard initializer
      super.init(coder: aDecoder)
      fromNib()   // 5.
   }
   init() {   // 3 - programmatic initializer
      super.init(frame: CGRect.zero)  // 4.
      fromNib()  // 6.
   }
   // other methods ...
}
  1. SomeClass to podklasa UIView, która ładuje swoją zawartość z pliku SomeClass.xib. Słowo kluczowe „końcowe” jest opcjonalne.
  2. Inicjator, gdy widok jest używany w scenorysie (pamiętaj, aby użyć SomeClass jako niestandardowej klasy widoku storyboardu).
  3. Inicjator, gdy widok jest tworzony programowo (np. „Let myView = SomeView ()”).
  4. Używanie ramki zawierającej same zera, ponieważ ten widok jest układany przy użyciu automatycznego układu. Zauważ, że metoda "init (frame: CGRect) {..}" nie jest tworzona niezależnie, ponieważ auto-layout jest używany wyłącznie w naszym projekcie.
  5. & 6. Ładowanie pliku xib przy użyciu rozszerzenia.

Kredyt: Użycie ogólnego rozszerzenia w tym rozwiązaniu zostało zainspirowane poniższą odpowiedzią Roberta.

Edytuj Zmieniając „widok” na „contentView”, aby uniknąć nieporozumień. Zmieniono także indeks tablicy na „.first”.

GK100
źródło
7
Ustawianie nazwy klasy tak, aby była File's Ownertrafiona ... Dzięki!
Aviel Gross
13
UIView nie ma widoku właściwości, więc wywołanie self.view powoduje błąd
Nastya Gorban
4
@NastyaGorban self.view w rzeczywistości odnosi się w tym przypadku do właściwości outletu (o nazwie „view), którą GK100 połączył z widoku najwyższego poziomu w .xib do SomeView.swift. Nie dodawanie tego punktu sprzedaży spowoduje błąd, ponieważ nie ma„ widoku ” "Właściwość w klasach NSView, jak mówisz.
Ausiàs
3
Występuje awaria podczas ładowania końcówki (loadNibNamed). Korzystanie z Xcode 6.3 i Swift
karthikPrabhu Alagu
11
wywołanie fromNib()od wewnątrz init(coder aDecoder: NSCoder)tworzy nieskończoną pętlę, ponieważ ładowanie Nib wewnątrz fromNib()metody powoduje wywołanie:init(coder aDecoder: NSCoder)
Matthew Cawley
334

Mój wkład:

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Następnie nazwij to tak:

let myCustomView: CustomView = UIView.fromNib()

..lub nawet:

let myCustomView: CustomView = .fromNib()
Robert Gummesson
źródło
20
Zdecydowanie najlepsza odpowiedź.
CodyMace
7
Najlepsza odpowiedź tutaj. Czyste i proste
Marquavious Draggon
3
@YuchenZhong - wolę [0] od .first, ponieważ zwróciłoby to opcjonalne. Gdybyś go rozpakował na siłę, nie byłoby to bezpieczniejsze. ... i nasuwa się pytanie: dlaczego nie zwrócić opcji jako niektórych z powyższych rozwiązań? Odpowiedź: Możesz. Nic w tym złego. Ale ... jeśli kiedykolwiek zwróci nil, nazwa klasy xib / nie będzie pasować. Jest to błąd programisty i należy go natychmiast złapać i nigdy nie trafić do produkcji. Tutaj wolałbym, aby aplikacja uległa awarii, zamiast pozostawić ją w jakimś dziwnym stanie. Tylko moje 2 centy / preferencje.
Robert Gummesson
1
@allenlinli - metoda jest statycznym rozszerzeniem UIView, zgodnie z założeniem CustomView. Działa, ponieważ kompilator wnioskuje o typie przy użyciu jawnej adnotacji typu. Ponieważ CustomView jest podklasą UIView, a typ został już wywnioskowany, nie musimy go ponownie wywnioskować, dlatego UIView można pominąć, jak pokazano w moim drugim przykładzie. Mając to na uwadze, możesz oczywiście wykonać połączenie tak, jak je odłożyłeś.
Robert Gummesson
3
To rozwiązanie nie działało dla mnie w przypadku, gdy wewnątrz pliku .xib był niestandardowy widok. Sugerowałbym naprawienie tej części w następujący sposób: return Bundle.main.loadNibNamed (String (opis: self), owner: nil, options: nil)! [0] as! T
Nadzeya
79

Teraz możliwość -> Selfszybkiego powrotu pomaga nieco to uprościć. Ostatnio potwierdzone w Swift 5.

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

Jeśli twój .xibplik i podklasa mają tę samą nazwę, możesz użyć:

let view = CustomView.fromNib()

Jeśli masz własną nazwę, użyj:

let view = CustomView.fromNib(named: "special-case")

UWAGA:

Jeśli otrzymujesz komunikat o błędzie „nie znaleziono widoku typu YourType w…”, oznacza to, że klasa widoku nie została ustawiona w pliku .xib pliku

Wybierz widok w .xibpliku i naciśnij cmd + opt + 4i we classwpisie wprowadź swoją klasę

Logan
źródło
1
Nie mogę uruchomić tego w XCode 7.1 beta 3 - nie jestem pewien, czy jest to wersja beta, ale w zasadzie próbowałem na wszystkie sposoby stworzyć niestandardowy widok bezpośrednio ze stalówki w Swift i zawsze otrzymuję ten sam wynik: klasa, którą tworzy nie jest zgodny z KVC z gniazdami. Nie jestem pewien, czy robię coś źle, ale moja klasa jest dość prosta, a właściciel pliku jest poprawny. Robiłem to cały czas w ramach celu C.
Echelon
1
@Logan nie jest tak naprawdę powiązany z twoim kodem, ale niestandardowe widoki imo powinny obsługiwać ładowanie z Storyboard / XIB. Mój komentarz był tylko powiadomieniem dla tych, którzy chcą tworzyć takie widoki
Nikita wziął
1
Uwaga Nadal mam problem z używaniem drugiej formy wywołania tej funkcji, a mianowicie let myCustomView = UIView.fromNib() as? CustomView. W tym przypadku, T.selfpostanawia UIViewzamiast CustomViewi nie udaje mu się znaleźć stalówkę. Nie jestem pewien, dlaczego tak jest - może wywnioskowany typ dla letśrodków, które funkcja jest nazywana UIView?
Echelon
2
Ważne jest, aby podkreślić, że próba użycia właściciela pliku do podłączenia gniazd (tak jak robiliśmy to w starych, dobrych czasach) spowoduje awarię. W IB właściciel pliku musi być zerowy / pusty, a gniazda powinny być zamiast tego podłączone do widoku.
Echelon
1
@Echelon uratowałeś mój dzień !!! Połączyłem moje gniazdka za pomocą właściciela pliku i to nie zadziałało, zamiast tego skorzystałem z widoku.
Jan Schlorf
19

spróbuj poniższego kodu.

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView

Edytować:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

    init()  {
        super.init()
       self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
    }


}
jaśmin
źródło
Muszę wywołać tę metodę wewnątrz metody inicjalizacji obiektu, która jest init () w języku Swift.
Bagusflyer
12

Rozszerzenia protokołu Swift 4

public protocol NibInstantiatable {

    static func nibName() -> String

}

extension NibInstantiatable {

    static func nibName() -> String {
        return String(describing: self)
    }

}

extension NibInstantiatable where Self: UIView {

    static func fromNib() -> Self {

        let bundle = Bundle(for: self)
        let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)

        return nib!.first as! Self

    }

}

Przyjęcie

class MyView: UIView, NibInstantiatable {

}

Ta implementacja zakłada, że ​​Nib ma taką samą nazwę jak klasa UIView. Dawny. MyView.xib. Możesz zmodyfikować to zachowanie, implementując nibName () w MyView, aby zwrócić inną nazwę niż domyślna implementacja rozszerzenia protokołu.

W xib właścicielem plików jest MyView, a główną klasą widoku jest MyView.

Stosowanie

let view = MyView.fromNib()
Brody Robertson
źródło
3
To zdecydowanie najbardziej eleganckie, proste rozwiązanie i nie mam pojęcia, dlaczego nie jest to akceptowana odpowiedź!
horseshoe7
@ horseshoe7, ponieważ zostało napisane 4 lata po pytaniu.
Freeubi
11

Osiągnąłem to dzięki Swift za pomocą następującego kodu:

class Dialog: UIView {
    @IBOutlet var view:UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.frame = UIScreen.mainScreen().bounds
        NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
        self.view.frame = UIScreen.mainScreen().bounds
        self.addSubview(self.view)
    }

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

Nie zapomnij podłączyć gniazdka widoku XIB, aby szybko wyświetlić gniazdo zdefiniowane. Możesz także ustawić dla pierwszej pomocy nazwę swojej klasy, aby rozpocząć podłączanie dodatkowych gniazd.

Mam nadzieję że to pomoże!

Amro Shafie
źródło
10

Testowane w Xcode 7 beta 4, Swift 2.0 i iOS9 SDK. Poniższy kod przypisze xib do uiview. Możesz użyć tego niestandardowego widoku XIB w scenorysie i uzyskać dostęp do obiektu IBOutlet.

import UIKit

@IBDesignable class SimpleCustomView:UIView
{
    var view:UIView!;

    @IBOutlet weak var lblTitle: UILabel!

   @IBInspectable var lblTitleText : String?
        {
        get{
            return lblTitle.text;
        }
        set(lblTitleText)
        {
            lblTitle.text = lblTitleText!;
        }
    }

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib ()
    }
    func loadViewFromNib() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        self.addSubview(view);



    }


}

Uzyskaj programowy dostęp do widoku niestandardowego

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Kod źródłowy - https://github.com/karthikprabhuA/CustomXIBSwift

karthikPrabhu Alagu
źródło
9

Jeśli masz wiele niestandardowych widoków w swoim projekcie, możesz utworzyć klasę, taką jak UIViewFromNib

Swift 2.3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(self.dynamicType)
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    private func loadViewFromNib() {
        contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
        contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Szybki 3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(describing: type(of: self))
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    func loadViewFromNib() {
        contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

W każdej klasie dziedziczącej po prostu UIViewFromNibmożesz również nadpisać nibNamewłaściwość, jeśli .xibplik ma inną nazwę:

class MyCustomClass: UIViewFromNib {

}
ChikabuZ
źródło
8

Opierając się na powyższych rozwiązaniach.

Będzie to działać we wszystkich paczkach projektów i nie będzie potrzeby stosowania typów ogólnych podczas wywoływania fromNib ().

Szybki 2

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nil)
    }

    public class func fromNib(nibName: String?) -> Self {

        func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
            let bundle = NSBundle(forClass: T.self)
            let name = nibName ?? String(T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName)
    }
}

Szybki 3

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nibName: nil)
    }

    public class func fromNib(nibName: String?) -> Self {
        func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
            let bundle = Bundle(for: T.self)
            let name = nibName ?? String(describing: T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName: nibName)
    }
}

Może być używany w ten sposób:

let someView = SomeView.fromNib()

Lub tak:

let someView = SomeView.fromNib("SomeOtherNibFileName")
Geneza
źródło
6

Szybki 4

Nie zapomnij napisać „.first as? CustomView”.

if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {    
    self.view.addSubview(customView)
    }

Jeśli chcesz używać w dowolnym miejscu

Najlepszym rozwiązaniem jest odpowiedź Roberta Gummessona .

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Następnie nazwij to tak:

let myCustomView: CustomView = UIView.fromNib()
Ali Ihsan URAL
źródło
5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
BaSha
źródło
Ale w init () języka Swift nie ma zwracanej wartości. Zapomniałem wspomnieć, że muszę wywołać loadNibNamed podczas inicjalizacji UIView.
Bagusflyer,
Co masz na myśli „brak wartości zwracanej”? selfjest niejawnie zwracany ze wszystkich initmetod ...
Grimxn
1
Chodzi mi o to, że wywołuję loadNibNamed wewnątrz metody init. załadowany UIView jest przypisany do siebie w ObjC. Ale szybko, tak nie jest.
Bagusflyer
5

Dobrym sposobem na zrobienie tego w Swift jest użycie enum.

enum Views: String {
    case view1 = "View1" // Change View1 to be the name of your nib
    case view2 = "View2" // Change View2 to be the name of another nib

    func getView() -> UIView? {
        return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
    }
}

Następnie w swoim kodzie możesz po prostu użyć:

let view = Views.view1.getView()
totiG
źródło
2
Zauważ, że jeśli zrobisz to z pustym plikiem nib lub plikiem nib bez węzła głównego UIView, nastąpi awaria, ponieważ nie sprawdzasz poprawności rozmiaru tablicy ani elementu na pozycji 0.
Matthew Cawley
4

Wolę to rozwiązanie (na podstawie odpowiedzi jeśli @ GK100):

  1. Stworzyłem XIB i klasę o nazwie SomeView (dla wygody i czytelności użyłem tej samej nazwy). Oparłem oba na UIView.
  2. W XIB zmieniłem klasę „Właściciel pliku” na SomeView (w inspektorze tożsamości).
  3. Utworzyłem punkt sprzedaży UIView w SomeView.swift, łącząc go z widokiem najwyższego poziomu w pliku XIB (dla wygody nazwałem go „widokiem”). Następnie w razie potrzeby dodałem inne wyjścia do innych formantów w pliku XIB.
  4. W SomeView.swift załadowałem XIB w inicjatorze initlub init:frame: CGRect. Nie ma potrzeby przypisywania czegokolwiek do „siebie”. Zaraz po załadowaniu XIB wszystkie gniazda są podłączone, w tym widok z góry. Jedyne, czego brakuje, to dodać widok z góry do hierarchii widoków:

    class SomeView: UIView {
      override init(frame: CGRect) {
        super.init(frame: frame)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
      required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
    
      ...
    }
konfile
źródło
wolę używać init z ramką, więc wykorzeniłem to! jedna uwaga ... dodaj self.view.frame = frame, jeśli chcesz, aby widok pasował do klatki, którą przekazujesz
Mike E
3

Szybka 3 wersja odpowiedzi Logana

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
            for nibView in nibViews {
                if let tog = nibView as? T {
                    view = tog
                }
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nib: UINib? {
        if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
            return UINib(nibName: nibName, bundle: nil)
        } else {
            return nil
        }
    }
}
Alexey Karetski
źródło
3

Oto czysty i deklaratywny sposób programowego ładowania widoku przy użyciu protokołu i rozszerzenia protokołu (Swift 4.2):

protocol XibLoadable {
    associatedtype CustomViewType
    static func loadFromXib() -> CustomViewType
}

extension XibLoadable where Self: UIView {
    static func loadFromXib() -> Self {
        let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
        guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
            // your app should crash if the xib doesn't exist
            preconditionFailure("Couldn't load xib for view: \(self)")
        }
        return customView
    }
}

Możesz użyć tego w następujący sposób:

// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }

// and when you want to use it
let viewInstance = MyView.loadFromXib()

Kilka dodatkowych uwag :

  1. Upewnij się, że plik xib niestandardowego widoku zawiera Custom Classzestaw widoku (i stamtąd ustawienia wyjść / działań), a nie właściciela pliku.
  2. Możesz użyć tego protokołu / rozszerzenia poza swoim widokiem niestandardowym lub wewnętrznym. Możesz zechcieć użyć go wewnętrznie, jeśli masz inne prace konfiguracyjne podczas inicjowania widoku.
  3. Twoja niestandardowa klasa widoku i plik xib muszą mieć tę samą nazwę.
jason z
źródło
2

Po prostu robię w ten sposób:

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

Ten przykład używa pierwszego widoku w końcówce „MyView.xib” w pakiecie głównym. Możesz jednak zmienić indeks, nazwę końcówki lub pakiet (domyślnie główny).

Kiedyś budziłem widoki do metody init widoku lub tworzyłem metody ogólne, jak w rozwiązaniach powyżej (które są sprytne), ale już tego nie robię.

W ten sposób mogę używać różnych układów lub cech, zachowując tę ​​samą logikę widoku i kod.

Łatwiej jest pozwolić obiektowi fabrycznemu (zwykle viewController, który będzie używać widoku), aby utworzył go tak, jak tego potrzebuje. Czasami potrzebujesz właściciela (zwykle gdy utworzony widok ma gniazdko podłączone do twórcy), czasami nie.

Prawdopodobnie dlatego Apple nie uwzględnił initFromNibmetody w swojej klasie UIView ...

Aby wziąć przykład z poziomu gruntu, nie wiesz, jak się urodziłeś. Po prostu się urodziłeś. Tak są widoki;)

Łoś
źródło
2

Wszystko, co musisz zrobić, to wywołać metodę init w swojej UIViewklasie.

Zrób to w ten sposób:

class className: UIView {

    @IBOutlet var view: UIView!

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

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

    func setup() {
        UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
        addSubview(view)
        view.frame = self.bounds
    }
}

Teraz, jeśli chcesz dodać ten widok jako widok podrzędny w kontrolerze widoku, zrób to w ten sposób w pliku view controller.swift:

self.view.addSubview(className())
Alap Anerao
źródło
to świetna odpowiedź, ale coś jest nie tak, poprawię to.
C0mrade
To jest sposób, w jaki zaimplementowałem. Ale możesz to zaimprowizować. Z góry dziękuję @ C0mrade
Alap Anerao
1

Podobne do niektórych odpowiedzi powyżej, ale bardziej spójne rozszerzenie Swift3 UIView:

extension UIView {
    class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
        let bundle = bundle ?? Bundle.main
        let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
        return nibViews?.first as? A
    }

    class func fromNib<T: UIView>() -> T? {
        return fromNib(nibName: String(describing: T.self), bundle: nil)
    }
}

Co zapewnia wygodę wczytywania klasy z końcówki o tej samej nazwie, ale także z innych końcówek / pakietów.

Mark Johnson
źródło
1

Możesz to zrobić za pomocą scenorysu, po prostu dodaj odpowiednie ograniczenia dla widoku. Możesz to łatwo zrobić, podklasując dowolny widok z własnego, powiedzmy BaseView:

Cel C

BaseView.h


/*!
 @class BaseView
 @discussion Base View for getting view from xibFile
 @availability ios7 and later
 */
@interface BaseView : UIView

@end


BaseView.m


#import "BaseView.h"

@implementation BaseView

#pragma mark - Public

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - LifeCycle

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

#pragma mark - Private

- (void)prepareView
{
    NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    UIView *view = [nibsArray firstObject];

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];
    [self addConstraintsForView:view];
}

#pragma mark - Add constraints

- (void)addConstraintsForView:(UIView *)view
{
    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeBottom
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeBottom
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeTop
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeTop
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeLeft
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeLeft
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeRight
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeRight
                                                       multiplier:1.0
                                                         constant:0]
                           ]];
}

@end

Szybki 4

import UIKit

class BaseView : UIView {

    // MARK: - LifeCycle

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

        prepareView()
    }

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

        prepareView()
    }

    internal class func xibName() -> String {
        return String(describing: self)
    }

    // MARK: - Private
    fileprivate func prepareView() {
        let nameForXib = BaseView.xibName()
        let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
        if let view = nibs?.first as? UIView {
            view.backgroundColor = UIColor.clear
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubviewWithConstraints(view, offset: false)
        }
    }
}

UIView+Subview


public extension UIView {
    // MARK: - UIView+Extensions

    public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
        subview.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "subview" : subview
        ]
        addSubview(subview)

        var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
        constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activate(constraints)
    }
}

Podaję 2 warianty dodawania ograniczeń - wspólne i w ramach języka formatu wizualnego - wybierz dowolne :)

Domyślnie przyjęto również, że xibnazwa ma taką samą nazwę jak nazwa klasy implementacji. Jeśli nie - po prostu zmieńxibName parametr.

Jeśli podklasujesz swój widok z BaseView- możesz łatwo umieścić dowolny widok i określić klasę w IB.

gbk
źródło
1
class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
Nadzeya
źródło
0

Mocniejsza wersja oparta na odpowiedzi Logana

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
            if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
                view = tog
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nibIndex: Int {
        return 0
    }

    public class var nibBundle: Bundle {
        return Bundle.main
    }
}

I możesz użyć podobnego

class BaseView: UIView {
    override class var nibName: String { return "BaseView" }
    weak var delegate: StandardStateViewDelegate?
}

class ChildView: BaseView {
    override class var nibIndex: Int { return 1 }
}
user2790103
źródło
0

Najwygodniejsza realizacja. Tutaj potrzebujesz dwóch metod, aby powrócić bezpośrednio do obiektu swojej klasy, a nie UIView.

  1. viewId oznaczony jako klasa , co pozwala na przesłonięcie
  2. Twój .xib może zawierać więcej niż jeden widok najwyższego poziomu, ta sytuacja jest również obsługiwana poprawnie.

extension UIView {

class var viewId: String {
    return String(describing: self)
}

static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
                    owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {

    return instancePrivate(from: bundle ?? Bundle.main,
                           nibName: nibName ?? viewId,
                           owner: owner,
                           options: options)
}

private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
                                              owner: Any?, options: [AnyHashable : Any]?) -> T? {

    guard
        let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
        let view = views.first(where: { $0 is T }) as? T else { return nil }

    return view
}
}

Przykład:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
SeRG1k
źródło
0

Jeśli chcesz, aby podklasa Swift UIView była całkowicie samowystarczalna i miała możliwość tworzenia instancji za pomocą init lub init (ramka :) bez ujawniania szczegółów implementacji używania Nib, możesz użyć rozszerzenia protokołu, aby to osiągnąć. To rozwiązanie pozwala uniknąć zagnieżdżonej hierarchii UIView, zgodnie z sugestią wielu innych rozwiązań.

public class CustomView: UIView {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!

    public convenience init() {
        self.init(frame: CGRect.zero)
    }

    public override convenience init(frame: CGRect) {
        self.init(internal: nil)
        self.frame = frame
    }

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

    fileprivate func commonInit() {
    }
}

fileprivate protocol _CustomView {
}

extension CustomView: _CustomView {
}

fileprivate extension _CustomView {

    // Protocol extension initializer - has the ability to assign to self, unlike
    // class initializers. Note that the name of this initializer can be anything
    // you like, here we've called it init(internal:)

    init(internal: Int?) {
        self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
    }
}
appfigurate
źródło
0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)
Divesh singh
źródło
Tylko kod odpowiedzi są odradzane. Proszę o wyjaśnienie, jak to rozwiązuje problem lub czym różni się to od istniejących odpowiedzi. Z recenzji
Nick
0

Wolę poniższe rozszerzenie

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

Różnica między tym rozszerzeniem a rozszerzeniem z najczęściej odpowiedziami polega na tym, że nie musisz zapisywać go jako stałej ani zmiennej.

class TitleView: UIView { }

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

self.navigationItem.titleView = TitleView.instanceFromNib
iCoder
źródło
Jakiej wersji Xcode używasz? Upewnij się, że używasz najnowszej wersji XCode. U mnie działa dobrze z XCode 11.5 (najnowsza wersja na dzień).
iCoder
0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Pratik
źródło