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.