Come inizializzare / creare un'istanza di una classe UIView personalizzata con un file XIB in Swift


139

Ho una classe chiamata MyClassche è una sottoclasse di UIView, che voglio inizializzare con un XIBfile. Non sono sicuro di come inizializzare questa classe con il file xib chiamatoView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}

5
fare riferimento campione completo di codice sorgente per iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift e relativo filo stackoverflow.com/questions/24857986/...
karthikPrabhu Alagu

Risposte:


266

Ho testato questo codice e funziona benissimo:

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

Inizializza la vista e usala come di seguito:

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

O

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

AGGIORNA Swift> = 3.x & Swift> = 4.x

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

1
Dovrebbe essere var view = MyClass.instanceFromNib()& self.view.addSubview(view)al contrario di var view = MyClass.instanceFromNib& self.view.addSubview(view()). Solo un piccolo suggerimento per migliorare la risposta :)
Logan,

1
Nel mio caso vista inizializzata più tardi! se è self.view.addSubview (vista), la vista dovrebbe essere var view = MyClass.instanceFromNib ()
Ezimet,

2
@Ezimet per quanto riguarda le IBActions all'interno di quella vista. dove gestirli. Come se avessi un pulsante nella mia vista (xib) come gestire l'evento click IBAction di quel pulsante.
Qadir Hussain,

6
Dovrebbe restituire "MyClass" anziché solo UIView?
Kesong Xie,

2
Gli IBoutlet non funzionano in questo approccio ... Sto ottenendo: "questa classe non è conforme alla codifica dei valori chiave per la chiave"
Radek Wilczak,

82

La soluzione di Sam è già eccezionale, nonostante non tenga conto di diversi bundle (NSBundle: forClass viene in soccorso) e richiede il caricamento manuale, ovvero il codice di battitura.

Se vuoi il pieno supporto per i tuoi Xib Outlets, diversi Bundle (usa in framework!) E ottieni una bella anteprima in Storyboard prova questo:

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

}

Usa il tuo xib come di consueto, ad esempio collega le prese al proprietario del file e imposta la classe del proprietario del file sulla tua classe.

Utilizzo: basta sottoclassare la propria classe View da NibLoadingView e impostare il nome della classe su Proprietario file nel file Xib

Non è più necessario alcun codice aggiuntivo.

Crediti a cui è dovuto il credito: biforcati con lievi modifiche da DenHeadless su GH. My Gist: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8


8
Questa soluzione frena le connessioni di uscita (perché aggiunge la vista caricata come sottoview) e la chiamata nibSetupda init?(coder:)causerà una ricorsione infinita se incorporata NibLoadingViewin una XIB.
redent84,

3
@ redent84 Grazie per il tuo commento e il tuo voto negativo. Se hai una seconda occhiata, dovrebbe sostituire la SubView precedente (non è stata fornita alcuna nuova variabile di istanza). Hai ragione sulla ricorsione infinita, che dovrebbe essere omessa se si lotta con IB.
Frederik Winkelsdorf,

1
Come accennato "connetti le prese al proprietario del file e imposta la classe del proprietario del file sulla tua classe". collegare le prese al proprietario del file
Yusuf X

1
Sono sempre a disagio nell'utilizzare questo metodo per caricare la vista da XIB. In sostanza stiamo aggiungendo una vista che è una sottoclasse di Classe A, in una vista che è anche sottoclasse di Classe A. Non esiste un modo per impedire questa iterazione?
Prajeet Shrestha,

1
@PrajeetShrestha Ciò è probabilmente dovuto a nibSetup () che ha la precedenza sul colore di sfondo .clearColor()dopo averlo caricato dallo Storyboard. Ma dovrebbe funzionare se lo fai per codice dopo che è stato istanziato. Comunque, come detto un approccio ancora più elegante è quello basato sul protocollo. Quindi, ecco un link per te: github.com/AliSoftware/Reusable . Sto usando un approccio simile ora riguardo a UITableViewCells (che ho implementato prima di scoprire quel progetto davvero utile). hth!
Frederik Winkelsdorf il

77

A partire da Swift 2.0, è possibile aggiungere un'estensione del protocollo. Secondo me, questo è un approccio migliore perché il tipo restituito è Selfpiuttosto che UIView, quindi il chiamante non ha bisogno di trasmettere alla classe vista.

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
  }

}

4
Questa è una soluzione migliore della risposta selezionata in quanto non è necessario eseguire il casting e sarà riutilizzabile in tutte le altre sottoclassi UIView create in futuro.
user3344977

3
Ho provato questo con Swift 2.1 e Xcode 7.2.1. Ha funzionato un po 'di tempo e avrebbe riattaccato imprevedibilmente gli altri con una serratura mutex. Le ultime due righe utilizzate direttamente nel codice funzionavano ogni volta con l'ultima riga modificata per esserevar myView = nib.instantiate... as! myViewType
Paul Linsay,

@ jr-root-cs La tua modifica conteneva errori di battitura / errori, ho dovuto ripristinarla. E comunque per favore non aggiungere codice alle risposte esistenti. Invece, fai un commento; o aggiungi la tua versione nella tua risposta . Grazie.
Eric Aya,

Ho pubblicato il codice che avevo aperto e testato nel mio progetto usando Swift 3(XCode 8.0 beta 6) senza problemi. L'errore era dentro Swift 2. Perché dovrebbe essere un'altra risposta quando questa risposta è buona e agli utenti piacerà probabilmente cercare quali sono le modifiche quando useranno XC8
jr.root.cs

1
@ jr.root.cs Sì, questa risposta è buona, ecco perché nessuno dovrebbe modificarla. Questa è la risposta di Sam, non la tua. Se vuoi commentarlo, lascia un commento; se vuoi pubblicare una versione nuova / aggiornata, fallo nel tuo post . Le modifiche hanno lo scopo di correggere errori di battitura / rientro / tag per non aggiungere le tue versioni nei post di altre persone. Grazie.
Eric Aya,

37

E questa è la risposta di Frederik su 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
    }
}

31

Modo universale di caricamento della vista da xib:

Esempio:

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

Implementazione:

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

La migliore risposta a me come visualizzazione di output come Tipo della sottoclasse
UIView

26

Swift 3 Risposta: Nel mio caso, volevo avere un outlet nella mia classe personalizzata che potevo modificare:

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

Quando si è in .xib, assicurarsi che il campo Classe personalizzata sia MyClassView. Non preoccuparti del proprietario del file.

Assicurarsi che la classe personalizzata sia MyClassView

Inoltre, assicurati di collegare la presa in MyClassView all'etichetta: Outlet per myLabel

Per istanziarlo:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"

Ricevo "caricato il pennino" MyClas "ma l'output della vista non era impostato." "Se il proprietario non è impostato
jcharch

22

Swift 4

Qui nel mio caso devo passare i dati in quella vista personalizzata, quindi creo una funzione statica per istanziare la vista.

  1. Crea l'estensione UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. Crea 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. Impostare la classe personalizzata su MyCustomView nel file .xib. Collegare la presa se necessario come al solito. inserisci qui la descrizione dell'immagine

  4. Visualizzazione istantanea

    let view = MyCustomView.instantiate(message: "Hello World.")

se nella vista personalizzata sono presenti pulsanti, come possiamo gestire le loro azioni in diversi controller di visualizzazione?
Jayay Rawat

puoi usare il protocollo delegato. dai un'occhiata qui stackoverflow.com/questions/29602612/… .
mnemonic23

Impossibile caricare un'immagine in xib, ottenendo il seguente errore. Impossibile caricare l'immagine " IBBrokenImage " a cui fa riferimento un pennino nel bundle con identificativo "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)

        })
    })


}

codice formattato errato, nessuna spiegazione, quindi il downvote.
J. Doe,

Cosa c'entra tutto questo con la domanda?
Ashley Mills,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.