Como escrevo um init personalizado para uma subclasse UIView no Swift?

125

Digamos que eu queira inituma UIViewsubclasse com a Stringe an Int.

Como eu faria isso no Swift se estou apenas subclassificando UIView? Se eu apenas criar uma init()função personalizada , mas os parâmetros forem uma String e uma Int, isso me diz que "super.init () não é chamado antes de retornar do inicializador".

E se eu ligar super.init(), me disseram que devo usar um inicializador designado. O que devo usar lá? A versão do quadro? A versão do codificador? Ambos? Por quê?

Doug Smith
fonte

Respostas:

207

A init(frame:)versão é o inicializador padrão. Você deve chamá-lo somente depois de inicializar suas variáveis ​​de instância. Se essa visualização estiver sendo reconstituída a partir de uma Nib, seu inicializador personalizado não será chamado e, em vez disso, a init?(coder:)versão será chamada. Como o Swift agora requer uma implementação do exigido init?(coder:), atualizei o exemplo abaixo e alterei as letdeclarações da variável para vare opcional. Nesse caso, você os inicializaria em awakeFromNib()algum momento posterior.

class TestView : UIView {
    var s: String?
    var i: Int?
    init(s: String, i: Int) {
        self.s = s
        self.i = i
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
Wolf McNally
fonte
5
Então, por todos os meios, faça-os var. Mas a melhor prática padrão no Swift é declarar variáveis, a letmenos que haja um motivo para declará-las var. Portanto, não havia esse motivo para fazê-lo no meu exemplo de código acima let.
Lobo McNally
2
Este código não compila. Você precisa implementar o inicializador necessário init(coder:).
Decade Moon
3
interessante como isso compilou anos atrás. Hoje em dia ele reclama sob init (codificador :) que "Propriedade self.s não inicializado na chamada super.init"
Mafioso
Exemplo fixo para o Swift 3.1. Compila em um playground importando o UIKit.
9139 Wolf McNally #
1
@ LightNight eu fiz se iopcional para manter as coisas simples aqui. Se não fossem opcionais, também precisariam ser inicializados no inicializador necessário. Torná-los opcionais significa que eles serão nilquando super.init()chamados. Se eles não fossem opcionais, eles precisariam ser atribuídos antes de chamar super.init ().
Wolf McNally
32

Eu crio um init comum para o designado e necessário. Por questões de conveniência, eu delego init(frame:)com quadro de zero.

Ter zero quadro não é um problema, porque normalmente a visualização está dentro da visualização de um ViewController; sua visualização personalizada terá uma chance boa e segura de planejar suas subvisões quando a supervisão ligar layoutSubviews()ou updateConstraints(). Essas duas funções são chamadas pelo sistema recursivamente em toda a hierarquia de visualizações. Você pode usar updateContstraints()ou layoutSubviews(). updateContstraints()é chamado primeiro, então layoutSubviews(). Em updateConstraints()certifique-se chamar de super última . Em layoutSubviews(), ligue super primeiro .

Aqui está o que eu faço:

@IBDesignable
class MyView: UIView {

      convenience init(args: Whatever) {
          self.init(frame: CGRect.zero)
          //assign custom vars
      }

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

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

      override func prepareForInterfaceBuilder() {
           super.prepareForInterfaceBuilder()
           commonInit()
      }

      private func commonInit() {
           //custom initialization
      }

      override func updateConstraints() {
           //set subview constraints here
           super.updateConstraints()
      }

      override func layoutSubviews() {
           super.layoutSubviews()
           //manually set subview frames here
      }

}
MH175
fonte
1
Não deve funcionar: o uso de 'self' na chamada de método 'commonInit' antes de super.init inicializar o self
surfrider
1
Inicialize argumentos personalizados após a chamada self.init. Atualizei minha resposta.
MH175
1
Mas e se você quiser inicializar algumas propriedades no commonInitmétodo, mas não puder colocá-las depois superneste caso, porque você deve inicializar todas as propriedades ANTES de superchamar. Lol, parece um loop morto.
surfrider
1
É assim que a inicialização Swift geralmente funciona: procure por "inicialização em duas fases". Você pode usar opcionais implicitamente desembrulhados, mas eu recomendo isso. Sua arquitetura, especialmente ao lidar com visualizações, deve inicializar todas as propriedades locais. Eu usei esse método commonInit () para centenas de visualizações agora. Funciona
MH175
17

Aqui está como eu faço isso no iOS 9 no Swift -

import UIKit

class CustomView : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        //for debug validation
        self.backgroundColor = UIColor.blueColor();
        print("My Custom Init");

        return;
    }

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

Aqui está um projeto completo com exemplo:

J-Dizzle
fonte
2
isso seria usar a tela inteira para a vista
RAPTOX
1
Sim! Se estiver interessado em um subexibição parcial, deixe-me saber e eu vou postar isso também
J-Dizzle
1
Eu gosto mais desta resposta, porque ter o fatalError significa que não preciso inserir nenhum código no init necessário.
Carter Medlin
1
@ J-Dizzle, eu gostaria de ver a solução para visualizações parciais.
Ari Lacenski 29/09/16
sua resposta não está dizendo o oposto da resposta aceita? Quer dizer que você está fazendo a personalização depois super.init, mas ele disse que deve ser feito antes super.init...
Mel
11

Aqui está como eu faço uma subview no iOS no Swift -

class CustomSubview : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        let windowHeight : CGFloat = 150;
        let windowWidth  : CGFloat = 360;

        self.backgroundColor = UIColor.whiteColor();
        self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
        self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);

        //for debug validation
        self.backgroundColor = UIColor.grayColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}
J-Dizzle
fonte
4
Bom uso da chamada fatalError (). Eu estava tendo que usar opcionais apenas para silenciar os avisos de um inicializador que nem estava sendo usado. Isso cala a boca! Obrigado.
Mike Critchley