Subkelas Swift UIView

95

Saya ingin membuat subkelas UIViewdan menampilkan tampilan seperti login. Saya telah membuat ini di Objective-C, tetapi sekarang saya ingin memindahkannya ke Swift. Saya tidak menggunakan storyboard, jadi saya membuat semua UI saya dalam kode.

Tapi masalah pertama yang harus saya implementasikan initWithCoder. Saya memberikannya implementasi default karena itu tidak akan dipanggil. Sekarang ketika saya menjalankan program itu macet, karena saya sudah menerapkannya initWithFramejuga. Sekarang saya mendapatkan ini:

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

Pertanyaan saya adalah di mana saya harus membuat textfield dll ... dan jika saya tidak pernah menerapkan frame dan coder bagaimana saya bisa "menyembunyikan" ini?

Haagenti
sumber

Jawaban:

174

Saya biasanya melakukan sesuatu seperti ini, agak bertele-tele.

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

(Sunting: Saya telah mengedit jawaban saya sehingga hubungan antara penginisialisasi lebih jelas)

Raymond
sumber
4
Tetapi addBehavior dipanggil dua kali karena initFrame dipanggil dan init. Jika Anda menjalankan kode saya, bingkai pertama init dicetak kemudian init default
Haagenti
6
Barang bagus, terima kasih. Alih-alih menggunakan CGRectZerosaya percaya itu disarankan untuk digunakan CGRect.zeroRect.
Tuan Rogers
57
Barang-barang inisialisasi ini sangat rumit.
Ian Warburton
3
Apakah ada cara untuk melakukan ini untuk Tata Letak Otomatis? Bingkai sudah ketinggalan zaman.
devios1
8
Ini bukanlah jawaban yang lengkap. UIView mendukung initWithCoding. Setiap tampilan yang dimuat dari ujung atau storyboard akan memanggil metode initWithCoding, dan akan mogok.
Iain Delaney
17

Ini lebih sederhana.

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

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

Contoh Subclass UIView Kustom

Saya biasanya membuat aplikasi iOS tanpa menggunakan storyboard atau ujung pena. Saya akan membagikan beberapa teknik yang telah saya pelajari untuk menjawab pertanyaan Anda.

 

Menyembunyikan initMetode yang Tidak Diinginkan

Saran pertama saya adalah mendeklarasikan basis UIViewuntuk menyembunyikan penginisialisasi yang tidak diinginkan. Saya telah membahas pendekatan ini secara rinci dalam jawaban saya untuk "Cara Menyembunyikan Papan Cerita dan Penginisialisasi Khusus Nib di Subkelas UI" . Catatan: Pendekatan ini mengasumsikan Anda tidak akan menggunakan BaseViewatau turunannya di storyboard atau ujung karena ini akan menyebabkan aplikasi error secara sengaja.

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

Subkelas UIView khusus Anda harus diwarisi dari BaseView. Itu harus memanggil super.init () di penginisialisasinya. Itu tidak perlu diimplementasikan init(coder:). Ini ditunjukkan pada contoh di bawah.

 

Menambahkan UITextField

Saya membuat properti yang disimpan untuk subview yang direferensikan di luar initmetode. Saya biasanya akan melakukannya untuk UITextField. Saya lebih suka subviews instantiate dalam deklarasi properti subview seperti ini: let textField = UITextField().

UITextField tidak akan terlihat kecuali Anda menambahkannya ke daftar subview tampilan kustom dengan memanggil addSubview(_:). Ini ditunjukkan pada contoh di bawah.

 

Tata Letak Terprogram Tanpa Tata Letak Otomatis

UITextField tidak akan terlihat kecuali Anda menyetel ukuran dan posisinya. Saya sering melakukan layout dalam kode (tidak menggunakan Auto Layout) dalam metode layoutSubviews .layoutSubviews()dipanggil pada awalnya dan setiap kali terjadi peristiwa pengubahan ukuran. Ini memungkinkan penyesuaian tata letak tergantung pada ukuran CustomView. Misalnya, jika Tampilan Kustom muncul dengan lebar penuh di berbagai ukuran iPhone dan iPad dan menyesuaikan rotasi, ia perlu mengakomodasi banyak ukuran awal dan mengubah ukuran secara dinamis.

Anda bisa merujuk ke frame.heightdan di frame.widthdalam layoutSubviews()untuk mendapatkan dimensi CustomView sebagai referensi. Ini ditunjukkan pada contoh di bawah.

 

Contoh Subclass UIView

Subclass UIView kustom yang berisi UITextField yang tidak perlu diimplementasikan 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)
    }
}

 

Tata Letak Terprogram dengan Tata Letak Otomatis

Anda juga dapat mengimplementasikan tata letak menggunakan Tata Letak Otomatis dalam kode. Karena saya jarang melakukan ini, saya tidak akan menunjukkan contoh. Anda dapat menemukan contoh penerapan Tata Letak Otomatis dalam kode di Stack Overflow dan di tempat lain di Internet.

 

Kerangka Tata Letak Terprogram

Ada kerangka kerja sumber terbuka yang mengimplementasikan tata letak dalam kode. Salah satu yang saya minati tetapi belum pernah dicoba adalah LayoutKit . Itu ditulis oleh tim pengembangan dan LinkedIn. Dari gudang Github: "LinkedIn membuat LayoutKit karena kami menemukan bahwa Tata Letak Otomatis tidak cukup berfungsi untuk hierarki tampilan yang rumit dalam tampilan yang dapat digulir."

 

Mengapa menempatkan fatalErrordiinit(coder:)

Saat membuat subclass UIView yang tidak akan pernah digunakan di storyboard atau nib, Anda mungkin memperkenalkan penginisialisasi dengan parameter berbeda dan persyaratan inisialisasi yang tidak dapat dipanggil oleh init(coder:)metode. Jika Anda tidak gagal init (coder :) dengan a fatalError, ini dapat menyebabkan masalah yang sangat membingungkan jika secara tidak sengaja digunakan dalam storyboard / nib. FatalError menegaskan maksud ini.

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

Jika Anda ingin menjalankan beberapa kode saat subclass dibuat terlepas dari apakah itu dibuat dalam kode atau storyboard / nib maka Anda dapat melakukan hal seperti berikut (berdasarkan jawaban 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
    }
}
Ponsel Dan
sumber
dan? dengan menambahkan fatalErrorAnda melarang untuk
memulai
@VyachaslavGerchicov, Jawaban saya menyatakan asumsi bahwa Anda tidak menggunakan xibs atau storyboard seperti halnya jawaban yang diterima dan pertanyaannya. Catatan pertanyaan "Saya tidak menggunakan storyboard, jadi saya membuat semua UI saya dalam kode".
Mobile Dan
lain kali Anda menulis fatalErrordi dalam metode dealloc dan memberi tahu kami bahwa itu tidak berfungsi karena kelas itu harus tunggal. Jika Anda lebih suka membuat elemen UI dalam kode maka Anda tidak boleh melarang secara manual semua cara lain. Akhirnya pertanyaannya adalah bagaimana membuat "secara terprogram tanpa papan cerita" tetapi xibs / nibs tidak disebutkan. Dalam kasus saya, saya perlu membuat array sel dengan secara terprogram + xib dan meneruskannya ke dalamnya DropDownMenuKitdan cara ini tidak berfungsi karena penulis pustaka ini juga melarang xibs.
Vyachaslav Gerchicov
@VyachaslavGerchicov Sepertinya jawaban Jeff Gu Kang adalah apa yang Anda cari karena mengakomodasi Papan Cerita / Xibs
Seluler Dan
1
@VyachaslavGerchicov Pertanyaannya juga menyatakan "dan jika saya tidak pernah menerapkan frame dan coder bagaimana saya bisa" menyembunyikan "ini?" Saat membuat subclass UIView yang tidak akan pernah digunakan di Xib / Storyboard, Anda mungkin memperkenalkan penginisialisasi dengan parameter berbeda yang tidak dapat dipanggil oleh metode init (coder :). Jika Anda tidak gagal init (coder :) dengan fatalError, ini dapat menyebabkan masalah yang sangat membingungkan jika secara tidak sengaja digunakan dalam Xib / Storyboard. FatalError menyatakan maksud ini. Ini adalah praktik yang disengaja dan umum seperti yang terlihat pada jawaban yang diterima.
Mobile Dan
4

UIView Anda harus dibuat oleh pembuat antarmuka / storyboard atau dari kode. Saya merasa berguna memiliki setupmetode untuk mengurangi duplikasi kode pengaturan apa pun. misalnya

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
sumber
4

Swift 4.0, Jika Anda ingin menggunakan tampilan dari file xib, maka ini untuk Anda. Saya membuat kelas Sub kelas CustomCalloutView dari UIView. Saya telah membuat file xib dan di IB cukup pilih pemilik file kemudian pilih Atribut inspektur set nama kelas ke CustomCalloutView, lalu buat outlet di kelas Anda.

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

// Sekarang menambahkannya

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

Berikut adalah contoh bagaimana saya biasanya membangun subclass saya (UIView). Saya memiliki konten sebagai variabel sehingga mereka dapat diakses dan diubah mungkin nanti di kelas lain. Saya juga telah menunjukkan bagaimana saya menggunakan tata letak otomatis dan menambahkan konten.

Misalnya dalam ViewController saya memiliki tampilan ini diinisialisasi Dalam ViewDidLoad () karena itu hanya dipanggil sekali ketika tampilan terlihat. Kemudian saya menggunakan fungsi ini yang saya buat di sini addContentToView()dan kemudian activateConstraints()untuk membangun konten dan mengatur batasan. Jika saya nanti di ViewController ingin warna katakanlah tombol menjadi merah, saya hanya melakukannya di fungsi tertentu di ViewController itu. Sesuatu seperti: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
sumber