UIView lớp con Swift


95

Tôi muốn phân lớp UIViewvà hiển thị một lượt xem đăng nhập. Tôi đã tạo nó trong Objective-C, nhưng bây giờ tôi muốn chuyển nó sang Swift. Tôi không sử dụng bảng phân cảnh, vì vậy tôi tạo tất cả giao diện người dùng của mình bằng mã.

Nhưng vấn đề đầu tiên là tôi phải thực hiện initWithCoder. Tôi đã đặt nó một triển khai mặc định vì Nó sẽ không được gọi. Bây giờ khi tôi chạy chương trình, nó bị treo, vì tôi cũng phải triển khai initWithFrame. Bây giờ tôi có cái này:

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

Câu hỏi của tôi là tôi nên tạo trường văn bản của mình ở đâu, v.v ... và nếu tôi không bao giờ triển khai frame và coder thì làm sao tôi có thể "ẩn" điều này?

Câu trả lời:


174

Tôi thường làm điều gì đó như thế này, hơi dài dòng.

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

(Chỉnh sửa: Tôi đã chỉnh sửa câu trả lời của mình để mối quan hệ giữa các trình khởi tạo rõ ràng hơn)


4
Nhưng addBehavior được gọi hai lần vì initFrame được gọi và init. Nếu bạn chạy mã frame đầu tiên của tôi init được in thì init mặc định
Haagenti

6
Thứ tốt, cảm ơn. Thay vì sử dụng, CGRectZerotôi tin rằng nó được khuyến khích sử dụng CGRect.zeroRect.
Mr Rogers

57
Công cụ khởi tạo này rất phức tạp.
Ian Warburton

3
Có cách nào để làm điều này cho Bố cục Tự động không? Khung quá lỗi thời.
defos1

8
Đây không phải là một câu trả lời đầy đủ. UIView không hỗ trợ initWithCoding. Bất kỳ chế độ xem nào được tải từ nib hoặc bảng phân cảnh sẽ gọi phương thức initWithCoding và bị lỗi.
Iain Delaney

17

Điều này đơn giản hơn.

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

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

10

Ví dụ về lớp con UIView tùy chỉnh

Tôi thường tạo ứng dụng iOS mà không sử dụng bảng phân cảnh hoặc ngòi. Tôi sẽ chia sẻ một số kỹ thuật tôi đã học được để trả lời câu hỏi của bạn.

 

Ẩn các initphương pháp không mong muốn

Đề xuất đầu tiên của tôi là khai báo một cơ sở UIViewđể ẩn các bộ khởi tạo không mong muốn. Tôi đã thảo luận chi tiết về cách tiếp cận này trong câu trả lời của mình cho "Cách ẩn Bảng phân cảnh và Bộ khởi tạo cụ thể Nib trong Lớp con giao diện người dùng" . Lưu ý: Cách tiếp cận này giả định rằng bạn sẽ không sử dụng BaseViewhoặc con cháu của nó trong bảng phân cảnh hoặc ngòi vì nó cố tình khiến ứng dụng gặp sự cố.

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

Lớp con UIView tùy chỉnh của bạn sẽ kế thừa từ BaseView. Nó phải gọi super.init () trong bộ khởi tạo của nó. Nó không cần phải thực hiện init(coder:). Điều này được chứng minh trong ví dụ dưới đây.

 

Thêm một UITextField

Tôi tạo thuộc tính được lưu trữ cho các lượt xem phụ được tham chiếu bên ngoài initphương thức. Tôi thường làm như vậy cho một UITextField. Tôi thích subviews thuyết minh trong việc kê khai tài sản như thế này subview: let textField = UITextField().

UITextField sẽ không hiển thị trừ khi bạn thêm nó vào danh sách lượt xem phụ của chế độ xem tùy chỉnh bằng cách gọi addSubview(_:). Điều này được chứng minh trong ví dụ dưới đây.

 

Bố cục có lập trình không có bố cục tự động

UITextField sẽ không hiển thị trừ khi bạn đặt kích thước và vị trí của nó. Tôi thường làm bố cục trong mã (không sử dụng Bố cục Tự động) trong phương thức layoutSubviews .layoutSubviews()được gọi ban đầu và bất cứ khi nào một sự kiện thay đổi kích thước xảy ra. Điều này cho phép điều chỉnh bố cục tùy thuộc vào kích thước của CustomView. Ví dụ: nếu CustomView xuất hiện toàn bộ chiều rộng trên các kích thước khác nhau của iPhone và iPad và điều chỉnh để xoay, thì nó cần phải phù hợp với nhiều kích thước ban đầu và thay đổi kích thước động.

Bạn có thể tham khảo frame.heightframe.widthbên trong layoutSubviews()để lấy kích thước của CustomView để tham khảo. Điều này được chứng minh trong ví dụ dưới đây.

 

Lớp con UIView mẫu

Một lớp con UIView tùy chỉnh chứa UITextField không cần triển khai 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)
    }
}

 

Bố cục có lập trình với Bố cục tự động

Bạn cũng có thể triển khai bố cục bằng cách sử dụng Bố cục Tự động trong mã. Vì tôi không thường xuyên làm điều này, tôi sẽ không đưa ra một ví dụ. Bạn có thể tìm thấy các ví dụ về việc triển khai Bố cục Tự động trong mã trên Stack Overflow và các nơi khác trên Internet.

 

Khung bố cục có lập trình

Có các khung công tác mã nguồn mở thực hiện bố cục trong mã. Một cái tôi quan tâm nhưng chưa thử là LayoutKit . Nó được viết bởi nhóm phát triển LinkedIn. Từ kho lưu trữ Github: "LinkedIn đã tạo LayoutKit vì chúng tôi nhận thấy rằng Bố cục Tự động không đủ hiệu quả đối với các cấu trúc phân cấp chế độ xem phức tạp trong các chế độ xem có thể cuộn."

 

Tại sao lại đưa fatalErrorvàoinit(coder:)

Khi tạo các lớp con UIView sẽ không bao giờ được sử dụng trong bảng phân cảnh hoặc nib, bạn có thể giới thiệu các trình khởi tạo với các tham số và yêu cầu khởi tạo khác nhau mà init(coder:)phương thức này không thể gọi được . Nếu bạn không init (coder :) với a fatalError, nó có thể dẫn đến các vấn đề rất khó hiểu ở dòng nếu vô tình được sử dụng trong storyboard / nib. FatError khẳng định những ý định này.

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

Nếu bạn muốn chạy một số mã khi lớp con được tạo bất kể nó được tạo bằng mã hay bảng phân cảnh / nib thì bạn có thể làm như sau (dựa trên câu trả lời của 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
    }
}

và? bằng cách thêm fatalErrorbạn cấm init quan điểm này với các file xib
Vyachaslav Gerchicov

@VyachaslavGerchicov, Câu trả lời của tôi nêu giả định rằng bạn không sử dụng xibs hoặc bảng phân cảnh cũng như câu trả lời được chấp nhận và câu hỏi. Câu hỏi ghi chú "Tôi không sử dụng bảng phân cảnh, vì vậy tôi tạo tất cả giao diện người dùng của mình bằng mã".
Mobile Dan

lần tới khi bạn viết fatalErrorbên trong phương thức dealloc và cho chúng tôi biết rằng nó không hoạt động vì lớp đó phải là một singleton. Nếu bạn thích tạo các phần tử giao diện người dùng trong mã thì bạn không nên cấm thủ công tất cả các cách khác. Cuối cùng câu hỏi đặt ra là làm thế nào để tạo "bảng phân cảnh theo chương trình" nhưng xibs / nibs không được đề cập. Trong trường hợp của tôi, tôi cần tạo một mảng ô với + xib theo lập trình và chuyển chúng vào DropDownMenuKitvà cách này không hoạt động vì tác giả của thư viện này cũng cấm xibs.
Vyachaslav Gerchicov

@VyachaslavGerchicov Nó âm thanh như câu trả lời Jeff Gu Kang là những gì bạn đang tìm kiếm vì nó chứa Storyboards / Xibs
Điện thoại di động Dan

1
@VyachaslavGerchicov Câu hỏi cũng nêu rõ "và nếu tôi không bao giờ triển khai frame và coder thì làm sao tôi có thể" ẩn "điều này?" Khi tạo các lớp con UIView sẽ không bao giờ được sử dụng trong Xib / Storyboard, bạn có thể giới thiệu các trình khởi tạo với các tham số khác nhau mà phương thức init (coder :) không thể gọi được. Nếu bạn không init (coder :) với lỗi deathError, nó có thể dẫn đến các vấn đề rất khó hiểu nếu vô tình được sử dụng trong Xib / Storyboard. FatError cho biết những ý định này. Đây là một thực tế có chủ đích và phổ biến như đã thấy trong câu trả lời được chấp nhận.
Mobile Dan

4

Điều quan trọng là UIView của bạn có thể được tạo bằng trình tạo giao diện / bảng phân cảnh hoặc từ mã. Tôi thấy hữu ích khi có một setupphương pháp để giảm việc sao chép bất kỳ mã thiết lập nào. ví dụ

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, Nếu bạn muốn sử dụng chế độ xem từ tệp xib, thì cái này là dành cho bạn. Tôi đã tạo lớp CustomCalloutView Lớp phụ của UIView. Tôi đã tạo một tệp xib và trong IB chỉ cần chọn chủ sở hữu tệp sau đó chọn Trình kiểm tra thuộc tính đặt tên lớp thành CustomCalloutView, sau đó tạo cửa hàng trong lớp của bạn.

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

// Bây giờ thêm nó

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

-1

Đây là một ví dụ về cách tôi thường xây dựng các lớp con của mình (UIView). Tôi có nội dung là các biến để chúng có thể được truy cập và chỉnh sửa sau này trong một số lớp khác. Tôi cũng đã chỉ ra cách tôi sử dụng bố cục tự động và thêm nội dung.

Ví dụ trong ViewController, tôi có chế độ xem này được khởi tạo trong ViewDidLoad () vì chế độ đó chỉ được gọi một lần khi chế độ xem được hiển thị. Sau đó, tôi sử dụng các chức năng tôi tạo ở đây addContentToView()và sau đó activateConstraints()để xây dựng nội dung và thiết lập các ràng buộc. Nếu sau này tôi trong ViewController muốn màu của một nút là màu đỏ, tôi chỉ làm điều đó trong một hàm cụ thể trong ViewController đó. Cái gì đó như: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)
}
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.