Cách khởi tạo / khởi tạo lớp UIView tùy chỉnh bằng tệp XIB trong Swift


139

Tôi có một lớp được gọi MyClasslà lớp con của UIView, mà tôi muốn khởi tạo với một XIBtệp. Tôi không chắc làm thế nào để khởi tạo lớp này với tệp xib được gọiView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}

5
tham khảo mẫu đầy đủ mã nguồn cho iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift và liên quan chủ đề stackoverflow.com/questions/24857986/...
karthikPrabhu Alagu

Câu trả lời:


266

Tôi đã thử nghiệm mã này và nó hoạt động rất tốt:

class MyClass: UIView {        
    class func instanceFromNib() -> UIView {
        return UINib(nibName: "nib file name", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
    }    
}

Khởi tạo chế độ xem và sử dụng nó như dưới đây:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

HOẶC LÀ

var view = MyClass.instanceFromNib
self.view.addSubview(view())

CẬP NHẬT Swift> = 3.x & Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}

1
Nó phải là var view = MyClass.instanceFromNib()& self.view.addSubview(view)trái ngược với var view = MyClass.instanceFromNib& self.view.addSubview(view()). Chỉ là một gợi ý nhỏ để cải thiện câu trả lời :)
Logan

1
Trong trường hợp của tôi xem khởi tạo sau! nếu là self.view.addSubview (view), view nên là var view = MyClass.instanceFromNib ()
Ezimet

2
@Ezimet những gì về IBActions trong quan điểm đó. nơi để xử lý chúng. Giống như tôi có một nút trong chế độ xem (xib) của mình, làm thế nào để xử lý sự kiện nhấp IBAction của nút đó.?
Qadir Hussain

6
Nó có nên trả về "MyClass" thay vì chỉ UIView không?
Kesong Xie

2
IBoutlets không hoạt động theo cách tiếp cận này ... Tôi nhận được: "lớp này không phải là giá trị khóa tuân thủ mã hóa cho khóa"
Radek Wilczak

82

Giải pháp của Sam thật tuyệt vời, mặc dù nó không tính đến các gói khác nhau (NSBundle: forClass mang đến giải cứu) và yêu cầu tải thủ công, còn gọi là nhập mã.

Nếu bạn muốn hỗ trợ đầy đủ cho Xib Outlets của mình, các Gói khác nhau (sử dụng trong khung!) Và có được bản xem trước đẹp trong Storyboard, hãy thử điều này:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        nibSetup()
    }

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Sử dụng xib của bạn như bình thường, tức là kết nối Outlets với Chủ sở hữu tệp và đặt lớp Chủ sở hữu tệp thành lớp của riêng bạn.

Cách sử dụng: Chỉ cần phân lớp lớp View của riêng bạn từ NibLoadingView & Đặt tên lớp thành Chủ sở hữu tệp trong tệp Xib

Không cần thêm mã nữa.

Tín dụng khi đáo hạn tín dụng: Đã chấm dứt điều này với những thay đổi nhỏ từ DenHeadless trên GH. Gist của tôi: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8


8
Giải pháp này hãm các kết nối đầu ra (vì nó thêm chế độ xem được tải dưới dạng một khung nhìn phụ) và gọi nibSetuptừ init?(coder:)sẽ gây ra đệ quy vô hạn nếu nhúng NibLoadingViewvào XIB.
red84

3
@ redent84 Cảm ơn bình luận của bạn và downvote. Nếu bạn có giao diện thứ 2, nó sẽ thay thế SubView trước đó (không có biến thể hiện mới nào được đưa ra). Bạn đã đúng về đệ quy vô hạn, điều đó nên được bỏ qua nếu đấu tranh với IB.
Frederik Winkelsdorf

1
Như đã đề cập "kết nối Outlets với Chủ sở hữu tệp và đặt lớp Chủ sở hữu tệp thành lớp của riêng bạn." kết nối các cửa hàng với Chủ sở hữu tệp
Yusuf X

1
Tôi luôn không thoải mái về việc sử dụng phương pháp này để tải view từ xib. Về cơ bản, chúng tôi đang thêm một chế độ xem là lớp con của Lớp A, trong chế độ xem cũng là lớp con của Lớp A. Không có cách nào để ngăn chặn việc lặp này?
Prajeet Shrestha

1
@PrajeetShrestha Điều đó có thể là do nibSetup () ghi đè màu Nền thành .clearColor()- sau khi tải nó từ Storyboard. Nhưng nó sẽ hoạt động nếu bạn làm điều đó bằng mã sau khi nó được khởi tạo. Dù sao, như đã nói, một cách tiếp cận thậm chí thanh lịch hơn là giao thức dựa trên giao thức. Vì vậy, chắc chắn, đây là một liên kết dành cho bạn: github.com/AliSoftware/Reustom . Bây giờ tôi đang sử dụng một cách tiếp cận tương tự liên quan đến UITableViewCells (mà tôi đã triển khai trước khi tôi phát hiện ra dự án thực sự hữu ích đó). hth
Frederik Winkelsdorf

77

Kể từ Swift 2.0, bạn có thể thêm tiện ích mở rộng giao thức. Theo tôi, đây là một cách tiếp cận tốt hơn vì kiểu trả về Selfthay vì UIView, vì vậy người gọi không cần phải chuyển sang lớp xem.

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}

4
Đây là một giải pháp tốt hơn câu trả lời được chọn vì không cần truyền và nó cũng sẽ được sử dụng lại trên bất kỳ lớp con UIView nào khác mà bạn tạo trong tương lai.
dùng344977

3
Tôi đã thử điều này với Swift 2.1 và Xcode 7.2.1. Nó đã làm việc một thời gian và sẽ treo lên một cách khó lường với những người khác với một khóa mutex. Hai dòng cuối cùng được sử dụng trực tiếp trong mã làm việc mỗi lần với dòng cuối cùng được sửa đổi thànhvar myView = nib.instantiate... as! myViewType
Paul Linsay

@ jr-root-cs Bản chỉnh sửa của bạn chứa lỗi chính tả / lỗi, tôi phải lật lại. Và dù sao xin vui lòng không thêm mã vào câu trả lời hiện có. Thay vào đó, hãy bình luận; hoặc thêm phiên bản của bạn trong câu trả lời của riêng bạn . Cảm ơn.
Eric Aya

Tôi đã đăng mã mà tôi đã mở và thử nghiệm trong dự án của mình bằng cách sử dụng Swift 3(XCode 8.0 beta 6) mà không gặp vấn đề gì. Lỗi đánh máy là trong Swift 2. Tại sao nó phải là một câu trả lời khác khi câu trả lời này là tốt và người dùng có thể sẽ thích tìm kiếm những thay đổi khi họ sẽ sử dụng XC8
jr.root.cs

1
@ jr.root.cs Có câu trả lời này là tốt, đó là lý do tại sao không ai nên thay đổi nó. Đây là câu trả lời của Sam, không phải của bạn. Nếu bạn muốn bình luận về nó, hãy để lại nhận xét; nếu bạn muốn đăng một phiên bản mới / cập nhật, hãy làm nó trong bài viết của riêng bạn . Chỉnh sửa có nghĩa là sửa lỗi chính tả / thụt lề / thẻ không thêm phiên bản của bạn vào bài đăng của người khác. Cảm ơn.
Eric Aya

37

Và đây là câu trả lời của Frederik trên Swift 3.0

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        nibSetup()
    }

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}

31

Cách tải phổ biến từ xib:

Thí dụ:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

Thực hiện:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}

Câu trả lời hay nhất cho tôi là chế độ xem đầu ra dưới dạng Loại của lớp con UIView
jfgrang

26

Swift 3 Trả lời: Trong trường hợp của tôi, tôi muốn có một ổ cắm trong lớp tùy chỉnh mà tôi có thể sửa đổi:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

Khi ở .xib, hãy đảm bảo rằng trường Lớp tùy chỉnh là MyClassView. Đừng bận tâm với Chủ sở hữu tệp.

Đảm bảo Lớp tùy chỉnh là MyClassView

Ngoài ra, hãy đảm bảo rằng bạn kết nối ổ cắm trong MyClassView với nhãn: Ổ cắm cho myLabel

Để khởi tạo nó:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"

Tôi quay lại "đã tải ngòi" MyClas "nhưng ổ cắm chế độ xem không được đặt. '" Nếu chủ sở hữu không được đặt
jcharch

22

Swift 4

Ở đây trong trường hợp của tôi, tôi phải chuyển dữ liệu vào chế độ xem tùy chỉnh đó, vì vậy tôi tạo hàm tĩnh để khởi tạo chế độ xem.

  1. Tạo tiện ích mở rộng UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. Tạo MyCustomView

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
  3. Đặt lớp tùy chỉnh thành MyCustomView trong tệp .xib. Kết nối ổ cắm nếu cần thiết như bình thường. nhập mô tả hình ảnh ở đây

  4. Xem ngay lập tức

    let view = MyCustomView.instantiate(message: "Hello World.")

nếu có các nút trong chế độ xem tùy chỉnh, làm thế nào chúng ta có thể xử lý các hành động của chúng trong các bộ điều khiển chế độ xem khác nhau?
jayant rawat

bạn có thể sử dụng giao thức-đại biểu. hãy xem tại đây stackoverflow.com/questions/29602612/ .
mnemonic23

Không thể tải hình ảnh trong xib, gặp lỗi sau. Không thể tải hình ảnh " IBBrokenImage " được tham chiếu từ ngòi trong gói có mã định danh "test.Testing"
Ravi Raja Jangid

-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

class func instanceFromNib() -> LAAlertView {
    return UINib(nibName: "LAAlertView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! LAAlertView
}

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}

mã định dạng xấu, không có giải thích, do đó downvote.
J. Doe

Tất cả những điều này có liên quan gì đến câu hỏi?
Ashley Mills
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.