คลาสย่อยของ Swift UIView


95

ฉันต้องการคลาสย่อยUIViewและแสดงการเข้าสู่ระบบเช่นมุมมอง ฉันสร้างสิ่งนี้ใน Objective-C แล้ว แต่ตอนนี้ฉันต้องการพอร์ตไปที่ Swift ฉันไม่ได้ใช้สตอรี่บอร์ดดังนั้นฉันจึงสร้าง UI ทั้งหมดในโค้ด

initWithCoderแต่ปัญหาแรกคือว่าผมจะต้องดำเนินการ ฉันให้การใช้งานเริ่มต้นเนื่องจากจะไม่มีการเรียกใช้ ตอนนี้เมื่อฉันรันโปรแกรมมันขัดข้องเพราะฉันก็ใช้งานinitWithFrameเช่นกัน ตอนนี้ฉันได้รับสิ่งนี้:

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

คำถามของฉันคือฉันควรสร้างฟิลด์ข้อความ ฯลฯ ที่ไหน ... และถ้าฉันไม่เคยใช้ frame และ coder ฉันจะ "ซ่อน" สิ่งนี้ได้อย่างไร

คำตอบ:


175

ฉันมักจะทำอะไรแบบนี้มันเป็นเรื่องฟุ่มเฟือย

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

(แก้ไข: ฉันได้แก้ไขคำตอบของฉันเพื่อให้ความสัมพันธ์ระหว่างตัวเริ่มต้นชัดเจนยิ่งขึ้น)


4
แต่ addBehavior ถูกเรียกสองครั้งเนื่องจากมีการเรียกใช้ initFrame และ init หากคุณเรียกใช้รหัสของฉัน init เฟรมแรกจะถูกพิมพ์ค่าเริ่มต้นเริ่มต้น
Haagenti

6
สิ่งที่ดีขอบคุณ แทนการใช้ผมเชื่อว่ามันแนะนำให้ใช้CGRectZero CGRect.zeroRect
Mr Rogers

57
สิ่งที่เริ่มต้นนี้ซับซ้อนมาก
Ian Warburton

3
มีวิธีใดบ้างในการทำโครงร่างอัตโนมัติ กรอบจึงล้าสมัย
devios1

8
นี่ไม่ใช่คำตอบที่สมบูรณ์ UIView รองรับ initWithCoding ทุกมุมมองที่โหลดจากปลายปากกาหรือสตอรีบอร์ดจะเรียกเมธอด initWithCoding และหยุด
Iain Delaney

17

นี่เป็นเรื่องง่ายมากขึ้น

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

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

10

ตัวอย่างคลาสย่อย UIView ที่กำหนดเอง

ฉันมักจะสร้างแอป iOS โดยไม่ใช้สตอรี่บอร์ดหรือปลายปากกา ฉันจะแบ่งปันเทคนิคบางอย่างที่ฉันได้เรียนรู้เพื่อตอบคำถามของคุณ

 

การซ่อนinitวิธีการที่ไม่ต้องการ

คำแนะนำแรกของฉันคือการประกาศฐานUIViewเพื่อซ่อนตัวเริ่มต้นที่ไม่ต้องการ ผมได้กล่าวถึงวิธีการนี้ในรายละเอียดในคำตอบของฉัน "วิธีการซ่อน Storyboard และปลายปากกา Initializers เฉพาะใน UI Subclasses" หมายเหตุ: วิธีนี้ถือว่าคุณจะไม่ใช้BaseViewหรือลูกหลานของมันในสตอรีบอร์ดหรือไส้ปากกาเนื่องจากจะทำให้แอปหยุดทำงานโดยเจตนา

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

คุณ subclass UIView BaseViewที่กำหนดเองควรสืบทอดจาก ต้องเรียก super.init () ใน initializer init(coder:)มันไม่จำเป็นต้องใช้ นี่แสดงให้เห็นในตัวอย่างด้านล่าง

 

การเพิ่ม UITextField

ฉันสร้างคุณสมบัติที่จัดเก็บไว้สำหรับมุมมองย่อยที่อ้างอิงนอกinitเมธอด โดยทั่วไปฉันจะทำเช่นนั้นสำหรับ UITextField ฉันชอบที่จะ subviews instantiate ภายในประกาศของทรัพย์สิน subview let textField = UITextField()เช่นนี้:

UITextField จะมองไม่เห็นจนกว่าคุณจะเพิ่มไปที่รายการ subview addSubview(_:)มุมมองที่กำหนดเองโดยการเรียก นี่แสดงให้เห็นในตัวอย่างด้านล่าง

 

เค้าโครงแบบเป็นโปรแกรมที่ไม่มีเค้าโครงอัตโนมัติ

UITextField จะไม่สามารถมองเห็นได้เว้นแต่คุณจะกำหนดขนาดและตำแหน่ง ฉันมักจะทำรูปแบบในรหัส (ไม่ได้ใช้เค้าโครงอัตโนมัติ) ภายในวิธี layoutSubviews layoutSubviews()ถูกเรียกในตอนแรกและเมื่อใดก็ตามที่มีเหตุการณ์การปรับขนาดเกิดขึ้น สิ่งนี้ช่วยให้สามารถปรับเค้าโครงได้ขึ้นอยู่กับขนาดของ CustomView ตัวอย่างเช่นหาก CustomView ปรากฏเต็มความกว้างบน iPhone และ iPad ขนาดต่างๆและปรับให้หมุนได้จำเป็นต้องรองรับขนาดเริ่มต้นจำนวนมากและปรับขนาดแบบไดนามิก

คุณสามารถอ้างถึงframe.heightและframe.widthภายในlayoutSubviews()เพื่อรับมิติข้อมูลของ CustomView สำหรับการอ้างอิง นี่แสดงให้เห็นในตัวอย่างด้านล่าง

 

ตัวอย่าง UIView Subclass

UIView กำหนดเอง subclass มี UITextField 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)
    }
}

 

เค้าโครงแบบเป็นโปรแกรมพร้อมเค้าโครงอัตโนมัติ

คุณยังสามารถใช้เลย์เอาต์โดยใช้การจัดวางอัตโนมัติในโค้ด เนื่องจากฉันไม่ได้ทำแบบนี้บ่อยๆฉันจะไม่แสดงตัวอย่าง คุณสามารถดูตัวอย่างการใช้งาน Auto Layout ในโค้ดบน Stack Overflow และที่อื่น ๆ บนอินเทอร์เน็ต

 

กรอบรูปแบบโปรแกรม

มีเฟรมเวิร์กโอเพนซอร์สที่ใช้เลย์เอาต์ในโค้ด หนึ่งฉันสนใจใน แต่ไม่ได้พยายามเป็นLayoutKit ซึ่งเขียนโดยทีมพัฒนา LinkedIn จากที่เก็บ Github: "LinkedIn สร้าง LayoutKit เนื่องจากเราพบว่า Auto Layout มีประสิทธิภาพไม่เพียงพอสำหรับลำดับชั้นของมุมมองที่ซับซ้อนในมุมมองที่เลื่อนได้"

 

ทำไมใส่fatalErrorในinit(coder:)

เมื่อสร้างคลาสย่อย UIView ที่จะไม่ใช้ในสตอรีบอร์ดหรือปลายปากกาคุณอาจแนะนำตัวเริ่มต้นที่มีพารามิเตอร์และข้อกำหนดการเริ่มต้นที่แตกต่างกันซึ่งไม่สามารถเรียกใช้โดยinit(coder:)วิธีการ หากคุณไม่ได้ทำการ init (coder :) ด้วย a fatalErrorอาจทำให้เกิดปัญหาที่สับสนมากในบรรทัดหากใช้ใน storyboard / nib โดยไม่ได้ตั้งใจ fatalError ยืนยันเจตนาเหล่านี้

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

หากคุณต้องการเรียกใช้โค้ดบางอย่างเมื่อคลาสย่อยถูกสร้างขึ้นโดยไม่คำนึงว่าจะสร้างในโค้ดหรือสตอรี่บอร์ด / ปลายปากกาคุณสามารถทำสิ่งต่อไปนี้ได้ (ตามคำตอบของ 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
    }
}

และ? โดยการเพิ่มfatalErrorคุณห้ามไม่ให้เริ่มมุมมองนี้ด้วยไฟล์
xib

@VyachaslavGerchicov คำตอบของฉันระบุสมมติฐานที่ว่าคุณไม่ได้ใช้ xibs หรือสตอรี่บอร์ดเช่นเดียวกับคำตอบที่ยอมรับและคำถาม คำถามระบุว่า "ฉันไม่ได้ใช้สตอรี่บอร์ดดังนั้นฉันจึงสร้าง UI ทั้งหมดในโค้ด"
Mobile Dan

ครั้งต่อไปที่คุณเขียนfatalErrorภายในเมธอด dealloc และบอกเราว่ามันใช้ไม่ได้เพราะคลาสนั้นควรเป็นซิงเกิลตัน หากคุณต้องการสร้างองค์ประกอบ UI ในโค้ดคุณไม่ควรห้ามวิธีอื่นด้วยตนเองทั้งหมด ในที่สุดคำถามก็คือวิธีสร้าง "แบบเขียนโปรแกรมโดยไม่มีสตอรี่บอร์ด" แต่ไม่ได้กล่าวถึง xibs / nib ในกรณีของฉันฉันต้องสร้างอาร์เรย์ของเซลล์ด้วยโปรแกรม + xib และส่งผ่านเข้าไปDropDownMenuKitและวิธีนี้ใช้ไม่ได้ผลเนื่องจากผู้เขียนไลบรารีนี้ห้าม xib ด้วย
Vyachaslav Gerchicov

@VyachaslavGerchicov ดูเหมือนคำตอบของ Jeff Gu Kangคือสิ่งที่คุณกำลังมองหาเนื่องจากรองรับ Storyboards / Xibs
Mobile Dan เมื่อ

1
@VyachaslavGerchicov คำถามยังระบุว่า "และถ้าฉันไม่เคยใช้ frame และ coder ฉันจะ" ซ่อน "สิ่งนี้ได้อย่างไร" เมื่อสร้างคลาสย่อย UIView ที่จะไม่ถูกใช้ใน Xib / Storyboard คุณอาจแนะนำตัวเริ่มต้นด้วยพารามิเตอร์ต่าง ๆ ที่ไม่สามารถเรียกใช้โดยวิธี init (coder :) หากคุณไม่ได้ทำการ init (coder :) ด้วย fatalError อาจทำให้เกิดปัญหาที่สับสนมากหากใช้ใน Xib / Storyboard โดยไม่ได้ตั้งใจ fatalError ระบุความตั้งใจเหล่านี้ นี่เป็นวิธีปฏิบัติโดยเจตนาและเป็นเรื่องธรรมดาตามที่เห็นในคำตอบที่ยอมรับ
Mobile Dan

4

สิ่งสำคัญคือคุณสามารถสร้าง UIView ได้โดยตัวสร้างอินเทอร์เฟซ / สตอรีบอร์ดหรือจากโค้ด ฉันคิดว่ามีประโยชน์ที่จะมีsetupวิธีลดการทำซ้ำรหัสการตั้งค่าใด ๆ เช่น

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 หากคุณต้องการใช้มุมมองจากไฟล์ xib สิ่งนี้เหมาะสำหรับคุณ ฉันสร้างคลาส CustomCalloutView คลาสย่อยของ UIView ฉันได้สร้างไฟล์ xib แล้วใน IB เพียงแค่เลือกเจ้าของไฟล์จากนั้นเลือกตัวตรวจสอบคุณสมบัติตั้งชื่อคลาสเป็น CustomCalloutView จากนั้นสร้างร้านในคลาสของคุณ

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

// ตอนนี้กำลังเพิ่ม

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

-1

นี่คือตัวอย่างของการสร้างคลาสย่อยของฉัน (UIView) ฉันมีเนื้อหาเป็นตัวแปรเพื่อให้สามารถเข้าถึงและปรับแต่งได้ในภายหลังในคลาสอื่น ๆ ฉันยังได้แสดงวิธีใช้การจัดวางอัตโนมัติและการเพิ่มเนื้อหา

ตัวอย่างเช่นใน ViewController ฉันมีมุมมองนี้เริ่มต้นใน ViewDidLoad () เนื่องจากจะเรียกเพียงครั้งเดียวเมื่อมองเห็นมุมมอง จากนั้นฉันใช้ฟังก์ชั่นเหล่านี้ที่ฉันสร้างไว้ที่นี่ addContentToView()แล้วactivateConstraints()สร้างเนื้อหาและกำหนดข้อ จำกัด ถ้าฉันอยู่ใน ViewController ในภายหลังต้องการให้สีของปุ่มเป็นสีแดงฉันก็ทำในฟังก์ชันเฉพาะนั้นใน ViewController นั้น สิ่งที่ต้องการ: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)
}
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.