วิธีเริ่มต้น / สร้างอินสแตนซ์ของคลาส UIView แบบกำหนดเองด้วยไฟล์ XIB ใน Swift


139

ฉันมีคลาสที่เรียกว่าMyClassซึ่งเป็นคลาสย่อยของUIViewที่ฉันต้องการเริ่มต้นด้วยXIBไฟล์ ฉันไม่แน่ใจว่าจะเริ่มต้นคลาสนี้ด้วยไฟล์ xib ที่เรียกว่าอย่างไรView.xib

class MyClass: UIView {

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

5
ดูตัวอย่างซอร์สโค้ดแบบเต็มสำหรับ iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift และเธรดที่เกี่ยวข้องstackoverflow.com/questions/24857986//
karthikPrabhu Alagu

คำตอบ:


266

ฉันทดสอบโค้ดนี้และใช้งานได้ดี:

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

เริ่มต้นมุมมองและใช้เหมือนด้านล่าง:

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

หรือ

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

UPDATE 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
มันควรจะเป็นvar view = MyClass.instanceFromNib()และself.view.addSubview(view)เมื่อเทียบกับ&var view = MyClass.instanceFromNib self.view.addSubview(view())เพียงแค่ข้อเสนอแนะในการปรับปรุงเล็ก ๆ คำตอบ :)
โลแกน

1
ในมุมมองกรณีของฉันเริ่มต้นในภายหลัง! ถ้าเป็น self.view.addSubview (มุมมอง) มุมมองควรเป็น var view = MyClass.instanceFromNib ()
Ezimet

2
@Ezimet เกี่ยวกับ IBActions ในมุมมองนั้น จัดการได้ที่ไหน เช่นเดียวกับฉันมีปุ่มในมุมมองของฉัน (xib) วิธีจัดการกับเหตุการณ์คลิก IBAction ของปุ่มนั้น
Qadir Hussain

6
มันควรจะส่งคืน "MyClass" แทนที่จะเป็นเพียง UIView หรือไม่
Kesong Xie

2
IBoutlet ไม่ทำงานในวิธีการนี้ ... ฉันได้รับ: "คลาสนี้ไม่ใช่ค่าคีย์ที่เข้ารหัสตามคีย์"
Radek Wilczak

82

วิธีแก้ปัญหาของ Sam นั้นยอดเยี่ยมแม้ว่าจะไม่คำนึงถึงการรวมกลุ่มที่แตกต่างกัน (NSBundle: forClass มาเพื่อช่วยเหลือ) และต้องใช้การโหลดด้วยตนเองหรือพิมพ์รหัส

หากคุณต้องการการสนับสนุนอย่างเต็มที่สำหรับ Xib Outlets ของคุณการรวมกลุ่มที่แตกต่างกัน (ใช้ในเฟรมเวิร์ก!) และดูตัวอย่างที่ดีใน Storyboard ลองสิ่งนี้:

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

}

ใช้ xib ของคุณตามปกติเช่นเชื่อมต่อ Outlets กับ File Owner และตั้งค่า Class เจ้าของไฟล์เป็น class ของคุณเอง

การใช้งาน: เพียงคลาสย่อยคลาสมุมมองของคุณเองจาก NibLoadingView และตั้งชื่อคลาสเป็นเจ้าของไฟล์ในไฟล์ Xib

ไม่ต้องใช้รหัสเพิ่มเติมอีกต่อไป

เครดิตที่เครดิตครบกำหนด: แยกสิ่งนี้ด้วยการเปลี่ยนแปลงเล็กน้อยจาก DenHeadless บน GH ส่วนสำคัญของฉัน: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8


8
โซลูชันนี้เบรกการเชื่อมต่อทางออก (เพราะจะเพิ่มมุมมองที่โหลดเป็นมุมมองย่อย) และการโทรnibSetupจากinit?(coder:)จะทำให้เกิดการเรียกซ้ำไม่สิ้นสุดหากฝังNibLoadingViewใน XIB
redent84

3
@ redent84 ขอบคุณสำหรับความคิดเห็นและ downvote ของคุณ หากคุณมีรูปลักษณ์ที่ 2 ควรเปลี่ยน SubView ก่อนหน้า (ไม่ได้กำหนดตัวแปรอินสแตนซ์ใหม่) คุณพูดถูกเกี่ยวกับการเรียกซ้ำแบบไม่มีที่สิ้นสุดซึ่งควรถูกมองข้ามถ้าการต่อสู้กับ IB
Frederik Winkelsdorf

1
ดังที่กล่าวไว้ "เชื่อมต่อ Outlets กับ File Owner และตั้งค่า Class เจ้าของไฟล์ให้เป็นคลาสของคุณเอง" เชื่อมต่อร้านค้ากับเจ้าของไฟล์
Yusuf X

1
ฉันรู้สึกไม่สบายใจเสมอเกี่ยวกับการใช้วิธีนี้ในการโหลดมุมมองจาก xib โดยพื้นฐานแล้วเรากำลังเพิ่มมุมมองที่เป็นคลาสย่อยของคลาส A ในมุมมองที่เป็นคลาสย่อยของ Class A ไม่มีวิธีป้องกันการทำซ้ำนี้หรือไม่
Prajeet Shrestha

1
@PrajeetShrestha อาจเกิดจาก nibSetup () แทนที่สีพื้นหลังเป็น.clearColor()- หลังจากที่โหลดจาก Storyboard แต่มันจะทำงานถ้าคุณทำมันด้วยรหัสหลังจากอินสแตนซ์ อย่างไรก็ตามอย่างที่กล่าวไปแล้วว่าวิธีการที่สวยงามยิ่งกว่านั้นก็คือโปรโตคอลที่ใช้ ดังนั้นแน่นอนว่านี่คือการเชื่อมโยงสำหรับคุณ: github.com/AliSoftware/Reusable ฉันกำลังใช้วิธีการที่คล้ายกันในขณะนี้เกี่ยวกับ UITableViewCells (ซึ่งฉันดำเนินการก่อนที่ฉันจะค้นพบโครงการที่มีประโยชน์จริง ๆ ) HTH!
Frederik Winkelsdorf

77

ตั้งแต่ Swift 2.0 คุณสามารถเพิ่มส่วนขยายโปรโตคอลได้ ในความคิดของฉันนี่เป็นวิธีที่ดีกว่าเพราะประเภทการส่งคืนเป็นSelfมากกว่าUIViewดังนั้นผู้โทรไม่จำเป็นต้องส่งไปยังคลาสการดู

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
นี่เป็นคำตอบที่ดีกว่าคำตอบที่เลือกเนื่องจากไม่จำเป็นต้องทำการคัดเลือกและจะสามารถนำกลับมาใช้ซ้ำได้ในคลาสย่อย UIView อื่น ๆ ที่คุณสร้างขึ้นในอนาคต
user3344977

3
ฉันลองสิ่งนี้กับ Swift 2.1 และ Xcode 7.2.1 มันใช้งานได้ในบางเวลาและจะวางสายกับผู้อื่นด้วยการล็อค mutex สองบรรทัดสุดท้ายที่ใช้โดยตรงในโค้ดทำงานทุกครั้งที่มีการแก้ไขบรรทัดสุดท้ายให้เป็นvar myView = nib.instantiate... as! myViewType
Paul Linsay

@ jr-root-cs การแก้ไขของคุณมีข้อผิดพลาด / ข้อผิดพลาดฉันต้องย้อนกลับ และโปรดอย่าเพิ่มรหัสลงในคำตอบที่มีอยู่ ให้แสดงความคิดเห็นแทน หรือเพิ่มรุ่นของคุณในคำตอบของคุณเอง ขอบคุณ
Eric Aya

ฉันโพสต์รหัสที่ฉันเปิดและทดสอบในโครงการของฉันโดยใช้Swift 3(XCode 8.0 beta 6) โดยไม่มีปัญหา Swift 2พิมพ์ผิดอยู่ใน ทำไมมันจึงเป็นคำตอบอีกข้อหนึ่งเมื่อคำตอบนี้ดีและผู้ใช้มีแนวโน้มที่จะค้นหาสิ่งที่เปลี่ยนแปลงเมื่อพวกเขาจะใช้ XC8
jr.root.cs

1
@ jr.root.cs ใช่คำตอบนี้ดีนั่นคือสาเหตุที่ไม่มีใครควรแก้ไข นี่คือคำตอบของแซมไม่ใช่ของคุณ หากคุณต้องการแสดงความคิดเห็นให้แสดงความคิดเห็น; ถ้าคุณต้องการโพสต์รุ่นใหม่ / ปรับปรุงทำมันในโพสต์ของคุณเอง การแก้ไขนั้นมีจุดประสงค์เพื่อแก้ไขการพิมพ์ / การเยื้อง / แท็กเพื่อไม่ให้เพิ่มเวอร์ชันของคุณในโพสต์ของคนอื่น ขอบคุณ
Eric Aya

37

และนี่คือคำตอบของ Frederik on 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

วิธีโหลดมุมมองสากลจาก xib:

ตัวอย่าง:

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

การดำเนินงาน:

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

คำตอบที่ดีที่สุดสำหรับฉันในฐานะมุมมองการส่งออกเป็นประเภทย่อยของ UIView
jfgrang

26

Swift 3 คำตอบ:ในกรณีของฉันฉันต้องการมีร้านค้าในชั้นเรียนที่กำหนดเองที่ฉันสามารถแก้ไขได้:

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

เมื่ออยู่ใน. xib ตรวจสอบให้แน่ใจว่าฟิลด์ Custom Class เป็น MyClassView อย่ารบกวนเจ้าของไฟล์

ตรวจสอบให้แน่ใจว่า Custom Class เป็น MyClassView

นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณเชื่อมต่อร้านใน MyClassView กับฉลาก: Outlet สำหรับ myLabel

เพื่อยกตัวอย่าง:

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

ฉันได้รับกลับ "โหลด" MyClas "ปลายปากกา แต่ไม่ได้ตั้งค่ามุมมองเต้าเสียบ" "หากเจ้าของไม่ได้ตั้งค่า
jcharch

22

สวิฟต์ 4

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

  1. สร้างส่วนขยาย UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. สร้าง 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. ตั้งค่าคลาสที่กำหนดเองเป็น MyCustomView ในไฟล์. xib เชื่อมต่อเต้าเสียบถ้าจำเป็นตามปกติ ป้อนคำอธิบายรูปภาพที่นี่

  4. ดูตัวอย่าง

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

หากมีปุ่มในมุมมองที่กำหนดเองเราจะจัดการการกระทำของพวกเขาในตัวควบคุมมุมมองที่แตกต่างกันอย่างไร
jayant rawat

คุณสามารถใช้ผู้รับมอบสิทธิ์โปรโตคอล ลองดูที่นี่stackoverflow.com/questions/29602612/… .
mnemonic23

ไม่สามารถโหลดรูปภาพเป็น xib ได้รับข้อผิดพลาดต่อไปนี้ ไม่สามารถโหลดภาพ " IBBrokenImage " ที่อ้างอิงจากปลายปากกาในชุดข้อมูลที่มีตัวระบุ "ทดสอบการทดสอบ"
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)

        })
    })


}

โค้ดที่จัดรูปแบบไม่ถูกต้องไม่มีการอธิบายดังนั้น downvote
J. Doe

ทั้งหมดนี้เกี่ยวข้องกับคำถามอย่างไร
Ashley Mills
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.