ฉันจะเขียน init ที่กำหนดเองสำหรับคลาสย่อย UIView ใน Swift ได้อย่างไร


125

บอกว่าผมต้องการที่จะsubclass ด้วยและinitUIViewStringInt

ฉันจะทำเช่นนี้ในสวิฟท์ถ้าฉันแค่ subclassing UIView? ถ้าฉันแค่สร้างinit()ฟังก์ชันที่กำหนดเองแต่พารามิเตอร์เป็น String และ Int มันจะบอกฉันว่า "super.init () ไม่ได้ถูกเรียกก่อนกลับจาก initializer"

และถ้าฉันโทรไปsuper.init()ฉันบอกว่าฉันต้องใช้ตัวเริ่มต้นที่กำหนด ฉันควรใช้อะไรที่นั่น? รุ่นเฟรม? รุ่น coder? ทั้งสอง? ทำไม?

คำตอบ:


207

init(frame:)รุ่นคือการเริ่มต้นเริ่มต้น คุณต้องเรียกใช้หลังจากเริ่มต้นตัวแปรอินสแตนซ์ของคุณแล้วเท่านั้น หากมุมมองนี้ถูกสร้างขึ้นใหม่จาก Nib ตัวเริ่มต้นที่กำหนดเองของคุณจะไม่ถูกเรียกและinit?(coder:)จะเรียกเวอร์ชันแทน เนื่องจากตอนนี้ Swift ต้องใช้สิ่งที่จำเป็นinit?(coder:)ฉันจึงอัปเดตตัวอย่างด้านล่างและเปลี่ยนการletประกาศตัวแปรเป็นvarและไม่บังคับ ในกรณีนี้คุณจะเริ่มต้นในawakeFromNib()หรือในภายหลัง

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

5
จากนั้นทำให้พวกเขาvarทั้งหมด แต่เริ่มต้นการปฏิบัติที่ดีที่สุดในสวิฟท์คือการประกาศตัวแปรเว้นแต่จะมีเหตุผลที่จะประกาศให้พวกเขาlet ไม่มีเหตุผลดังกล่าวจะทำเช่นนั้นในตัวอย่างรหัสของฉันข้างต้นเป็นเหตุvar let
Wolf McNally

2
รหัสนี้ไม่ได้รวบรวม คุณจำเป็นต้องใช้ init(coder:)initialiser
Decade Moon

3
น่าสนใจว่าเมื่อปีที่แล้วเรียบเรียงอย่างไร ทุกวันนี้มันบ่นกันใน init (coder :) ว่า "Property self.s not initialized at super.init call"
mafiOSo

ตัวอย่างคงที่สำหรับ Swift 3.1 รวบรวมภายใต้สนามเด็กเล่นนำเข้า UIKit
Wolf McNally

1
@LightNight ฉันสร้างsและเป็นiทางเลือกเพื่อให้สิ่งต่างๆเรียบง่ายที่นี่ หากไม่ใช่ทางเลือกก็จำเป็นต้องกำหนดค่าเริ่มต้นในโปรแกรมเริ่มต้นที่จำเป็น การทำให้เป็นทางเลือกหมายความว่าพวกเขาจะได้รับnilเมื่อsuper.init()ถูกเรียก หากไม่ใช่ทางเลือกพวกเขาจะต้องได้รับมอบหมายก่อนที่จะโทรหา super.init ()
Wolf McNally

33

ฉันสร้าง init ทั่วไปสำหรับสิ่งที่กำหนดและจำเป็น เพื่อความสะดวกในการเข้าร่วมฉันมอบหมายให้init(frame:)ด้วยกรอบของศูนย์

การมีเฟรมเป็นศูนย์ไม่ใช่ปัญหาเพราะโดยทั่วไปมุมมองจะอยู่ในมุมมองของ ViewController มุมมองที่กำหนดเองของคุณจะได้รับสิ่งที่ดีโอกาสที่ปลอดภัยในการจัดวาง subviews เมื่อโทร SuperView ของมันหรือlayoutSubviews() updateConstraints()ฟังก์ชันทั้งสองนี้ถูกเรียกโดยระบบแบบวนซ้ำตลอดลำดับชั้นของมุมมอง คุณสามารถใช้อย่างใดอย่างหนึ่งupdateContstraints()หรือlayoutSubviews(). จะเรียกว่าเป็นครั้งแรกแล้วupdateContstraints() layoutSubviews()ในupdateConstraints()ให้แน่ใจว่าจะเรียกซุปเปอร์สุดท้าย ในlayoutSubviews()โทรซุปเปอร์แรก

นี่คือสิ่งที่ฉันทำ:

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

}

1
ไม่ควรทำงาน: ใช้ 'self' ในวิธีการเรียก 'commonInit' ก่อนที่ super.init จะเริ่มต้น self
surfrider

1
เริ่มต้นอาร์กิวเมนต์ที่กำหนดเองหลังจากการเรียก self.init อัปเดตคำตอบของฉัน
MH175

1
แต่ถ้าคุณต้องการเริ่มต้นคุณสมบัติบางอย่างในcommonInitวิธีการ แต่คุณไม่สามารถวางได้superในกรณีนี้เนื่องจากคุณควรเริ่มต้นคุณสมบัติทั้งหมดก่อนที่จะsuperเรียกใช้ ฮ่า ๆ ดูเหมือนวนรอบตาย
surfrider

1
นี่คือวิธีการเริ่มต้นของ Swift มักจะทำงาน: ค้นหา "two-phase initialization" คุณสามารถใช้ตัวเลือกที่ไม่ได้ปิดโดยปริยาย แต่ฉันไม่แนะนำให้ใช้ สถาปัตยกรรมของคุณโดยเฉพาะอย่างยิ่งเมื่อจัดการกับมุมมองควรเริ่มต้นคุณสมบัติในเครื่องทั้งหมด ฉันใช้วิธี commonInit () นี้กับหลายร้อยวิวแล้ว ได้ผล
MH175

17

นี่คือวิธีที่ฉันทำบน iOS 9 ใน 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"); }
}

นี่คือโครงการฉบับเต็มพร้อมตัวอย่าง:


2
สิ่งนี้จะใช้การแสดงผลทั้งหมดสำหรับ View
RaptoX

1
อ๋อ! หากสนใจในมุมมองย่อยบางส่วนแจ้งให้เราทราบและฉันจะโพสต์สิ่งนี้เช่นกัน
J-Dizzle

1
ฉันชอบคำตอบนี้ที่สุดเพราะการมี fatalError หมายความว่าฉันไม่ต้องใส่รหัสใด ๆ ในการเริ่มต้นที่ต้องการ
Carter Medlin

1
@ J-Dizzle ฉันต้องการเห็นวิธีแก้ปัญหาสำหรับการดูบางส่วน
Ari Lacenski

คำตอบของคุณไม่ตรงข้ามกับคำตอบที่ยอมรับใช่หรือไม่? ฉันหมายความว่าคุณกำลังทำปรับแต่งหลังsuper.initแต่เขาบอกว่ามันควรจะทำก่อนsuper.init...
น้ำผึ้ง

11

นี่คือวิธีการทำ Subview บน iOS ใน 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"); }
}

4
ใช้การโทร fatalError () ได้ดี ฉันต้องใช้ตัวเลือกเพื่อปิดเสียงเตือนจากตัวเริ่มต้นที่ไม่ได้ใช้งาน ปิดมันซะ! ขอบคุณ
Mike Critchley
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.