Carregar um UIView da ponta no Swift

148

Aqui está o meu código Objective-C que estou usando para carregar uma ponta para o meu personalizado UIView:

-(id)init{

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

}

Qual é o código equivalente no Swift?

Bagusflyer
fonte

Respostas:

172

Solução original

  1. Criei um XIB e uma classe chamada SomeView (usei o mesmo nome para conveniência e legibilidade). Baseei ambos em um UIView.
  2. No XIB, alterei a classe "Proprietário do arquivo" para SomeView (no inspetor de identidade).
  3. Criei uma saída UIView em SomeView.swift, vinculando-a à visualização de nível superior no arquivo XIB (chamada de "visualização" por conveniência). Em seguida, adicionei outras saídas a outros controles no arquivo XIB, conforme necessário.
  4. em SomeView.swift, carreguei o XIB dentro do inicializador "init com código". Não há necessidade de atribuir nada ao "eu". Assim que o XIB é carregado, todas as tomadas são conectadas, incluindo a visualização de nível superior. A única coisa que falta é adicionar a vista superior à hierarquia da vista:

.

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

Note que desta maneira eu recebo uma classe que se carrega da ponta. Eu poderia usar o SomeView como uma classe sempre que o UIView pudesse ser usado no projeto (no construtor de interfaces ou programaticamente).

Atualização - usando a sintaxe do Swift 3

O carregamento de um xib na seguinte extensão é gravado como um método de instância, que pode ser usado por um inicializador como o descrito acima:

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. O uso de um valor de retorno descartável, pois a visualização retornada não interessa mais ao chamador quando todas as tomadas já estiverem conectadas.
  2. Este é um método genérico que retorna um objeto opcional do tipo UIView. Se não conseguir carregar a visualização, ele retornará nulo.
  3. Tentativa de carregar um arquivo XIB com o mesmo nome que a instância de classe atual. Se isso falhar, nada será retornado.
  4. Incluindo a visualização de nível superior na hierarquia da visualização.
  5. Esta linha assume que estamos usando restrições para o layout da exibição.
  6. Esse método adiciona restrições superiores, inferiores, iniciais e finais - anexando a exibição a "auto" em todos os lados (consulte https://stackoverflow.com/a/46279424/2274829 para obter detalhes)
  7. Retornando a visualização de nível superior

E o método de chamada pode ser assim:

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 é uma subclasse UIView que carrega seu conteúdo de um arquivo SomeClass.xib. A palavra-chave "final" é opcional.
  2. Um inicializador para quando a visualização é usada em um storyboard (lembre-se de usar SomeClass como a classe personalizada da visualização do storyboard).
  3. Um inicializador para quando a exibição é criada programaticamente (ou seja: "deixe myView = SomeView ()").
  4. Usando um quadro com todos os zeros, já que essa visualização é organizada usando o layout automático. Observe que o método "init (frame: CGRect) {..}" não é criado independentemente, pois o layout automático é usado exclusivamente em nosso projeto.
  5. & 6. Carregando o arquivo xib usando a extensão.

Crédito: O uso de uma extensão genérica nesta solução foi inspirado na resposta de Robert abaixo.

Editar Alterando "view" para "contentView" para evitar confusão. Também alterou o subscrito da matriz para ".first".

GK100
fonte
7
Definindo o nome da classe para File's Ownerchegar ao local ... Obrigado!
Aviel Gross
13
Não UIView não tem vista da propriedade, assim que chamar self.view causa um erro
Nastya Gorban
4
@NastyaGorban self.view, na verdade, refere-se, neste caso, à propriedade outlet (denominada "view) que o GK100 vinculou a partir da visualização de nível superior no .xib a SomeView.swift. A não inclusão dessa tomada causará erro, pois não há" view "nas classes NSView como você diz.
Ausiàs
3
Estou recebendo uma falha ao carregar a ponta (loadNibNamed). Usando o Xcode 6.3 e Swift
karthikPrabhu Alagu 6/15/15
11
ligando fromNib()de dentro init(coder aDecoder: NSCoder)cria um ciclo infinito como o carregamento da ponta no interior do fromNib()método faz uma chamada para:init(coder aDecoder: NSCoder)
Matthew Cawley
334

Minha contribuição:

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

Então chame assim:

let myCustomView: CustomView = UIView.fromNib()

..ou mesmo:

let myCustomView: CustomView = .fromNib()
Robert Gummesson
fonte
20
Melhor resposta de longe.
CodyMace 26/01
7
Melhor resposta aqui. Limpo e Simples
Marquavious Draggon
3
@YuchenZhong - prefiro [0] ao invés de .first, pois isso retornaria um opcional. Se você forçar a desembrulhar, não seria mais seguro. ... e isso levanta a questão: Por que não retornar um opcional como algumas das soluções acima? Resposta: Você pode. Nada de errado com isso. Mas ... se algum dia retornar nulo, o nome do xib / class não corresponderá. Este é um erro do desenvolvedor e deve ser detectado imediatamente e nunca chegar à produção. Aqui, eu preferiria que o aplicativo travasse, deixando-o em um estado estranho. Apenas meus 2 centavos / preferência.
Robert Gummesson
1
@allenlinli - O método é uma extensão estática do UIView como deveria para o CustomView. Funciona porque o compilador deduz o tipo usando a anotação de tipo explícita. Como o CustomView é uma subclasse de UIView e o tipo já foi inferido, não precisamos deduzi-lo novamente, portanto, o UIView pode ser omitido, como mostrado no meu segundo exemplo. Dito isto, você poderia obviamente fazer a ligação da mesma forma que a anota.
Robert Gummesson
3
Essa solução não funcionou para mim no caso em que havia uma exibição personalizada dentro do .xib. Sugiro corrigir esta parte para: retornar Bundle.main.loadNibNamed (String (descrevendo: self), owner: nil, options: nil)! [0] as! T
Nadzeya
79

Agora, poder retornar -> Selfrapidamente ajuda a simplificar um pouco isso. Última confirmação em 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
    }
}

Se seu .xibarquivo e subclasse compartilharem o mesmo nome, você poderá usar:

let view = CustomView.fromNib()

Se você tiver um nome personalizado, use:

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

NOTA:

Se você estiver recebendo o erro "visualização do tipo YourType não encontrada em ..", não definiu a classe da visualização no .xibarquivo

Selecione sua visualização no .xibarquivo, pressione cmd + opt + 4e, na classentrada, insira sua classe

Logan
fonte
1
Não consigo fazer isso funcionar no XCode 7.1 beta 3 - não tenho certeza se é uma coisa beta, mas basicamente tentei de todas as maneiras criar uma exibição personalizada diretamente de uma ponta no Swift e sempre obtenho o mesmo resultado: a classe que está criando não é compatível com KVC com as tomadas. Não tenho certeza se é algo que estou fazendo de errado, mas minha classe é bem simples e o Dono do arquivo está correto. Eu costumava fazer isso o tempo todo no Objective-C.
Echelon
1
O @Logan não está realmente relacionado ao seu código, mas as visualizações personalizadas imo devem suportar o carregamento do Storyboard / XIB. Meu comentário foi apenas uma notificação para aqueles que desejam criar tais visualizações
Nikita Tomou
1
Nota: Ainda tenho um problema ao usar a segunda forma de chamar essa função, a saber let myCustomView = UIView.fromNib() as? CustomView. Neste caso, T.selfresolve UIView, em vez de CustomViewe ele não consegue encontrar a ponta. Não sei por que isso é - talvez o tipo inferido para os letmeios pelos quais a função é chamada como a UIView?
Echelon
2
É importante ressaltar que tentar usar o Proprietário do arquivo para conectar as tomadas (como fizemos nos bons e velhos tempos) causará um travamento. No IB, o Dono do arquivo deve estar nulo / vazio e as tomadas devem ser conectadas à visualização.
Echelon
1
@Echelon você salvou meu dia !!! Conectei minhas tomadas usando o Proprietário do arquivo e ele não funcionou, usando a exibição funcionou.
Jan Schlorf
19

tente seguir o código.

var uiview :UIView?

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

Editar:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

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


}
Jasmim
fonte
Eu preciso chamar esse método dentro do método de inicialização do objeto, que é init () no Swift.
Bagusflyer
12

Extensões de protocolo 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

    }

}

Adoção

class MyView: UIView, NibInstantiatable {

}

Esta implementação assume que o Nib tem o mesmo nome que a classe UIView. Ex. MyView.xib. Você pode modificar esse comportamento implementando nibName () no MyView para retornar um nome diferente do que a implementação da extensão de protocolo padrão.

No xib, o proprietário dos arquivos é MyView e a classe de visualização raiz é MyView.

Uso

let view = MyView.fromNib()
Brody Robertson
fonte
3
Essa é, de longe, a solução mais elegante e direta e não tenho idéia do motivo de não ser a resposta aceita!
horseshoe7
@ horseshoe7 porque está escrito 4 anos após a pergunta.
Freeubi 06/02
11

Consegui isso com o Swift pelo seguinte código:

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

Não se esqueça de ligar o seu XIB vista tomada a vista tomada definido na rápida. Você também pode definir o Primeiro Respondente como seu nome de classe personalizado para começar a conectar quaisquer tomadas adicionais.

Espero que isto ajude!

Amro Shafie
fonte
10

Testado no Xcode 7 beta 4, Swift 2.0 e iOS9 SDK. O código a seguir atribuirá o xib à uiview. Você pode usar essa visualização xib customizada no storyboard e acessar também o objeto 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);



    }


}

Acesse customview programaticamente

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

Código fonte - https://github.com/karthikprabhuA/CustomXIBSwift

karthikPrabhu Alagu
fonte
9

Se você tiver muitas visualizações personalizadas em seu projeto, poderá criar classes como 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)
    }
}

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

E em todas as classes herdadas UIViewFromNib, também é possível substituir a nibNamepropriedade se o .xibarquivo tiver um nome diferente:

class MyCustomClass: UIViewFromNib {

}
ChikabuZ
fonte
8

Com base nas soluções acima.

Isso funcionará em todos os pacotes configuráveis ​​do projeto e não haverá necessidade de genéricos ao chamar fromNib ().

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

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

Pode ser usado assim:

let someView = SomeView.fromNib()

Ou assim:

let someView = SomeView.fromNib("SomeOtherNibFileName")
Gênese
fonte
6

Swift 4

Não esqueça de escrever ".primeiro como? CustomView".

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

Se você quiser usar em qualquer lugar

A melhor solução é a resposta de Robert Gummesson .

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

Então chame assim:

let myCustomView: CustomView = UIView.fromNib()
Ali Ihsan URAL
fonte
5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
BaSha
fonte
Mas no init () do Swift, não há valor retornado. Esqueci de mencionar que preciso chamar loadNibNamed na inicialização de um UIView.
precisa saber é o seguinte
O que você quer dizer com "sem valor de retorno"? selfé implicitamente retornado de todos os initmétodos ...
Grimxn 21/07
1
O que quero dizer é que chamo loadNibNamed dentro do método init. o UIView carregado é atribuído a si próprio no ObjC. Mas rapidamente, não é.
Bagusflyer
5

Uma boa maneira de fazer isso com o Swift é usar um 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
    }
}

Em seu código, você pode simplesmente usar:

let view = Views.view1.getView()
totiG
fonte
2
Note que se você fizer isso com um arquivo nib vazio ou um arquivo nib com um nó UIView raiz nenhum você irá falhar como você é não verificação de sanidade tamanho da matriz ou o elemento na posição 0.
Matthew Cawley
4

Eu prefiro esta solução (com base na resposta se @ GK100):

  1. Criei um XIB e uma classe chamada SomeView (usei o mesmo nome para conveniência e legibilidade). Baseei ambos em um UIView.
  2. No XIB, alterei a classe "Proprietário do arquivo" para SomeView (no inspetor de identidade).
  3. Criei uma saída UIView em SomeView.swift, vinculando-a à visualização de nível superior no arquivo XIB (chamada de "visualização" por conveniência). Em seguida, adicionei outras saídas a outros controles no arquivo XIB, conforme necessário.
  4. No SomeView.swift, carreguei o XIB dentro do initou init:frame: CGRectinicializador. Não há necessidade de atribuir nada ao "eu". Assim que o XIB é carregado, todas as tomadas são conectadas, incluindo a visualização de nível superior. A única coisa que falta é adicionar a vista superior à hierarquia da vista:

    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
      }
    
    
      ...
    }
confundir
fonte
Eu prefiro usar init com frame, então eu desenraizei isso! Uma coisa a notar ... add self.view.frame = quadro se você gostaria que o fim de coincidir com o quadro que você passar
Mike E
3

Versão Swift 3 da resposta de Logan

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
fonte
3

Aqui está uma maneira clara e declarativa de carregar programaticamente uma exibição usando um protocolo e extensão de protocolo (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
    }
}

E você pode usar isso assim:

// 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()

Algumas considerações adicionais :

  1. Certifique-se de que o arquivo xib da sua visualização personalizada tenha as Custom Class conjunto (e saídas / ações definidas a partir daí), não o do Proprietário do Arquivo.
  2. Você pode usar esse protocolo / extensão externo à sua exibição personalizada ou interna. Você pode usá-lo internamente se tiver alguma outra configuração de trabalho ao inicializar sua exibição.
  3. Sua classe de visualização customizada e o arquivo xib precisam ter o mesmo nome.
jason z
fonte
2

Eu apenas faço desta maneira:

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

Este exemplo usa a primeira visualização na ponta "MyView.xib" no pacote principal. Mas você pode variar o índice, o nome da ponta ou o pacote (principal por padrão).

Eu costumava despertar visualizações no método view init ou criar métodos genéricos, como nas soluções acima (que são inteligentes por sinal), mas não faço mais isso.

Dessa forma, posso usar layouts ou características diferentes, mantendo a mesma lógica e código de exibição.

Acho mais fácil deixar um objeto de fábrica (geralmente o viewController que usará o view) criá-lo conforme necessário. Às vezes você precisa de um proprietário (geralmente quando a visualização criada tem uma tomada conectada ao criador), às vezes não.

É provavelmente por isso que a Apple não incluiu um initFromNibmétodo em sua classe UIView ...

Para dar um exemplo ao nível do solo, você não sabe como nasceu. Você acabou de nascer. Assim são os pontos de vista;)

alce
fonte
2

Tudo o que você precisa fazer é chamar o método init no seu UIView classe.

Faça assim:

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

Agora, se você deseja adicionar essa visualização como uma subvisualização no controlador de exibição, faça o mesmo no arquivo controller.swift da exibição:

self.view.addSubview(className())
Alap Anerao
fonte
é uma ótima resposta, mas há algo errado, eu vou editá-lo.
#mrade #
É a maneira que eu implementei. Mas você pode improvisar. Obrigado antecipadamente @ C0mrade
Alap Anerao 14/10
1

Semelhante a algumas das respostas acima, mas uma extensão Swift3 UIView mais consistente:

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

O que oferece a conveniência de poder carregar a classe a partir de uma ponta denominada nib, mas também de outras nibs / pacotes configuráveis.

Mark Johnson
fonte
1

Você pode fazer isso via storyboard, basta adicionar restrições apropriadas para visualização. Você pode fazer isso facilmente subclassificando qualquer visualização, digamos BaseView:

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

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

Eu forneço 2 variantes como adicionar restrições - uma comum e dentro da linguagem de formato visual - selecione a que desejar :)

Além disso, por padrão, assumiu que o xibnome tem o mesmo nome que o nome da classe de implementação. Se não - apenas mudexibName parâmetro.

Se você subclassificar sua visualização de BaseView- você pode facilmente colocar qualquer visualização e especificar a classe no IB.

gbk
fonte
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
fonte
0

Versão mais poderosa baseada na resposta de Logan

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

E você pode usar como

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
fonte
0

A implementação mais conveniente. Aqui você precisa de dois métodos, para retornar diretamente ao objeto da sua classe, não o UIView.

  1. viewId marcado como uma classe , permitindo a substituição
  2. Seu .xib pode conter mais de uma visualização do nível superior; essa situação também é tratada corretamente.

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

Exemplo:

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

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
SeRG1k
fonte
0

Se você deseja que a subclasse Swift UIView seja totalmente independente e tenha a capacidade de ser instanciada usando init ou init (quadro :) sem expor os detalhes de implementação do uso de uma Nib, é possível usar uma extensão de protocolo para conseguir isso. Esta solução evita a hierarquia UIView aninhada, conforme sugerido por muitas das outras soluções.

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
fonte
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
fonte
As respostas apenas de código são desencorajadas. Por favor, adicione algumas explicações sobre como isso resolve o problema ou como isso difere das respostas existentes. Da avaliação
Nick
0

Eu prefiro a extensão abaixo

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

A diferença entre essa e a extensão mais respondida é que você não precisa armazenar uma constante ou variável.

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
fonte
Qual versão do Xcode você está usando? Verifique se você está usando a versão mais recente do XCode. Funciona bem para mim com o XCode 11.5 (versão mais recente na data).
iCoder 01/06
0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Pratik
fonte