Swift sottoclasse UIView


95

Voglio creare UIViewuna sottoclasse e mostrare una vista simile al login. L'ho creato in Objective-C, ma ora voglio portarlo su Swift. Non uso storyboard, quindi creo tutta la mia interfaccia utente nel codice.

Ma il primo problema è che devo attuare initWithCoder. Gli ho dato un'implementazione predefinita poiché non verrà chiamato. Ora quando eseguo il programma si blocca, perché anch'io devo implementarlo initWithFrame. Ora ho capito:

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

La mia domanda è dove dovrei creare il mio campo di testo ecc ... e se non implemento mai frame e coder come posso "nasconderlo"?

Risposte:


174

Di solito faccio qualcosa del genere, è un po 'prolisso.

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

(Modifica: ho modificato la mia risposta in modo che la relazione tra gli inizializzatori sia più chiara)


4
Ma addBehavior viene chiamato due volte poiché viene chiamato initFrame e init. Se esegui il mio codice, viene stampato il primo frame init, quindi l'init predefinito
Haagenti

6
Roba buona, grazie. Invece di usare CGRectZerocredo che sia consigliato usare CGRect.zeroRect.
Mr Rogers

57
Questa roba dell'inizializzatore è incredibilmente complicata.
Ian Warburton

3
C'è un modo per farlo per il layout automatico? Il telaio è così obsoleto.
devios1

8
Questa non è una risposta completa. UIView supporta initWithCoding. Qualsiasi visualizzazione caricata da un pennino o da uno storyboard chiamerà il metodo initWithCoding e si bloccherà.
Iain Delaney

17

Questo è più semplice.

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

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

10

Esempio di sottoclasse UIView personalizzata

Di solito creo app iOS senza usare storyboard o pennini. Condividerò alcune tecniche che ho imparato per rispondere alle tue domande.

 

Nascondere initmetodi indesiderati

Il mio primo suggerimento è di dichiarare una base UIViewper nascondere gli inizializzatori indesiderati. Ho discusso questo approccio in dettaglio nella mia risposta a "Come nascondere gli inizializzatori specifici dello storyboard e del pennino nelle sottoclassi dell'interfaccia utente" . Nota: questo approccio presuppone che non utilizzerai i BaseViewsuoi discendenti negli storyboard o nei pennini poiché causerà intenzionalmente l'arresto anomalo dell'app.

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

La tua sottoclasse UIView personalizzata dovrebbe ereditare da BaseView. Deve chiamare super.init () nel suo inizializzatore. Non è necessario implementarlo init(coder:). Ciò è dimostrato nell'esempio seguente.

 

Aggiunta di un UITextField

Creo proprietà memorizzate per le sottoview referenziate al di fuori del initmetodo. In genere lo farei per un UITextField. Preferisco istanziare le sottoview all'interno della dichiarazione della proprietà della sottoview in questo modo:let textField = UITextField() .

UITextField non sarà visibile a meno che non lo si aggiunga all'elenco delle sottoview della visualizzazione personalizzata chiamando addSubview(_:). Ciò è dimostrato nell'esempio seguente.

 

Layout programmatico senza layout automatico

UITextField non sarà visibile a meno che non ne imposti le dimensioni e la posizione. Spesso eseguo il layout nel codice (senza utilizzare il layout automatico) all'interno del metodo layoutSubviews .layoutSubviews()viene chiamato inizialmente e ogni volta che si verifica un evento di ridimensionamento. Ciò consente di regolare il layout in base alle dimensioni di CustomView. Ad esempio, se CustomView appare a tutta larghezza su varie dimensioni di iPhone e iPad e si regola per la rotazione, deve adattarsi a molte dimensioni iniziali e ridimensionare dinamicamente.

È possibile fare riferimento a frame.heighte frame.widthall'interno layoutSubviews()per ottenere le dimensioni di CustomView come riferimento. Ciò è dimostrato nell'esempio seguente.

 

Esempio di sottoclasse UIView

Una sottoclasse UIView personalizzata contenente un UITextField che non deve essere implementato 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 programmatico con layout automatico

È inoltre possibile implementare il layout utilizzando Layout automatico nel codice. Dato che non lo faccio spesso, non mostrerò un esempio. Puoi trovare esempi di implementazione del layout automatico nel codice su Stack Overflow e altrove su Internet.

 

Framework di layout programmatico

Esistono framework open source che implementano il layout nel codice. Uno che mi interessa ma che non ho provato è LayoutKit . È stato scritto dal team di sviluppo e LinkedIn. Dal repository Github: "LinkedIn ha creato LayoutKit perché abbiamo scoperto che il layout automatico non è abbastanza performante per complicate gerarchie di visualizzazione in viste scorrevoli."

 

Perché mettere fatalErrorininit(coder:)

Quando si creano sottoclassi UIView che non verranno mai utilizzate in uno storyboard o in un pennino, è possibile introdurre inizializzatori con parametri e requisiti di inizializzazione diversi che non possono essere chiamati dal init(coder:)metodo. Se non hai fallito init (coder :) con un fatalError, potrebbe portare a problemi molto confusi su tutta la linea se usato accidentalmente in uno storyboard / pennino. Il fatalError afferma queste intenzioni.

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

Se si desidera eseguire del codice quando viene creata la sottoclasse, indipendentemente dal fatto che sia stata creata nel codice o in uno storyboard / pennino, è possibile fare qualcosa di simile (basato su risposta di 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
    }
}

e? aggiungendo fatalErrorsi vieta di iniziare questa visualizzazione con file
xib

@VyachaslavGerchicov, La mia risposta afferma il presupposto che non stai usando xib o storyboard come fa la risposta accettata e la domanda. La domanda osserva "Non uso storyboard, quindi creo tutta la mia interfaccia utente in codice".
Mobile Dan

la prossima volta che scrivi fatalErrornel metodo dealloc e ci dici che non funziona perché quella classe dovrebbe essere un singleton. Se preferisci creare elementi dell'interfaccia utente nel codice, non dovresti proibire manualmente tutti gli altri modi. Infine la domanda è come creare "programmaticamente senza storyboard" ma xibs / nibs non sono menzionati. Nel mio caso ho bisogno di creare un array di celle con + xib a livello di programmazione e passarle in DropDownMenuKite in questo modo non funziona perché l'autore di questa libreria vieta anche gli xib.
Vyachaslav Gerchicov

@VyachaslavGerchicov Sembra che la risposta di Jeff Gu Kang sia ciò che stai cercando poiché ospita Storyboard / Xibs
Mobile Dan

1
@VyachaslavGerchicov La domanda diceva anche "e se non implemento mai frame e coder come posso" nascondere "questo?" Quando si creano sottoclassi UIView che non verranno mai utilizzate in Xib / Storyboard, è possibile introdurre inizializzatori con parametri diversi che non possono essere chiamati dal metodo init (coder :). Se non hai fallito init (coder :) con un fatalError, potrebbe portare a problemi molto confusi su tutta la linea se usato accidentalmente in uno Xib / Storyboard. Il fatalError afferma queste intenzioni. Questa è una pratica intenzionale e comune come si vede nella risposta accettata.
Mobile Dan

4

È importante che la tua UIView possa essere creata dal costruttore di interfacce / storyboard o dal codice. Trovo utile avere un setupmetodo per ridurre la duplicazione di qualsiasi codice di installazione. per esempio

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

4

Swift 4.0, se vuoi usare la visualizzazione dal file xib, allora questo è per te. Ho creato CustomCalloutView classe Sottoclasse di UIView. Ho creato un file xib e in IB seleziona semplicemente il proprietario del file, quindi seleziona l'ispettore degli attributi imposta il nome della classe su CustomCalloutView, quindi crea presa nella tua 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)
        }
    }

// Ora aggiungendolo

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

-1

Ecco un esempio di come di solito costruisco le mie sottoclassi (UIView). Ho il contenuto come variabili in modo che possano essere consultate e modificate forse più tardi in qualche altra classe. Ho anche mostrato come utilizzo il layout automatico e l'aggiunta di contenuti.

Ad esempio in un ViewController ho questa vista inizializzata in ViewDidLoad () poiché viene chiamata solo una volta quando la vista è visibile. Quindi utilizzo queste funzioni che creo qui addContentToView()e poi activateConstraints()per costruire il contenuto e impostare i vincoli. Se in seguito in un ViewController voglio che il colore di diciamo un pulsante sia rosso, lo faccio solo in quella funzione specifica in quel ViewController. Qualcosa di simile a: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)
}
}
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.