Schnelle Unterklasse UIView

94

Ich möchte eine Unterklasse erstellen UIViewund eine loginähnliche Ansicht anzeigen. Ich habe dies in Objective-C erstellt, möchte es aber jetzt auf Swift portieren. Ich verwende keine Storyboards, daher erstelle ich meine gesamte Benutzeroberfläche im Code.

Aber das erste Problem ist, dass ich implementieren muss initWithCoder. Ich habe ihm eine Standardimplementierung gegeben, da er nicht aufgerufen wird. Wenn ich jetzt das Programm starte, stürzt es ab, weil ich es ebenfalls implementieren muss initWithFrame. Jetzt habe ich folgendes:

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

Meine Frage ist, wo ich mein Textfeld usw. erstellen soll. Und wenn ich nie Frame und Codierer implementiere, wie kann ich dies "verbergen"?

Haagenti
quelle

Antworten:

174

Normalerweise mache ich so etwas, es ist ein bisschen ausführlich.

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

(Bearbeiten: Ich habe meine Antwort so bearbeitet, dass die Beziehung zwischen den Initialisierern klarer wird.)

Raymond
quelle
4
AddBehavior wird jedoch zweimal aufgerufen, da initFrame und init aufgerufen werden. Wenn Sie meinen Code ausführen, wird der erste Frame-Init gedruckt, dann der Standard-Init
Haagenti
6
Gutes Zeug, danke. Anstatt zu verwenden, CGRectZeroglaube ich, wird empfohlen, zu verwenden CGRect.zeroRect.
Herr Rogers
57
Dieses Initialisierungsmaterial ist verrückt kompliziert.
Ian Warburton
3
Gibt es eine Möglichkeit, dies für das automatische Layout zu tun? Rahmen ist so veraltet.
Devios1
8
Dies ist keine vollständige Antwort. UIView unterstützt initWithCoding. Jede Ansicht, die von einer Schreibfeder oder einem Storyboard geladen wird, ruft die Methode initWithCoding auf und stürzt ab.
Iain Delaney
17

Das ist einfacher.

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

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

Beispiel für eine benutzerdefinierte UIView-Unterklasse

Normalerweise erstelle ich iOS-Apps ohne Storyboards oder Schreibfedern. Ich werde einige Techniken teilen, die ich gelernt habe, um Ihre Fragen zu beantworten.

 

Unerwünschte initMethoden ausblenden

Mein erster Vorschlag ist, eine Basis UIViewzu deklarieren , um unerwünschte Initialisierer auszublenden. Ich habe diesen Ansatz in meiner Antwort auf "Ausblenden von Storyboard- und Nib-spezifischen Initialisierern in UI-Unterklassen" ausführlich erörtert . Hinweis: Bei diesem Ansatz wird davon ausgegangen, dass Sie BaseViewoder seine Nachkommen nicht in Storyboards oder Schreibfedern verwenden, da die App absichtlich abstürzt.

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

Ihre benutzerdefinierte UIView-Unterklasse sollte von erben BaseView. Es muss super.init () in seinem Initialisierer aufrufen. Es muss nicht implementiert werden init(coder:). Dies wird im folgenden Beispiel gezeigt.

 

Hinzufügen eines UITextField

Ich erstelle gespeicherte Eigenschaften für Unteransichten, auf die außerhalb der initMethode verwiesen wird. Ich würde dies normalerweise für ein UITextField tun. Ich bevorzuge es, Unteransichten innerhalb der Deklaration der Unteransichtseigenschaft wie folgt zu instanziieren : let textField = UITextField().

Das UITextField ist nur sichtbar, wenn Sie es durch Aufrufen zur Unteransichtsliste der benutzerdefinierten Ansicht hinzufügen addSubview(_:). Dies wird im folgenden Beispiel gezeigt.

 

Programmatisches Layout ohne automatisches Layout

Das UITextField ist nur sichtbar, wenn Sie seine Größe und Position festlegen. Ich mache oft Layout in Code (ohne Auto Layout) innerhalb der layoutSubviews Methode .layoutSubviews()wird anfänglich aufgerufen und immer dann, wenn ein Größenänderungsereignis auftritt. Dies ermöglicht das Anpassen des Layouts abhängig von der Größe von CustomView. Wenn CustomView beispielsweise auf verschiedenen Größen von iPhones und iPads in voller Breite angezeigt wird und sich an die Drehung anpasst, muss es viele Anfangsgrößen berücksichtigen und die Größe dynamisch ändern.

Sie können auf frame.heightund frame.widthinnerhalb verweisen, layoutSubviews()um die Abmessungen von CustomView als Referenz zu erhalten. Dies wird im folgenden Beispiel gezeigt.

 

Beispiel UIView-Unterklasse

Eine benutzerdefinierte UIView-Unterklasse, die ein UITextField enthält, das nicht implementiert werden muss 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)
    }
}

 

Programmatisches Layout mit automatischem Layout

Sie können das Layout auch mithilfe des automatischen Layouts im Code implementieren. Da ich das nicht oft mache, werde ich kein Beispiel zeigen. Beispiele für die Implementierung des automatischen Layouts im Code finden Sie im Stapelüberlauf und an anderer Stelle im Internet.

 

Programmatische Layout-Frameworks

Es gibt Open Source-Frameworks, die das Layout in Code implementieren. Eine, die mich interessiert, aber noch nicht ausprobiert hat, ist LayoutKit . Es wurde vom Entwicklungsteam ein LinkedIn geschrieben. Aus dem Github-Repository: "LinkedIn hat LayoutKit erstellt, weil wir festgestellt haben, dass das automatische Layout für komplizierte Ansichtshierarchien in scrollbaren Ansichten nicht leistungsfähig genug ist."

 

Warum setzen fatalErrorininit(coder:)

Wenn Sie UIView-Unterklassen erstellen, die niemals in einem Storyboard oder einer Schreibfeder verwendet werden, können Sie Initialisierer mit unterschiedlichen Parametern und Initialisierungsanforderungen einführen, die von der init(coder:)Methode nicht aufgerufen werden konnten . Wenn Sie init (coder :) nicht mit a versagt haben fatalError, kann dies zu sehr verwirrenden Problemen führen, wenn Sie versehentlich in einem Storyboard / einer Feder verwendet werden. Der fatalError bestätigt diese Absichten.

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

Wenn Sie beim Erstellen der Unterklasse Code ausführen möchten, unabhängig davon, ob dieser im Code oder in einem Storyboard / einer Feder erstellt wurde, können Sie Folgendes tun (basierend auf der Antwort von 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
    }
}
Mobiler Dan
quelle
und? fatalErrorIndem Sie hinzufügen , verbieten Sie, diese Ansicht mit XIB-Dateien zu initiieren
Vyachaslav Gerchicov
@VyachaslavGerchicov, Meine Antwort besagt die Annahme, dass Sie keine Xibs oder Storyboards verwenden, ebenso wie die akzeptierte Antwort und die Frage. In der Frage heißt es: "Ich verwende keine Storyboards, daher erstelle ich meine gesamte Benutzeroberfläche im Code."
Mobile Dan
fatalErrorWenn Sie das nächste Mal in die Dealloc-Methode schreiben und uns mitteilen, dass dies nicht funktioniert, sollte diese Klasse ein Singleton sein. Wenn Sie es vorziehen, UI-Elemente im Code zu erstellen, sollten Sie alle anderen Möglichkeiten nicht manuell verbieten. Schließlich stellt sich die Frage, wie "programmgesteuert ohne Storyboards" erstellt werden kann, Xibs / Nibs werden jedoch nicht erwähnt. In meinem Fall muss ich mit programmgesteuert + xib ein Array von Zellen erstellen und diese übergeben. DropDownMenuKitDies funktioniert nicht, da der Autor dieser Bibliothek auch xibs verbietet.
Vyachaslav Gerchicov
@VyachaslavGerchicov Es klingt wie Jeff Gu Kangs Antwort ist, was Sie suchen, da es Storyboards / Xibs
Mobile Dan
1
@VyachaslavGerchicov Die Frage lautete auch "und wenn ich nie Frame und Codierer implementiere, wie kann ich das" verstecken "?" Wenn Sie UIView-Unterklassen erstellen, die in einem Xib / Storyboard niemals verwendet werden, können Sie Initialisierer mit unterschiedlichen Parametern einführen, die von der Methode init (coder :) nicht aufgerufen werden konnten. Wenn Sie init (coder :) nicht mit einem fatalError fehlgeschlagen haben, kann dies zu sehr verwirrenden Problemen führen, wenn Sie es versehentlich in einem Xib / Storyboard verwenden. Der fatalError gibt diese Absichten an. Dies ist eine absichtliche und übliche Praxis, wie aus der akzeptierten Antwort hervorgeht.
Mobile Dan
4

Es ist wichtig, dass Ihr UIView vom Interface Builder / Storyboard oder aus Code erstellt werden kann. Ich finde es nützlich, eine setupMethode zu haben, um das Duplizieren von Setup-Code zu reduzieren. z.B

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

Swift 4.0, Wenn Sie die Ansicht aus der XIB-Datei verwenden möchten, ist dies das Richtige für Sie. Ich habe die CustomCalloutView-Klasse Subklasse von UIView erstellt. Ich habe eine xib-Datei erstellt und in IB einfach den Dateieigentümer ausgewählt, dann den Attributinspektor ausgewählt, den Klassennamen auf CustomCalloutView gesetzt und dann den Ausgang in Ihrer Klasse erstellt.

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

// Jetzt füge es hinzu

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

Hier ist ein Beispiel, wie ich normalerweise meine Unterklassen (UIView) erstelle. Ich habe den Inhalt als Variablen, damit auf sie zugegriffen und sie möglicherweise später in einer anderen Klasse optimiert werden können. Ich habe auch gezeigt, wie ich das automatische Layout verwende und Inhalte hinzufüge.

Zum Beispiel habe ich in einem ViewController diese Ansicht in ViewDidLoad () initialisiert, da diese nur einmal aufgerufen wird, wenn die Ansicht sichtbar ist. Dann benutze ich diese Funktionen, die ich hier addContentToView()und dann mache activateConstraints(), um den Inhalt zu erstellen und Einschränkungen festzulegen. Wenn ich später in einem ViewController möchte, dass die Farbe einer Schaltfläche rot ist, mache ich das einfach in dieser speziellen Funktion in diesem ViewController. Etwas wie: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
quelle