โหลด UIView จากปลายปากกาใน Swift


148

นี่คือรหัส Objective-C ของฉันที่ฉันใช้ในการโหลดไส้ปากกาสำหรับการปรับแต่งของฉันUIView:

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}

รหัสเทียบเท่าใน Swift คืออะไร

คำตอบ:


172

โซลูชันดั้งเดิม

  1. ฉันสร้าง XIB และคลาสชื่อ SomeView (ใช้ชื่อเดียวกันเพื่อความสะดวกและอ่านง่าย) ฉันใช้ทั้ง UIView
  2. ใน XIB ฉันเปลี่ยนคลาส "เจ้าของไฟล์" เป็น SomeView (ในตัวตรวจสอบตัวตน)
  3. ฉันสร้างเต้าเสียบ UIView ใน SomeView.swift ลิงก์ไปยังมุมมองระดับสูงสุดในไฟล์ XIB (ตั้งชื่อเป็น "ดู" เพื่อความสะดวก) จากนั้นฉันเพิ่มช่องอื่น ๆ ลงในส่วนควบคุมอื่น ๆ ในไฟล์ XIB ตามต้องการ
  4. ใน SomeView.swift ฉันโหลด XIB ภายในเครื่องมือเริ่มต้น "init with code" ไม่จำเป็นต้องกำหนดอะไรให้กับ "ตัวเอง" ทันทีที่มีการโหลด XIB ร้านค้าทั้งหมดจะถูกเชื่อมต่อรวมถึงมุมมองระดับสูงสุด สิ่งเดียวที่ขาดหายไปคือการเพิ่มมุมมองด้านบนให้กับลำดับชั้นของมุมมอง:

.

class SomeView: UIView {
   required init(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
      self.addSubview(self.view);    // adding the top level view to the view hierarchy
   }
   ...
}

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

อัพเดท - ใช้ไวยากรณ์ของ Swift 3

การโหลด xib ในส่วนขยายต่อไปนี้จะถูกเขียนเป็นวิธีการแบบอินสแตนซ์ซึ่งสามารถใช้งานได้โดย initializer เช่นเดียวกับด้านบน:

extension UIView {

    @discardableResult   // 1
    func fromNib<T : UIView>() -> T? {   // 2
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {    // 3
            // xib not loaded, or its top view is of the wrong type
            return nil
        }
        self.addSubview(contentView)     // 4
        contentView.translatesAutoresizingMaskIntoConstraints = false   // 5 
        contentView.layoutAttachAll(to: self)   // 6 
        return contentView   // 7
    }
}
  1. การใช้ค่าส่งคืนที่ถูกทิ้งเนื่องจากมุมมองที่ส่งคืนส่วนใหญ่จะไม่สนใจผู้โทรเมื่อช่องเสียบทั้งหมดเชื่อมต่ออยู่แล้ว
  2. นี่เป็นวิธีการทั่วไปที่ส่งกลับวัตถุทางเลือกประเภท UIView ถ้ามันล้มเหลวในการโหลดมุมมองก็จะส่งกลับศูนย์
  3. ความพยายามโหลดไฟล์ XIB ด้วยชื่อเดียวกันกับคลาสอินสแตนซ์ปัจจุบัน หากล้มเหลวจะไม่มีการส่งคืน
  4. การเพิ่มมุมมองระดับสูงสุดให้กับลำดับชั้นการดู
  5. บรรทัดนี้สมมติว่าเรากำลังใช้ข้อ จำกัด ในการจัดวางมุมมอง
  6. วิธีนี้จะเพิ่มข้อ จำกัด ด้านบน, ล่าง, นำ & ตามรอย - แนบมุมมองไปที่ "ตัวเอง" ในทุกด้าน (ดู: https://stackoverflow.com/a/46279424/2274829เพื่อดูรายละเอียด)
  7. ส่งคืนมุมมองระดับบนสุด

และวิธีการโทรอาจมีลักษณะเช่นนี้:

final class SomeView: UIView {   // 1.
   required init?(coder aDecoder: NSCoder) {   // 2 - storyboard initializer
      super.init(coder: aDecoder)
      fromNib()   // 5.
   }
   init() {   // 3 - programmatic initializer
      super.init(frame: CGRect.zero)  // 4.
      fromNib()  // 6.
   }
   // other methods ...
}
  1. SomeClass เป็นคลาสย่อย UIView ที่โหลดเนื้อหาจากไฟล์ SomeClass.xib คำหลัก "สุดท้าย" เป็นตัวเลือก
  2. เครื่องมือเริ่มต้นสำหรับเมื่อใช้มุมมองในกระดานเรื่องราว (อย่าลืมใช้ SomeClass เป็นคลาสที่กำหนดเองของมุมมองสตอรีบอร์ดของคุณ)
  3. เครื่องมือเริ่มต้นสำหรับเมื่อมุมมองถูกสร้างโดยทางโปรแกรม (เช่น: "let myView = SomeView ()")
  4. ใช้เฟรมแบบศูนย์ทั้งหมดเนื่องจากมุมมองนี้ถูกจัดวางโดยใช้โครงร่างอัตโนมัติ โปรดทราบว่าวิธีการ "init (frame: CGRect) {.. }" ไม่ได้ถูกสร้างขึ้นอย่างอิสระเนื่องจากการจัดวางอัตโนมัตินั้นใช้เฉพาะในโครงการของเรา
  5. & 6. การโหลดไฟล์ xib โดยใช้ส่วนขยาย

เครดิต: การใช้ส่วนขยายทั่วไปในโซลูชันนี้ได้รับแรงบันดาลใจจากคำตอบของ Robert ด้านล่าง

แก้ไข การเปลี่ยน "มุมมอง" เป็น "contentView" เพื่อหลีกเลี่ยงความสับสน เปลี่ยนตัวห้อยของอาร์เรย์เป็น ".first" ด้วยเช่นกัน


7
ตั้งชื่อคลาสให้File's Ownerตรงจุด ... ขอบคุณ!
Aviel Gross

13
UIView ไม่มีมุมมองด้านอสังหาริมทรัพย์ดังนั้นการเรียก self.view ทำให้เกิดข้อผิดพลาด
Nastya Gorban

4
@NastyaGorban self.view จริง ๆ แล้วอ้างถึงในกรณีนี้กับคุณสมบัติเต้าเสียบ (ชื่อ "มุมมอง") ที่ GK100 เชื่อมโยงจากมุมมองระดับสูงสุดใน. xib ไปยัง SomeView.swift การเพิ่มร้านนั้นจะทำให้คุณเกิดข้อผิดพลาดเนื่องจากไม่มีมุมมอง " "คุณสมบัติในคลาส NSView ตามที่คุณพูด
Ausiàs

3
ฉันได้รับความผิดพลาดเมื่อโหลดปลายปากกา (loadNibNamed) ใช้ Xcode 6.3 และ Swift
karthikPrabhu Alagu

11
การโทรfromNib()จากภายในinit(coder aDecoder: NSCoder)สร้างลูปแบบไม่สิ้นสุดขณะโหลด Nib ภายในfromNib()เมธอดทำการโทรไปที่:init(coder aDecoder: NSCoder)
Matthew Cawley

334

ผลงานของฉัน:

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

จากนั้นเรียกมันว่าสิ่งนี้:

let myCustomView: CustomView = UIView.fromNib()

..หรือแม้กระทั่ง:

let myCustomView: CustomView = .fromNib()

20
คำตอบที่ดีที่สุดโดยไกล
CodyMace

7
คำตอบที่ดีที่สุดที่นี่ สะอาดและเรียบง่าย
Draggon Marquavious

3
@YuchenZhong - ฉันชอบ [0] มากกว่า. อันดับแรกเพราะจะส่งคืนตัวเลือก หากคุณบังคับให้แกะมันจะไม่ปลอดภัยกว่า ... และสิ่งนี้ทำให้เกิดคำถาม: ทำไมไม่ส่งคืนตัวเลือกเป็นวิธีแก้ไขปัญหาด้านบน คำตอบ: คุณทำได้ ไม่มีอะไรผิดปกติกับที่ แต่ ... ถ้ามันจะคืนค่าศูนย์ชื่อของ xib / class ไม่ตรงกัน นี่เป็นข้อผิดพลาดของนักพัฒนาและควรจะถูกจับทันทีและไม่เคยทำให้เกิดการผลิต ที่นี่ฉันอยากจะให้แอพหยุดทำงานไปปล่อยให้มันอยู่ในสถานะแปลก ๆ แค่ 2 เซ็นต์ / การตั้งค่าของฉัน
Robert Gummesson

1
@allenlinli - วิธีการเป็นส่วนขยายคงที่ของ UIView ตามที่ควรจะเป็น CustomView มันทำงานได้เพราะคอมไพเลอร์ infers พิมพ์โดยใช้คำอธิบายประกอบประเภทที่ชัดเจน เนื่องจาก CustomView เป็นประเภทย่อยของ UIView และประเภทถูกอนุมานแล้วเราไม่จำเป็นต้องอนุมานอีกครั้งดังนั้น UIView จึงสามารถละเว้นได้ดังที่แสดงในตัวอย่างที่สองของฉัน จากที่กล่าวมาคุณสามารถโทรออกได้อย่างชัดเจน
Robert Gummesson

3
วิธีนี้ไม่ได้ผลสำหรับฉันเมื่อมีมุมมองที่กำหนดเองภายใน. xib ฉันขอแนะนำให้แก้ไขส่วนนี้เป็น: return Bundle.main.loadNibNamed (String (อธิบาย: ตนเอง) เจ้าของ: ไม่มีตัวเลือก: ไม่มี)! [0] เป็น! T
Nadzeya

79

ตอนนี้ความสามารถในการกลับมา-> Selfอย่างรวดเร็วช่วยให้สิ่งนี้ง่ายขึ้นเล็กน้อย ยืนยันล่าสุดเมื่อ Swift 5

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

หาก.xibไฟล์และคลาสย่อยของคุณใช้ชื่อเดียวกันคุณสามารถใช้:

let view = CustomView.fromNib()

หากคุณมีชื่อที่กำหนดเองให้ใช้:

let view = CustomView.fromNib(named: "special-case")

บันทึก:

หากคุณได้รับข้อผิดพลาด "มุมมองประเภท YourType ไม่พบใน .. " คุณจะไม่ได้ตั้งคลาสของมุมมองใน.xibไฟล์

เลือกมุมมองของคุณใน.xibไฟล์แล้วกดcmd + opt + 4และในclassอินพุตป้อนคลาสของคุณ


1
ฉันไม่สามารถใช้งานได้ภายใต้ XCode 7.1 beta 3 - ไม่แน่ใจว่ามันเป็นรุ่นเบต้าหรือไม่ แต่โดยทั่วไปฉันได้ลองทุกวิธีในการสร้างมุมมองที่กำหนดเองโดยตรงจากปลายปากกาใน Swift และฉันได้รับผลลัพธ์เดียวกันเสมอ ไม่สอดคล้องกับ KVC กับร้านค้า ไม่แน่ใจว่าเป็นสิ่งที่ฉันทำผิด แต่ชั้นเรียนของฉันนั้นค่อนข้างเรียบง่ายและเจ้าของไฟล์นั้นถูกต้อง ฉันเคยทำสิ่งนี้ตลอดเวลาภายใต้วัตถุประสงค์ - C
Echelon

1
@Logan ไม่เกี่ยวข้องกับรหัสของคุณ แต่มุมมองที่กำหนดเอง imo ควรรองรับการโหลดจาก Storyboard / XIB ความคิดเห็นของฉันเป็นเพียงการแจ้งเตือนสำหรับผู้ที่ต้องการสร้างมุมมองดังกล่าว
Nikita Took

1
หมายเหตุ: let myCustomView = UIView.fromNib() as? CustomViewฉันยังคงมีปัญหาในการใช้รูปแบบที่สองของการเรียกฟังก์ชั่นนี้คือ ในกรณีนี้T.selfแก้ไขUIViewเป็นมากกว่าCustomViewและไม่สามารถหาปากกาได้ ฉันไม่แน่ใจว่าทำไมจึงเป็นเช่นนี้ - บางทีประเภทที่อนุมานสำหรับletวิธีการที่เรียกว่าฟังก์ชันเป็นUIView?
Echelon

2
สิ่งสำคัญคือต้องชี้ให้เห็นว่าการพยายามใช้เจ้าของไฟล์เพื่อเชื่อมต่อกับร้านค้า (อย่างที่เราทำในสมัยก่อน) จะทำให้สิ่งนี้ขัดข้อง ใน IB เจ้าของไฟล์จะต้องไม่มีข้อมูล / เปล่าและควรจะเชื่อมต่อกับมุมมองแทน
Echelon

1
@Echelon คุณบันทึกวันของฉัน !!! ฉันเชื่อมต่อร้านค้าของฉันโดยใช้เจ้าของไฟล์และมันใช้งานไม่ได้ใช้มุมมองแทนใช้งานได้
Jan Schlorf

19

ลองรหัสต่อไปนี้

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView

แก้ไข:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

    init()  {
        super.init()
       self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
    }


}

ฉันต้องเรียกวิธีนี้ภายในวิธีการเริ่มต้นวัตถุซึ่งเป็น init () ใน Swift
Bagusflyer

12

ส่วนขยายของ Swift 4 Protocol

public protocol NibInstantiatable {

    static func nibName() -> String

}

extension NibInstantiatable {

    static func nibName() -> String {
        return String(describing: self)
    }

}

extension NibInstantiatable where Self: UIView {

    static func fromNib() -> Self {

        let bundle = Bundle(for: self)
        let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)

        return nib!.first as! Self

    }

}

การรับเป็นบุตรบุญธรรม

class MyView: UIView, NibInstantiatable {

}

การนำไปใช้นี้จะถือว่า Nib มีชื่อเดียวกันกับคลาส UIView อดีต MyView.xib คุณสามารถปรับเปลี่ยนพฤติกรรมนี้ได้โดยการใช้ nibName () ใน MyView เพื่อส่งคืนชื่อที่แตกต่างจากการใช้ส่วนขยายโปรโตคอลเริ่มต้น

ใน xib เจ้าของไฟล์คือ MyView และคลาสรูทวิวคือ MyView

การใช้

let view = MyView.fromNib()

3
นี่เป็นทางออกที่ตรงไปตรงมาที่สุดและฉันก็ไม่รู้ว่าทำไมมันถึงไม่ใช่คำตอบที่ได้รับการยอมรับ!
horseshoe7

@ horseshoe7 เพราะมันเขียน 4 ปีหลังจากคำถาม
Freeubi

11

ฉันได้รับสิ่งนี้กับ Swift ด้วยรหัสต่อไปนี้:

class Dialog: UIView {
    @IBOutlet var view:UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.frame = UIScreen.mainScreen().bounds
        NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
        self.view.frame = UIScreen.mainScreen().bounds
        self.addSubview(self.view)
    }

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

อย่าลืมเชื่อมต่อมุมมอง XIB ของคุณเพื่อดูช่องทางที่กำหนดไว้อย่างรวดเร็ว คุณยังสามารถตั้งค่า First Responder เป็นชื่อคลาสแบบกำหนดเองของคุณเพื่อเริ่มเชื่อมต่อกับร้านค้าเพิ่มเติม

หวังว่านี่จะช่วยได้!


10

ทดสอบใน Xcode 7 เบต้า 4, Swift 2.0 และ iOS9 SDK รหัสต่อไปนี้จะกำหนด xib ให้กับ uiview คุณสามารถใช้มุมมอง xib ที่กำหนดเองนี้ในกระดานเรื่องราวและเข้าถึงวัตถุ IBOutlet ได้เช่นกัน

import UIKit

@IBDesignable class SimpleCustomView:UIView
{
    var view:UIView!;

    @IBOutlet weak var lblTitle: UILabel!

   @IBInspectable var lblTitleText : String?
        {
        get{
            return lblTitle.text;
        }
        set(lblTitleText)
        {
            lblTitle.text = lblTitleText!;
        }
    }

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib ()
    }
    func loadViewFromNib() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        self.addSubview(view);



    }


}

เข้าถึง customview โดยทางโปรแกรม

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

ซอร์สโค้ด - https://github.com/karthikprabhuA/CustomXIBSwift


9

หากคุณมีมุมมองที่กำหนดเองจำนวนมากในโครงการของคุณคุณสามารถสร้างคลาสได้ UIViewFromNib

สวิฟท์ 2.3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(self.dynamicType)
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    private func loadViewFromNib() {
        contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
        contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

สวิฟท์ 3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(describing: type(of: self))
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    func loadViewFromNib() {
        contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

และในทุก ๆ คลาสที่สืบทอดมาUIViewFromNibคุณสามารถแทนที่nibNameคุณสมบัติได้ถ้า.xibไฟล์มีชื่อแตกต่างกัน:

class MyCustomClass: UIViewFromNib {

}

8

การสร้างโซลูชั่นด้านบน

สิ่งนี้จะใช้ได้กับบันเดิลโปรเจ็กต์ทั้งหมดและไม่จำเป็นต้องใช้ชื่อสามัญเมื่อโทรจากนิบ ()

สวิฟท์ 2

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nil)
    }

    public class func fromNib(nibName: String?) -> Self {

        func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
            let bundle = NSBundle(forClass: T.self)
            let name = nibName ?? String(T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName)
    }
}

สวิฟท์ 3

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nibName: nil)
    }

    public class func fromNib(nibName: String?) -> Self {
        func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
            let bundle = Bundle(for: T.self)
            let name = nibName ?? String(describing: T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName: nibName)
    }
}

สามารถใช้สิ่งนี้:

let someView = SomeView.fromNib()

หรือเช่นนี้

let someView = SomeView.fromNib("SomeOtherNibFileName")

6

สวิฟต์ 4

อย่าลืมเขียน ".first เป็นอย่างไร CustomView"

if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {    
    self.view.addSubview(customView)
    }

หากคุณต้องการใช้งานได้ทุกที่

ทางออกที่ดีที่สุดคือคำตอบของRobert Gummesson

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

จากนั้นเรียกมันว่าสิ่งนี้:

let myCustomView: CustomView = UIView.fromNib()

5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]

แต่ใน init () ของ Swift จะไม่มีค่าที่ส่งคืน ฉันลืมที่จะพูดถึงว่าฉันต้องเรียก loadNibNamed ในการเริ่มต้นของ UIView
Bagusflyer

คุณหมายถึง "ไม่มีค่าส่งคืน" หมายความว่าอย่างไร selfถูกส่งกลับโดยปริยายจากinitวิธีการทั้งหมด...
Grimxn

1
สิ่งที่ฉันหมายถึงคือฉันเรียก loadNibNamed ภายในวิธีการเริ่มต้น UIView ที่โหลดถูกกำหนดให้เป็นตัวเองใน ObjC แต่อย่างรวดเร็วมันไม่ได้
Bagusflyer

5

วิธีที่ดีในการทำเช่นนี้กับ Swift คือใช้ enum

enum Views: String {
    case view1 = "View1" // Change View1 to be the name of your nib
    case view2 = "View2" // Change View2 to be the name of another nib

    func getView() -> UIView? {
        return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
    }
}

จากนั้นในรหัสของคุณคุณสามารถใช้:

let view = Views.view1.getView()

2
โปรดทราบว่าถ้าคุณทำเช่นนี้ด้วยไฟล์ปลายปากกาที่ว่างเปล่าหรือไฟล์ปลายปากกาที่ไม่มีโหนดรากของ UIView คุณจะทำงานล้มเหลวเนื่องจากคุณไม่ได้ตรวจสอบขนาดอาร์เรย์หรือองค์ประกอบในตำแหน่ง 0
Matthew Cawley เมื่อ

4

ฉันชอบวิธีนี้ (ขึ้นอยู่กับคำตอบถ้า @ GK100):

  1. ฉันสร้าง XIB และคลาสชื่อ SomeView (ใช้ชื่อเดียวกันเพื่อความสะดวกและอ่านง่าย) ฉันใช้ทั้ง UIView
  2. ใน XIB ฉันเปลี่ยนคลาส "เจ้าของไฟล์" เป็น SomeView (ในตัวตรวจสอบตัวตน)
  3. ฉันสร้างเต้าเสียบ UIView ใน SomeView.swift ลิงก์ไปยังมุมมองระดับสูงสุดในไฟล์ XIB (ตั้งชื่อเป็น "ดู" เพื่อความสะดวก) จากนั้นฉันเพิ่มช่องอื่น ๆ ลงในส่วนควบคุมอื่น ๆ ในไฟล์ XIB ตามต้องการ
  4. ใน SomeView.swift ฉันโหลด XIB ภายในinitหรือinit:frame: CGRectinitializer ไม่จำเป็นต้องกำหนดอะไรให้กับ "ตัวเอง" ทันทีที่มีการโหลด XIB ร้านค้าทั้งหมดจะถูกเชื่อมต่อรวมถึงมุมมองระดับสูงสุด สิ่งเดียวที่ขาดหายไปคือการเพิ่มมุมมองด้านบนให้กับลำดับชั้นของมุมมอง:

    class SomeView: UIView {
      override init(frame: CGRect) {
        super.init(frame: frame)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
      required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
    
      ...
    }

ฉันชอบที่จะใช้ init กับเฟรมดังนั้นฉันจึงลบล้างสิ่งนี้! สิ่งหนึ่งที่ควรทราบ ... เพิ่ม self.view.frame = frame หากคุณต้องการให้มุมมองตรงกับเฟรมที่คุณผ่าน
Mike E

3

โลแกนตอบSwift 3เวอร์ชั่น

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
            for nibView in nibViews {
                if let tog = nibView as? T {
                    view = tog
                }
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nib: UINib? {
        if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
            return UINib(nibName: nibName, bundle: nil)
        } else {
            return nil
        }
    }
}

3

นี่เป็นวิธีที่สะอาดและมีการประกาศในการโหลดมุมมองโดยใช้โปรแกรมโดยใช้โปรโตคอลและส่วนขยายโปรโตคอล (Swift 4.2):

protocol XibLoadable {
    associatedtype CustomViewType
    static func loadFromXib() -> CustomViewType
}

extension XibLoadable where Self: UIView {
    static func loadFromXib() -> Self {
        let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
        guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
            // your app should crash if the xib doesn't exist
            preconditionFailure("Couldn't load xib for view: \(self)")
        }
        return customView
    }
}

และคุณสามารถใช้สิ่งนี้เช่น:

// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }

// and when you want to use it
let viewInstance = MyView.loadFromXib()

ข้อควรพิจารณาเพิ่มเติมบางประการ :

  1. ตรวจสอบให้แน่ใจว่าไฟล์ xib ของมุมมองที่กำหนดเองของคุณมีชุดมุมมองCustom Class(และช่องทาง / การกระทำที่ตั้งจากที่นั่น) ไม่ใช่ของเจ้าของไฟล์
  2. คุณสามารถใช้โปรโตคอล / ส่วนขยายนี้ภายนอกมุมมองที่กำหนดเองหรือภายในของคุณ คุณอาจต้องการใช้ภายในหากคุณมีงานตั้งค่าอื่น ๆ เมื่อเริ่มต้นมุมมองของคุณ
  3. คลาสมุมมองแบบกำหนดเองและไฟล์ xib ของคุณต้องมีชื่อเดียวกัน

2

ฉันทำแบบนี้:

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

ตัวอย่างนี้ใช้มุมมองแรกในปลายปากกา "MyView.xib" ในบันเดิลหลัก แต่คุณสามารถเปลี่ยนแปลงดัชนีชื่อปลายปากกาหรือบันเดิล (หลักตามค่าเริ่มต้น)

ฉันเคยตื่นขึ้นมาดูในวิธีการดู init หรือทำวิธีการทั่วไปในการแก้ปัญหาข้างต้น (ซึ่งเป็นวิธีที่ฉลาด) แต่ฉันไม่ทำมันอีกต่อไป

วิธีนี้ฉันสามารถใช้เลย์เอาต์หรือลักษณะที่แตกต่างกันในขณะที่รักษาลอจิกและโค้ดที่เหมือนกัน

ฉันพบว่าการปล่อยให้วัตถุจากโรงงานง่ายกว่า (โดยปกติคือ viewController ที่จะใช้มุมมอง) สร้างตามที่ต้องการ บางครั้งคุณต้องการเจ้าของ (โดยปกติเมื่อมุมมองที่สร้างขึ้นมีเต้าเสียบเชื่อมต่อกับผู้สร้าง) บางครั้งก็ไม่ได้ ..

นั่นอาจเป็นเหตุผลที่ Apple ไม่ได้รวมinitFromNibวิธีการไว้ในคลาส UIView ...

ในการยกตัวอย่างระดับพื้นดินคุณไม่รู้ว่าคุณเกิดอย่างไร คุณเพิ่งเกิด ดังนั้นมุมมอง;)


2

สิ่งที่คุณต้องทำคือวิธีการโทรใน UIViewชั้นเรียน

ทำอย่างนั้น:

class className: UIView {

    @IBOutlet var view: UIView!

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

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

    func setup() {
        UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
        addSubview(view)
        view.frame = self.bounds
    }
}

ตอนนี้ถ้าคุณต้องการเพิ่มมุมมองนี้เป็นมุมมองย่อยในตัวควบคุมมุมมองให้ทำเช่นนั้นในไฟล์ view controller.swift:

self.view.addSubview(className())

มันเป็นคำตอบที่ดี แต่มีบางอย่างผิดปกติฉันจะแก้ไขมัน
C0mrade

มันเป็นวิธีที่ฉันใช้ แต่คุณสามารถโพล่งมันได้ ขอบคุณล่วงหน้า @ C0mrade
Alap Anerao

1

คล้ายกับคำตอบบางส่วนด้านบน แต่ส่วนขยาย Swift3 UIView ที่สอดคล้องกันมากขึ้น:

extension UIView {
    class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
        let bundle = bundle ?? Bundle.main
        let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
        return nibViews?.first as? A
    }

    class func fromNib<T: UIView>() -> T? {
        return fromNib(nibName: String(describing: T.self), bundle: nil)
    }
}

ซึ่งให้ความสะดวกในการโหลดคลาสจาก self nib ชื่อ แต่ยังมาจากไส้ปากกา / บันเดิลอื่น ๆ


1

คุณสามารถทำได้ผ่านกระดานเรื่องราวเพียงแค่เพิ่มข้อ จำกัด ที่เหมาะสมสำหรับการดู คุณสามารถทำสิ่งนี้ได้อย่างง่ายดายโดย subclassing มุมมองใด ๆ จากของคุณเองสมมติว่าBaseView:

Objective-C

BaseView.h


/*!
 @class BaseView
 @discussion Base View for getting view from xibFile
 @availability ios7 and later
 */
@interface BaseView : UIView

@end


BaseView.m


#import "BaseView.h"

@implementation BaseView

#pragma mark - Public

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - LifeCycle

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - Private

- (void)prepareView
{
    NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    UIView *view = [nibsArray firstObject];

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];
    [self addConstraintsForView:view];
}

#pragma mark - Add constraints

- (void)addConstraintsForView:(UIView *)view
{
    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeBottom
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeBottom
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeTop
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeTop
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeLeft
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeLeft
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeRight
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeRight
                                                       multiplier:1.0
                                                         constant:0]
                           ]];
}

@end

สวิฟต์ 4

import UIKit

class BaseView : UIView {

    // MARK: - LifeCycle

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

        prepareView()
    }

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

        prepareView()
    }

    internal class func xibName() -> String {
        return String(describing: self)
    }

    // MARK: - Private
    fileprivate func prepareView() {
        let nameForXib = BaseView.xibName()
        let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
        if let view = nibs?.first as? UIView {
            view.backgroundColor = UIColor.clear
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubviewWithConstraints(view, offset: false)
        }
    }
}

UIView+Subview


public extension UIView {
    // MARK: - UIView+Extensions

    public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
        subview.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "subview" : subview
        ]
        addSubview(subview)

        var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
        constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activate(constraints)
    }
}

ฉันให้วิธีการเพิ่มข้อ จำกัด 2 แบบ - แบบทั่วไปและแบบภาษาที่มีภาพ - เลือกแบบที่คุณต้องการ :)

นอกจากนี้โดยค่าเริ่มต้นถือว่าxibชื่อนั้นมีชื่อเหมือนกับชื่อคลาสการใช้งาน ถ้าไม่ - แค่เปลี่ยนxibNameพารามิเตอร์

หากคุณซับคลาสมุมมองของคุณBaseView- คุณสามารถใส่มุมมองและระบุคลาสใน IB ได้อย่างง่ายดาย


1
class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}

0

รุ่นที่มีประสิทธิภาพยิ่งขึ้นตามคำตอบของ Logan

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
            if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
                view = tog
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nibIndex: Int {
        return 0
    }

    public class var nibBundle: Bundle {
        return Bundle.main
    }
}

และคุณสามารถใช้เช่น

class BaseView: UIView {
    override class var nibName: String { return "BaseView" }
    weak var delegate: StandardStateViewDelegate?
}

class ChildView: BaseView {
    override class var nibIndex: Int { return 1 }
}

0

การใช้งานที่สะดวกที่สุด ที่นี่คุณต้องการสองวิธีในการส่งคืนโดยตรงไปยังวัตถุในชั้นเรียนของคุณไม่ใช่ UIView

  1. viewId ทำเครื่องหมายเป็นคลาสช่วยให้แทนที่
  2. . xib ของคุณสามารถมีมากกว่าหนึ่งมุมมองของระดับบนสุดสถานการณ์นี้ได้รับการจัดการอย่างถูกต้องเช่นกัน

extension UIView {

class var viewId: String {
    return String(describing: self)
}

static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
                    owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {

    return instancePrivate(from: bundle ?? Bundle.main,
                           nibName: nibName ?? viewId,
                           owner: owner,
                           options: options)
}

private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
                                              owner: Any?, options: [AnyHashable : Any]?) -> T? {

    guard
        let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
        let view = views.first(where: { $0 is T }) as? T else { return nil }

    return view
}
}

ตัวอย่าง:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true

0

หากคุณต้องการให้คลาสย่อย Swift UIView มีอยู่ในตัวเองทั้งหมดและมีความสามารถในการสร้างอินสแตนซ์โดยใช้ init หรือ init (เฟรม :) โดยไม่เปิดเผยรายละเอียดการใช้งานของการใช้ Nib คุณสามารถใช้ส่วนขยายโปรโตคอลเพื่อให้บรรลุสิ่งนี้ โซลูชันนี้จะหลีกเลี่ยงลำดับชั้นของ UIView ที่ซ้อนกันตามที่แนะนำโดยโซลูชันอื่น ๆ

public class CustomView: UIView {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!

    public convenience init() {
        self.init(frame: CGRect.zero)
    }

    public override convenience init(frame: CGRect) {
        self.init(internal: nil)
        self.frame = frame
    }

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

    fileprivate func commonInit() {
    }
}

fileprivate protocol _CustomView {
}

extension CustomView: _CustomView {
}

fileprivate extension _CustomView {

    // Protocol extension initializer - has the ability to assign to self, unlike
    // class initializers. Note that the name of this initializer can be anything
    // you like, here we've called it init(internal:)

    init(internal: Int?) {
        self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
    }
}

0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)

คำตอบรหัสเท่านั้นที่จะหมดกำลังใจ โปรดเพิ่มคำอธิบายเกี่ยวกับวิธีการแก้ไขปัญหาหรือวิธีการนี้แตกต่างจากคำตอบที่มีอยู่ จากรีวิว
นิค

0

ฉันชอบส่วนขยายด้านล่าง

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

ความแตกต่างระหว่างส่วนขยายนี้กับส่วนขยายที่มีคำตอบยอดนิยมคือคุณไม่จำเป็นต้องเก็บค่าคงที่หรือตัวแปร

class TitleView: UIView { }

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

self.navigationItem.titleView = TitleView.instanceFromNib

คุณใช้ Xcode เวอร์ชันใดอยู่ โปรดตรวจสอบให้แน่ใจว่าคุณใช้ XCode เวอร์ชันล่าสุด ทำงานได้ดีสำหรับฉันด้วย XCode 11.5 (รุ่นล่าสุด ณ วันที่)
iCoder

0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.