Como inicializar / instanciar uma classe UIView personalizada com um arquivo XIB no Swift

139

Eu tenho uma classe chamada MyClassque é uma subclasse de UIView, que eu quero inicializar com um XIBarquivo. Não sei como inicializar essa classe com o arquivo xib chamadoView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Stephen Fox
fonte
5
referem amostra completa código fonte para iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift e linha relacionada stackoverflow.com/questions/24857986/...
karthikPrabhu Alagu

Respostas:

266

Eu testei esse código e funciona muito bem:

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

Inicialize a visualização e use-a como abaixo:

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

OU

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

UPDATE 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
}
Ezimet
fonte
1
Deve ser var view = MyClass.instanceFromNib()& self.view.addSubview(view)ao contrário de var view = MyClass.instanceFromNib& self.view.addSubview(view()). Apenas uma pequena sugestão para melhorar a resposta :)
Logan
1
No meu caso, a visualização inicializada mais tarde! se é self.view.addSubview (vista), vista deve ser var view = MyClass.instanceFromNib ()
Ezimet
2
@Ezimet, e as IBActions dentro dessa exibição. onde lidar com eles. Como se eu tivesse um botão na minha opinião (xib), como lidar com o evento de clique IBAction desse botão.
Qadir Hussain
6
Ele deve retornar "MyClass" em vez de apenas UIView?
Kesong Xie
2
IBOutlets não estão funcionando nesta abordagem ... Eu estou recebendo: "esta classe não é valor de chave de codificação compatível para a chave"
Radek Wilczak
82

A solução de Sam já é ótima, apesar de não levar em conta pacotes diferentes (o NSBundle: forClass vem em socorro) e requer carregamento manual, também conhecido como código de digitação.

Se você deseja suporte completo para seus Xib Outlets, pacotes diferentes (use em estruturas!) E obtenha uma boa visualização no Storyboard, tente o seguinte:

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

}

Use seu xib como de costume, ou seja, conecte Outlets ao File Owner e defina a classe File Owner para sua própria classe.

Uso: basta subclassificar sua própria classe View de NibLoadingView e definir o nome da classe como Proprietário do arquivo no arquivo Xib

Não é mais necessário código adicional.

Créditos onde o crédito é devido: bifurcou-o com pequenas alterações de DenHeadless no GH. Minha essência: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Frederik Winkelsdorf
fonte
8
Essa solução freia as conexões de saída (porque adiciona a visualização carregada como uma subvisualização) e a chamada nibSetupde init?(coder:)causará uma recursão infinita se incorporada NibLoadingViewem um XIB.
redent84
3
@ redent84 Obrigado pelo seu comentário e pelo voto negativo. Se você tiver uma segunda olhada, ele deve substituir o SubView anterior (nenhuma nova variável de instância foi fornecida). Você está certo sobre a recursão infinita, que deve ser omitida se estiver lutando com o IB.
Frederik Winkelsdorf
1
Como mencionado, "conecte Outlets ao proprietário do arquivo e defina a classe Proprietário do arquivo como sua própria classe". conecte as saídas ao proprietário do arquivo
Yusuf X
1
Fico sempre desconfortável com o uso desse método para carregar a exibição do xib. Basicamente, estamos adicionando uma visão que é uma subclasse da Classe A, em uma visão que também é da subclasse da Classe A. Não há alguma maneira de impedir essa iteração?
Prajeet Shrestha
1
@PrajeetShrestha Provavelmente, o nibSetup () substituiu a cor de fundo por .clearColor()- depois de carregá-la no Storyboard. Mas deve funcionar se você fizer isso por código após instanciar. De qualquer forma, como dito, uma abordagem ainda mais elegante é a baseada em protocolo. Então, com certeza, aqui está um link para você: github.com/AliSoftware/Reusable . Agora, estou usando uma abordagem semelhante em relação ao UITableViewCells (que eu implementei antes de descobrir esse projeto realmente útil). hth!
Frederik Winkelsdorf 23/01
77

A partir do Swift 2.0, você pode adicionar uma extensão de protocolo. Na minha opinião, essa é uma abordagem melhor porque o tipo de retorno é Selfmelhor que UIView, portanto, o chamador não precisa transmitir para a classe view.

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
fonte
4
Essa é uma solução melhor que a resposta selecionada, pois não há necessidade de transmissão e também será reutilizável em qualquer outra subclasse UIView que você criar no futuro.
precisa saber é o seguinte
3
Eu tentei isso com o Swift 2.1 e o Xcode 7.2.1. Funcionava algumas vezes e pendurava imprevisivelmente em outras pessoas com um bloqueio mutex. As duas últimas linhas usadas diretamente no código funcionavam todas as vezes com a última linha modificada para servar myView = nib.instantiate... as! myViewType
Paul Linsay 24/02
@ jr-root-cs Sua edição continha erros de digitação, tive que revertê-la. De qualquer forma, não adicione código às respostas existentes. Em vez disso, faça um comentário; ou adicione sua versão em sua própria resposta . Obrigado.
Eric Aya
Publiquei o código que havia aberto e testado no meu projeto usando Swift 3(XCode 8.0 beta 6) sem problemas. O erro ocorreu Swift 2. Por que deveria ser outra resposta quando esta resposta é boa e usuários, provavelmente, como a busca, quais são as alterações quando eles vão usar XC8
jr.root.cs
1
@ jr.root.cs Sim, esta resposta é boa, é por isso que ninguém deve alterá-la. Esta é a resposta de Sam, não a sua. Se você quiser comentar, deixe um comentário; se você quiser publicar uma versão nova / atualizada, faça-a em sua própria postagem . As edições destinam-se a corrigir erros de digitação / indentação / tags para não adicionar suas versões nas postagens de outras pessoas. Obrigado.
Eric Aya
37

E esta é a resposta de Frederik no 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
fonte
31

Maneira universal de carregar a vista do xib:

Exemplo:

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

Implementação:

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))
    }
}
Максим Мартынов
fonte
Melhor resposta para mim como o ponto de vista de saída como o Type da UIView subclasse
jfgrang
26

Resposta Swift 3: No meu caso, eu queria ter uma saída na minha classe personalizada que pudesse modificar:

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 estiver no .xib, verifique se o campo Classe personalizada é MyClassView. Não se preocupe com o proprietário do arquivo.

Verifique se a classe personalizada é MyClassView

Além disso, certifique-se de conectar a tomada no MyClassView ao rótulo: Saída para myLabel

Para instanciar:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Jeremy
fonte
Volto "carreguei a ponta" MyClas ", mas a saída da vista não estava definida. '" Se o proprietário não estiver definido
jcharch 15/07/19
22

Swift 4

Aqui, no meu caso, tenho que passar dados para essa exibição personalizada, portanto, crio uma função estática para instanciar a exibição.

  1. Criar extensão UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. Criar 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. Defina a classe personalizada como MyCustomView no arquivo .xib. Conecte a tomada, se necessário, como de costume. insira a descrição da imagem aqui

  4. Instanciar visualização

    let view = MyCustomView.instantiate(message: "Hello World.")
mnemonic23
fonte
se houver botões na exibição personalizada, como podemos lidar com suas ações em diferentes controladores de exibição?
jayant rawat 31/01
você pode usar o delegado de protocolo. dê uma olhada aqui stackoverflow.com/questions/29602612/… .
mnemonic23
Falha ao carregar uma imagem no xib, obtendo o seguinte erro. Não foi possível carregar a imagem " IBBrokenImage " referenciada de uma ponta no pacote com o identificador "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
fonte
código formatado incorreto, sem explicação, daí o voto negativo.
31919 J. Doe
O que tudo isso tem a ver com a questão?
Ashley Mills