Jak zainicjować / utworzyć wystąpienie niestandardowej klasy UIView z plikiem XIB w języku Swift

139

Mam klasę o nazwie, MyClassktóra jest podklasą UIView, którą chcę zainicjować z XIBplikiem. Nie jestem pewien, jak zainicjować tę klasę za pomocą pliku xib o nazwieView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Stephen Fox
źródło
5
zobacz pełną próbkę kodu źródłowego dla iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift i powiązany wątek stackoverflow.com/questions/24857986/…
karthikPrabhu Alagu

Odpowiedzi:

266

Przetestowałem ten kod i działa świetnie:

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

Zainicjuj widok i użyj go jak poniżej:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

LUB

var view = MyClass.instanceFromNib
self.view.addSubview(view())

AKTUALIZACJA Swift> = 3.x i Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Ezimet
źródło
1
Powinien być var view = MyClass.instanceFromNib()& self.view.addSubview(view)w przeciwieństwie do var view = MyClass.instanceFromNib& self.view.addSubview(view()). Tylko mała sugestia, aby poprawić odpowiedź :)
Logan
1
W moim przypadku widok został zainicjowany później! jeśli to self.view.addSubview (view), widok powinien być var ​​view = MyClass.instanceFromNib ()
Ezimet
2
@Ezimet, a co z IBActions w tym widoku. gdzie sobie z nimi poradzić. Tak jak mam w widoku przycisk (xib), jak obsłużyć zdarzenie kliknięcia IBAction tego przycisku.?
Qadir Hussain
6
Czy powinien zwrócić „MyClass” zamiast tylko UIView?
Kesong Xie
2
IBoutlets nie działają w tym podejściu ... Otrzymuję: "ta klasa nie jest zgodna z kodowaniem wartości klucza dla klucza"
Radek Wilczak
82

Rozwiązanie Sama jest już świetne, mimo że nie bierze pod uwagę różnych pakietów (na ratunek przychodzi NSBundle: forClass) i wymaga ręcznego ładowania, czyli wpisywania kodu.

Jeśli chcesz mieć pełne wsparcie dla swoich gniazd Xib, różnych pakietów (używaj w frameworkach!) I uzyskaj ładny podgląd w Storyboard, spróbuj tego:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Użyj swojego xib jak zwykle, tj. Połącz Outlety z File Owner i ustaw klasę File Owner na własną klasę.

Użycie: Po prostu podklasuj własną klasę View z NibLoadingView i ustaw nazwę klasy na Właściciel pliku w pliku Xib

Nie jest już wymagany żaden dodatkowy kod.

Kredyty, w przypadku których należy się kredyt: rozwidlono to z niewielkimi zmianami z DenHeadless na GH. Moje streszczenie: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Frederik Winkelsdorf
źródło
8
To rozwiązanie blokuje połączenia wyjściowe (ponieważ dodaje załadowany widok jako podwidok), a wywołanie nibSetupz init?(coder:)spowoduje nieskończoną rekursję, jeśli zostanie osadzony NibLoadingVieww XIB.
redent84
3
@ redent84 Dziękujemy za komentarz i głos przeciw. Jeśli masz drugie spojrzenie, powinno ono zastąpić poprzedni SubView (nie podano nowej zmiennej instancji). Masz rację co do nieskończonej rekurencji, którą należy pominąć, jeśli walczysz z IB.
Frederik Winkelsdorf
1
Jak wspomniano, „podłącz Outlety do File Owner i ustaw klasę File Owner na własną klasę”. podłącz gniazda do właściciela plików
Yusuf X
1
Zawsze czuję się nieswojo używając tej metody do ładowania widoku z xib. Zasadniczo dodajemy widok, który jest podklasą klasy A, w widoku, który jest również podklasą klasy A. Czy nie ma sposobu, aby zapobiec tej iteracji?
Prajeet Shrestha
1
@PrajeetShrestha Jest to prawdopodobnie spowodowane tym, że nibSetup () nadpisuje kolor tła na .clearColor()- po załadowaniu go z Storyboard. Ale powinno działać, jeśli zrobisz to za pomocą kodu po utworzeniu wystąpienia. W każdym razie, jak powiedziano, jeszcze bardziej eleganckim podejściem jest podejście oparte na protokole. Tak więc, oto link dla Ciebie: github.com/AliSoftware/Reusable . Używam teraz podobnego podejścia w odniesieniu do UITableViewCells (które zaimplementowałem, zanim odkryłem ten naprawdę przydatny projekt). hth!
Frederik Winkelsdorf
77

Począwszy od Swift 2.0, możesz dodać rozszerzenie protokołu. Moim zdaniem jest to lepsze podejście, ponieważ typ zwracany jest Selfraczej niż UIView, więc obiekt wywołujący nie musi rzutować na klasę widoku.

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Sam
źródło
4
Jest to lepsze rozwiązanie niż wybrana odpowiedź, ponieważ nie ma potrzeby przesyłania, a także będzie można je ponownie wykorzystać we wszystkich innych podklasach UIView, które utworzysz w przyszłości.
user3344977
3
Wypróbowałem to w Swift 2.1 i Xcode 7.2.1. Czasami działało, a na innych rozłączało się w nieprzewidywalny sposób z blokadą mutex. Ostatnie dwie linie używane bezpośrednio w kodzie działały za każdym razem, a ostatnia linia została zmodyfikowana navar myView = nib.instantiate... as! myViewType
Paul Linsay,
@ jr-root-cs Twoja edycja zawierała literówki / błędy, musiałem ją wycofać. A poza tym proszę nie dodawać kodu do istniejących odpowiedzi. Zamiast tego zrób komentarz; lub dodaj swoją wersję we własnej odpowiedzi . Dzięki.
Eric Aya,
Kod, który otworzyłem i przetestowałem w swoim projekcie przy użyciu Swift 3(XCode 8.0 beta 6) bez problemów opublikowałem . Wpisano literówkę Swift 2. Dlaczego miałaby to być kolejna odpowiedź, skoro ta odpowiedź jest dobra, a użytkownicy prawdopodobnie polubią wyszukiwanie, jakie zmiany będą miały miejsce, gdy będą używać XC8
jr.root.cs
1
@ jr.root.cs Tak, ta odpowiedź jest dobra, dlatego nikt nie powinien jej zmieniać. To jest odpowiedź Sama, nie twoja. Jeśli chcesz to skomentować, zostaw komentarz; jeśli chcesz opublikować nową / zaktualizowaną wersję, zrób to we własnym poście . Zmiany mają na celu poprawianie literówek / wcięć / tagów, a nie dodawanie twoich wersji w postach innych osób. Dzięki.
Eric Aya,
37

I to jest odpowiedź Frederika na Swift 3.0

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}
Mahmood S
źródło
31

Uniwersalny sposób wczytywania widoku z XIB:

Przykład:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

Realizacja:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}
Максим Мартынов
źródło
Najlepsza odpowiedź dla mnie jako widok wyjściowy jako typ podklasy UIView
jfgrang
26

Odpowiedź Swift 3: W moim przypadku chciałem mieć gniazdko w mojej klasie niestandardowej, które mógłbym zmodyfikować:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

W pliku xib upewnij się, że pole klasy niestandardowej to MyClassView. Nie zawracaj sobie głowy właścicielem pliku.

Upewnij się, że klasa niestandardowa to MyClassView

Upewnij się również, że podłączasz gniazdko w MyClassView do etykiety: Wylot dla myLabel

Aby go utworzyć:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Jeremy
źródło
Wracam „załadowałem stalówkę„ MyClas ”, ale nie ustawiono wyjścia widoku.” „Jeśli właściciel nie jest ustawiony
jcharch
22

Szybki 4

Tutaj w moim przypadku muszę przekazać dane do tego niestandardowego widoku, więc tworzę funkcję statyczną, aby utworzyć wystąpienie widoku.

  1. Utwórz rozszerzenie UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. Utwórz MyCustomView

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
  3. Ustaw klasę niestandardową na MyCustomView w pliku xib. W razie potrzeby podłącz gniazdko w zwykły sposób. wprowadź opis obrazu tutaj

  4. Utwórz instancję widoku

    let view = MyCustomView.instantiate(message: "Hello World.")
mnemonic23
źródło
jeśli w widoku niestandardowym są przyciski, jak możemy obsłużyć ich działania w różnych kontrolerach widoku?
Jayant Rawat
możesz użyć delegata protokołu. zajrzyj tutaj stackoverflow.com/questions/29602612/… .
mnemonic23
Nie udało się załadować obrazu do xib, powodując następujący błąd. Nie można załadować obrazu „ IBBrokenImage ”, do którego odwołuje się stalówka w pakiecie z identyfikatorem „test.Testing”
Ravi Raja Jangid
-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

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

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}
Madan Kumawat
źródło
źle sformatowany kod, brak wyjaśnienia, stąd głos przeciw.
J. Doe,
Co to wszystko ma wspólnego z pytaniem?
Ashley Mills,