UIView da subclasse Swift

95

Eu quero criar uma subclasse UIViewe mostrar uma visualização como o login. Eu criei isso em Objective-C, mas agora quero portá-lo para Swift. Eu não uso storyboards, então crio toda a minha IU no código.

Mas o primeiro problema é que devo implementar initWithCoder. Eu dei a ele uma implementação padrão, já que ele não será chamado. Agora, quando executo o programa, ele trava, porque também preciso implementar initWithFrame. Agora eu entendi:

override init() {
    super.init()
    println("Default init")
}

override init(frame: CGRect) {
    super.init(frame: frame)
    println("Frame init")
}

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    println("Coder init")
}

Minha pergunta é onde devo criar meu campo de texto, etc ... e se eu nunca implementar frame e coder, como posso "esconder" isso?

Haagenti
fonte

Respostas:

174

Eu normalmente faço algo assim, é um pouco prolixo.

class MyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        addBehavior()
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("This class does not support NSCoding")
    }

    func addBehavior() {
        print("Add all the behavior here")
    }
}



let u = MyView(frame: CGRect.zero)
let v = MyView()

(Editar: editei minha resposta para que a relação entre os inicializadores seja mais clara)

Raymond
fonte
4
Mas addBehavior é chamado duas vezes, pois initFrame é chamado e init. Se você executar meu código, o primeiro frame será impresso, então o init padrão
Haagenti
6
Bom material, obrigado. Em vez de usar CGRectZero, acredito que seja recomendado usar CGRect.zeroRect.
Sr. Rogers
57
Essa coisa do inicializador é muito complicada.
Ian Warburton
3
Existe alguma maneira de fazer isso para o Layout automático? O quadro está tão desatualizado.
devios1 01 de
8
Esta não é uma resposta completa. UIView oferece suporte a initWithCoding. Qualquer visualização carregada de uma ponta ou storyboard chamará o método initWithCoding e travará.
Iain Delaney
17

Isso é mais simples.

override init (frame : CGRect) {
    super.init(frame : frame)
    // Do what you want.
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}
Jeff Gu Kang
fonte
10

Exemplo de subclasse UIView personalizado

Normalmente crio aplicativos iOS sem usar storyboards ou pontas. Vou compartilhar algumas técnicas que aprendi para responder às suas perguntas.

 

Escondendo initmétodos indesejados

Minha primeira sugestão é declarar uma base UIViewpara ocultar inicializadores indesejados. Eu discuti essa abordagem em detalhes em minha resposta a "Como ocultar inicializadores específicos de storyboard e Nib em subclasses de IU" . Observação: esta abordagem pressupõe que você não usará BaseViewseus descendentes em storyboards ou pontas, pois isso fará com que o aplicativo trave intencionalmente.

class BaseView: UIView {

    // This initializer hides init(frame:) from subclasses
    init() {
        super.init(frame: CGRect.zero)
    }

    // This attribute hides `init(coder:)` from subclasses
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

Sua subclasse UIView personalizada deve ser herdada de BaseView. Ele deve chamar super.init () em seu inicializador. Não é necessário implementar init(coder:). Isso é demonstrado no exemplo abaixo.

 

Adicionando um UITextField

Eu crio propriedades armazenadas para subvisualizações referenciadas fora do initmétodo. Eu normalmente faria isso para um UITextField. Eu prefiro subviews instanciar dentro da declaração da propriedade subexibição assim: let textField = UITextField().

O UITextField não estará visível a menos que você o adicione à lista de subvisualização da visualização personalizada chamando addSubview(_:). Isso é demonstrado no exemplo abaixo.

 

Layout programático sem layout automático

O UITextField não ficará visível a menos que você defina seu tamanho e posição. Costumo fazer layout em código (não usando Auto Layout) dentro do método layoutSubviews . layoutSubviews()é chamado inicialmente e sempre que ocorre um evento de redimensionamento. Isso permite ajustar o layout dependendo do tamanho de CustomView. Por exemplo, se CustomView aparece na largura total em vários tamanhos de iPhones e iPads e se ajusta para rotação, ele precisa acomodar muitos tamanhos iniciais e redimensionar dinamicamente.

Você pode consultar frame.heighte frame.widthem layoutSubviews()para obter as dimensões do CustomView para referência. Isso é demonstrado no exemplo abaixo.

 

Exemplo de subclasse UIView

Uma subclasse UIView personalizada contendo um UITextField que não precisa ser implementado init?(coder:).

class CustomView: BaseView {

    let textField = UITextField()

    override init() {
        super.init()

        // configure and add textField as subview
        textField.placeholder = "placeholder text"
        textField.font = UIFont.systemFont(ofSize: 12)
        addSubview(textField)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // Set textField size and position
        textField.frame.size = CGSize(width: frame.width - 20, height: 30)
        textField.frame.origin = CGPoint(x: 10, y: 10)
    }
}

 

Layout programático com layout automático

Você também pode implementar layout usando Layout automático no código. Como não faço isso com frequência, não mostrarei um exemplo. Você pode encontrar exemplos de implementação de Layout automático em código no Stack Overflow e em outros lugares na Internet.

 

Estruturas de layout programático

Existem estruturas de código aberto que implementam layout no código. Um no qual estou interessado, mas não tentei, é o LayoutKit . Foi escrito pela equipe de desenvolvimento do LinkedIn. Do repositório Github: "O LinkedIn criou o LayoutKit porque descobrimos que o Auto Layout não tem desempenho suficiente para hierarquias de visualização complicadas em visualizações com rolagem."

 

Por que colocar fatalErroreminit(coder:)

Ao criar subclasses de UIView que nunca serão usadas em um storyboard ou bico, você pode introduzir inicializadores com diferentes parâmetros e requisitos de inicialização que não podem ser chamados pelo init(coder:)método. Se você não reprovou o init (coder :) com um fatalError, isso poderia levar a problemas muito confusos no futuro se usado acidentalmente em um storyboard / bico. O fatalError afirma essas intenções.

required init?(coder aDecoder: NSCoder) {
    fatalError("NSCoding not supported")
}

Se você deseja executar algum código quando a subclasse é criada, independentemente de ser criado no código ou em um storyboard / nib, você pode fazer algo como o seguinte (com base na resposta de Jeff Gu Kang )

class CustomView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        initCommon()
    }

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

    func initCommon() {
        // Your custom initialization code
    }
}
Dan móvel
fonte
e? ao adicionar fatalErrorvocê proíbe de iniciar esta visão com arquivos xib
Vyachaslav Gerchicov
@VyachaslavGerchicov, Minha resposta afirma que você não está usando xibs ou storyboards como a resposta aceita e a pergunta. A pergunta indica "Eu não uso storyboards, portanto, crio toda a minha IU no código".
Mobile Dan
da próxima vez que você escrever fatalErrordentro do método dealloc e nos dizer que ele não funciona porque essa classe deve ser um singleton. Se você preferir criar elementos de IU no código, não deve proibir manualmente todas as outras maneiras. Finalmente, a questão é como criar "programaticamente sem storyboards", mas xibs / nibs não são mencionados. No meu caso, preciso criar um array de células com programaticamente + xib e passá-los para DropDownMenuKite desta forma não funciona porque o autor desta biblioteca proíbe o xibs também.
Vyachaslav Gerchicov
@VyachaslavGerchicov Parece que a resposta de Jeff Gu Kang é o que você está procurando, já que acomoda Storyboards / Xibs
Mobile Dan
1
@VyachaslavGerchicov A questão também afirmava "e se eu nunca implementar o frame e o coder, como posso" esconder "isso?" Ao criar subclasses UIView que nunca serão usadas em um Xib / Storyboard, você pode introduzir inicializadores com parâmetros diferentes que não podem ser chamados pelo método init (coder :). Se você não falhou em init (coder :) com fatalError, isso poderia levar a problemas muito confusos no futuro se usado acidentalmente em um Xib / Storyboard. O fatalError declara essas intenções. Esta é uma prática intencional e comum, conforme visto na resposta aceita.
Mobile Dan
4

É importante que seu UIView possa ser criado por construtor de interface / storyboards ou a partir de código. Acho útil ter um setupmétodo para reduzir a duplicação de qualquer código de configuração. por exemplo

class RedView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        setup()
    }

    func setup () {
        backgroundColor = .red
    }
}
Jason Moore
fonte
4

Swift 4.0, se você deseja usar a visualização do arquivo xib, então isto é para você. Criei a classe CustomCalloutView Subclasse de UIView. Eu criei um arquivo xib e em IB apenas selecione o proprietário do arquivo, selecione o inspetor de atributos, defina o nome da classe para CustomCalloutView e crie uma saída em sua classe.

    import UIKit
    class CustomCalloutView: UIView {

        @IBOutlet var viewCallout: UIView! // This is main view

        @IBOutlet weak var btnCall: UIButton! // subview of viewCallout
        @IBOutlet weak var btnDirection: UIButton! // subview of viewCallout
        @IBOutlet weak var btnFavourite: UIButton! // subview of viewCallout 

       // let nibName = "CustomCalloutView" this is name of xib file

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

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

        func nibSetup() {
            Bundle.main.loadNibNamed(String(describing: CustomCalloutView.self), owner: self, options: nil)
            guard let contentView = viewCallout else { return } // adding main view 
            contentView.frame = self.bounds //Comment this line it take default frame of nib view
           // custom your view properties here
            self.addSubview(contentView)
        }
    }

// Agora adicionando

    let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
    self.view.addSubview(viewCustom)
Gurjinder Singh
fonte
-1

Aqui está um exemplo de como normalmente construo minhas subclasses (UIView). Eu tenho o conteúdo como variáveis ​​para que eles possam ser acessados ​​e ajustados talvez mais tarde em alguma outra classe. Também mostrei como uso o layout automático e adição de conteúdo.

Por exemplo, em um ViewController eu tenho esta visão inicializada em ViewDidLoad (), uma vez que só é chamada uma vez quando a visão está visível. Então eu uso essas funções que crio aqui addContentToView()e então activateConstraints()para construir o conteúdo e definir restrições. Se eu posteriormente em um ViewController quiser que a cor de, digamos, um botão seja vermelho, eu apenas faço isso naquela função específica naquele ViewController. Algo como:func tweaksome(){ self.customView.someButton.color = UIColor.red}

class SomeView: UIView {


var leading: NSLayoutConstraint!
var trailing: NSLayoutConstraint!
var bottom: NSLayoutConstraint!
var height: NSLayoutConstraint!


var someButton: UIButton = {
    var btn: UIButton = UIButton(type: UIButtonType.system)
    btn.setImage(UIImage(named: "someImage"), for: .normal)
    btn.translatesAutoresizingMaskIntoConstraints = false
    return btn
}()

var btnLeading: NSLayoutConstraint!
var btnBottom: NSLayoutConstraint!
var btnTop: NSLayoutConstraint!
var btnWidth: NSLayoutConstraint!

var textfield: UITextField = {
    var tf: UITextField = UITextField()
    tf.adjustsFontSizeToFitWidth = true
    tf.placeholder = "Cool placeholder"
    tf.translatesAutoresizingMaskIntoConstraints = false
    tf.backgroundColor = UIColor.white
    tf.textColor = UIColor.black
    return tf
}()
var txtfieldLeading: NSLayoutConstraint!
var txtfieldTrailing: NSLayoutConstraint!
var txtfieldCenterY: NSLayoutConstraint!

override init(frame: CGRect){
    super.init(frame: frame)
    self.translatesAutoresizingMaskIntoConstraints = false
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    //fatalError("init(coder:) has not been implemented")
}



/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code

}
*/
func activateConstraints(){
    NSLayoutConstraint.activate([self.btnLeading, self.btnBottom, self.btnTop, self.btnWidth])
    NSLayoutConstraint.activate([self.txtfieldCenterY, self.txtfieldLeading, self.txtfieldTrailing])
}

func addContentToView(){
    //setting the sizes
    self.addSubview(self.userLocationBtn)

    self.btnLeading = NSLayoutConstraint(
        item: someButton,
        attribute: .leading,
        relatedBy: .equal,
        toItem: self,
        attribute: .leading,
        multiplier: 1.0,
        constant: 5.0)
    self.btnBottom = NSLayoutConstraint(
        item: someButton,
        attribute: .bottom,
        relatedBy: .equal,
        toItem: self,
        attribute: .bottom,
        multiplier: 1.0,
        constant: 0.0)
    self.btnTop = NSLayoutConstraint(
        item: someButton,
        attribute: .top,
        relatedBy: .equal,
        toItem: self,
        attribute: .top,
        multiplier: 1.0,
        constant: 0.0)
    self.btnWidth = NSLayoutConstraint(
        item: someButton,
        attribute: .width,
        relatedBy: .equal,
        toItem: self,
        attribute: .height,
        multiplier: 1.0,
        constant: 0.0)        


    self.addSubview(self.textfield)
    self.txtfieldLeading = NSLayoutConstraint(
        item: self.textfield,
        attribute: .leading,
        relatedBy: .equal,
        toItem: someButton,
        attribute: .trailing,
        multiplier: 1.0,
        constant: 5)
    self.txtfieldTrailing = NSLayoutConstraint(
        item: self.textfield,
        attribute: .trailing,
        relatedBy: .equal,
        toItem: self.doneButton,
        attribute: .leading,
        multiplier: 1.0,
        constant: -5)
    self.txtfieldCenterY = NSLayoutConstraint(
        item: self.textfield,
        attribute: .centerY,
        relatedBy: .equal,
        toItem: self,
        attribute: .centerY,
        multiplier: 1.0,
        constant: 0.0)
}
}
Starlord
fonte