โหลดมุมมองจากไฟล์ xib ภายนอกในกระดานเรื่องราว


131

ฉันต้องการใช้มุมมองจากหลาย ๆ ผู้ควบคุมการดูในกระดานเรื่องราว ดังนั้นฉันจึงคิดเกี่ยวกับการออกแบบมุมมองใน xib ภายนอกดังนั้นการเปลี่ยนแปลงจึงสะท้อนให้เห็นใน viewcontroller แต่วิธีหนึ่งสามารถโหลดมุมมองจากภายนอก xib ในกระดานเรื่องราวและมันเป็นไปได้ยัง? หากไม่ใช่กรณีนี้มีทางเลือกอื่นใดบ้างที่จะเหมาะสมกับสถานการณ์ต่อไปนี้



1
ดูวิดีโอนี้: youtube.com/watch?v=o3MawJVxTgk
malhobayyeb

คำตอบ:


132

ตัวอย่างแบบเต็มของฉันอยู่ที่นี่แต่ฉันจะให้ข้อมูลสรุปด้านล่าง

แบบ

เพิ่มไฟล์. swift และ. xib แต่ละไฟล์ด้วยชื่อเดียวกันกับโครงการของคุณ ไฟล์. xib มีโครงร่างมุมมองที่กำหนดเองของคุณ (โดยใช้ข้อ จำกัด การจัดวางอัตโนมัติโดยเฉพาะอย่างยิ่ง)

ทำให้ไฟล์ swift เป็นเจ้าของไฟล์ xib

ป้อนคำอธิบายรูปภาพที่นี่ รหัส

เพิ่มรหัสต่อไปนี้ไปยังไฟล์. swift และเชื่อมต่อร้านค้าและการดำเนินการจากไฟล์. xib

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

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

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

ใช้มัน

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

ป้อนคำอธิบายรูปภาพที่นี่


3
ไม่ใช่ว่าการเรียก loadNibNamed init (coder :) หรือไม่ ฉันมีปัญหาในการพยายามปรับวิธีการของคุณ
ฟิชแมน

@Fishman ถ้าคุณพยายามที่จะโหลดดูโปรแกรม (มากกว่าจากกระดาน) init(frame:)มันจะผิดพลาดเพราะมันไม่ได้มีอยู่ในปัจจุบัน ดูบทช่วยสอนนี้สำหรับรายละเอียดเพิ่มเติม
Suragch

7
อีกสาเหตุของการล้มเหลวไม่ได้ตั้งค่ามุมมองที่กำหนดเองไปยังเจ้าของไฟล์ เห็นวงกลมสีแดงในคำตอบของฉัน
Suragch

5
ใช่ฉันได้ตั้งคลาสของมุมมองรูทแทนที่จะเป็นเจ้าของไฟล์และมันทำให้เกิดการวนซ้ำไม่สิ้นสุด
devios1

2
หลังจากเพิ่ม view.autoresizingMask = [.flexibleWidth, .flexibleHeight] บรรทัดก่อน self.addSubview (มุมมอง) มันทำงานได้อย่างสมบูรณ์
Pramod Tapaniya

69

ในขณะที่วิธีการของ Christopher Swasey เป็นวิธีที่ดีที่สุดที่ฉันค้นพบ ฉันถามผู้อาวุโสสองคนในทีมของฉันเกี่ยวกับมันและหนึ่งในนั้นมีทางออกที่สมบูรณ์แบบ ! มันตอบสนองทุกข้อกังวลที่ Christopher Swasey กล่าวถึงอย่างละเอียดและไม่ต้องใช้รหัส subclass สำเร็จรูป (ความกังวลหลักของฉันกับแนวทางของเขา) มีหนึ่ง gotchaแต่นอกเหนือจากนั้นมันค่อนข้างง่ายและใช้งานง่าย

  1. สร้างคลาส UIView ที่กำหนดเองในไฟล์. swift เพื่อควบคุม xib ของคุณ กล่าวคือMyCustomClass.swift
  2. สร้างไฟล์. xib และจัดรูปแบบตามที่คุณต้องการ กล่าวคือMyCustomClass.xib
  3. ตั้งค่าFile's Ownerไฟล์. xib ให้เป็นคลาสที่คุณกำหนดเอง ( MyCustomClass)
  4. GOTCHA:ปล่อยให้classค่า (ใต้identity Inspector) สำหรับมุมมองที่กำหนดเองของคุณในไฟล์. xib ว่างเปล่า ดังนั้นมุมมองที่กำหนดเองของคุณจะไม่มีคลาสที่ระบุ แต่จะมีเจ้าของไฟล์ที่ระบุ
  5. Assistant Editorเบ็ดขึ้นร้านของคุณเป็นคุณตามปกติจะใช้
    • หมายเหตุ: หากคุณมองไปที่Connections Inspectorคุณจะพบว่า Outlets อ้างอิงของคุณไม่ได้อ้างอิงคลาสที่กำหนดเองของคุณ (เช่นMyCustomClass) File's Ownerแต่การอ้างอิง เนื่องจากFile's Ownerมีการระบุว่าเป็นคลาสที่กำหนดเองของคุณร้านค้าจะเชื่อมต่อและทำงานอย่างถูกต้อง
  6. ตรวจสอบให้แน่ใจว่าคลาสแบบกำหนดเองของคุณมี @IBDesignable ก่อนคำสั่งคลาส
  7. ทำให้คลาสที่คุณกำหนดเองเป็นไปตามNibLoadableโปรโตคอลที่อ้างอิงด้านล่าง
    • หมายเหตุ: ถ้า.swiftชื่อคลาสแบบกำหนดเองของคุณแตกต่างจาก.xibชื่อไฟล์ของคุณให้ตั้งค่าnibNameคุณสมบัติให้เป็นชื่อ.xibไฟล์ของคุณ
  8. นำไปใช้required init?(coder aDecoder: NSCoder)และoverride init(frame: CGRect)เพื่อโทรsetupFromNib()ตามตัวอย่างด้านล่าง
  9. เพิ่ม UIView ลงในกระดานเรื่องราวที่คุณต้องการและตั้งชั้นให้เป็นชื่อคลาสที่คุณกำหนดเอง (เช่นMyCustomClass)
  10. รับชม IBDesignable ในขณะที่วาด. xib ของคุณไว้ในกระดานเรื่องราวด้วยความกลัวและสงสัย

นี่คือโปรโตคอลที่คุณจะต้องการอ้างอิง:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

และนี่คือตัวอย่างของMyCustomClassการใช้โปรโตคอล (ด้วยไฟล์. xib ที่มีชื่อMyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

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

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

}

หมายเหตุ: หากคุณพลาด Gotcha และตั้งclassค่าในไฟล์. xib ของคุณให้เป็นคลาสแบบกำหนดเองของคุณแล้วมันจะไม่วาดลงในกระดานเรื่องราวและคุณจะได้รับEXC_BAD_ACCESSข้อผิดพลาดเมื่อคุณเรียกใช้แอปเพราะมันติดอยู่ในวงวนไม่สิ้นสุดของ พยายามเริ่มต้นชั้นเรียนจากปลายปากกาโดยใช้init?(coder aDecoder: NSCoder)วิธีการที่เรียกแล้วโทรSelf.nib.instantiateออกinitอีกครั้ง


2
นี่เป็นอีกวิธีที่ยอดเยี่ยมสำหรับมัน แต่ฉันรู้สึกว่าข้างบนยังดีกว่า: medium.com/zenchef-tech-and-product/…
Ben Patch

4
วิธีการที่คุณกล่าวถึงข้างต้นทำงานได้อย่างสมบูรณ์แบบและใช้การแสดงตัวอย่างแบบสดๆในกระดานเรื่องราว มันมีประโยชน์และยอดเยี่ยมมาก ๆ !
Igor Leonovich

1
FYI: โซลูชันนี้โดยใช้คำจำกัดความในsetupFromNib()ดูเหมือนว่าจะแก้ไขปัญหาการจัดวางอัตโนมัติบางอย่างที่แปลกประหลาดกับเซลล์มุมมองตารางปรับขนาดอัตโนมัติที่มีมุมมอง XIB ที่สร้างขึ้น
Gary

1
ทางออกที่ดีที่สุด! ฉันรักการ@IBDesignableทำงานร่วมกัน ไม่อยากเชื่อเลยว่าเพราะเหตุใด Xcode หรือ UIKit จึงไม่ได้ให้สิ่งนี้เป็นค่าเริ่มต้นเมื่อเพิ่มไฟล์ UIView
fl034

1
ดูเหมือนจะไม่สามารถทำให้สิ่งนี้ทำงานได้ .. ทุกครั้งที่ฉันตั้งค่าเจ้าของไฟล์ไว้ในไฟล์. xib ของฉันมันจะตั้งค่าคลาสที่กำหนดเองด้วย
drewster

32

สมมติว่าคุณได้สร้าง xib ที่คุณต้องการใช้:

1) สร้างคลาสย่อยที่กำหนดเองของ UIView (คุณสามารถไปที่ไฟล์ -> ใหม่ -> ไฟล์ ... -> Cocoa Touch Class ตรวจสอบให้แน่ใจว่า "คลาสย่อยของ:" คือ "UIView")

2) เพิ่มมุมมองที่ยึดตาม xib เป็นมุมมองย่อยของมุมมองนี้เมื่อเริ่มต้น

ใน Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

ใน Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

ใน Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) ทุกที่ที่คุณต้องการใช้ในกระดานเรื่องราวของคุณเพิ่ม UIView ตามปกติเลือกมุมมองที่เพิ่มใหม่ไปที่ Identity Inspector (ไอคอนที่สามด้านขวาบนที่ดูเหมือนรูปสี่เหลี่ยมผืนผ้าที่มีเส้นอยู่) และป้อนชื่อคลาสย่อยของคุณในฐานะ "Class" ภายใต้ "Custom Class"


xibView.frame = self.frame;ควรเป็นxibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);อย่างอื่น xibView จะมีออฟเซ็ตเมื่อเพิ่มมุมมองลงในกระดานเรื่องราว
BabyPanda

ไปงานปาร์ตี้ช้า แต่ดูเหมือนว่าเขาเปลี่ยนเป็น xibView.frame = self.bounds ซึ่งเป็นเฟรมที่ไม่มีการชดเชย
Heavy_Bullets

20
ส่งผลให้เกิดข้อผิดพลาดเนื่องจากการเรียกซ้ำไม่สิ้นสุด การโหลดปลายปากกาสร้างอินสแตนซ์อื่นของคลาสย่อย
David

2
คลาสของมุมมอง xib ไม่ควรเหมือนกับคลาสย่อยใหม่นี้ หาก xib เป็น MyClass คุณสามารถสร้าง MyClassContainer คลาสใหม่นี้ได้
user1021430

การแก้ปัญหาข้างต้นจะผิดพลาดกับการเรียกซ้ำไม่สิ้นสุด
JBarros35

27

ฉันมักจะพบวิธีแก้ปัญหา "เพิ่มเป็นมุมมองย่อย" ที่น่าพอใจเพราะมันสกรูที่มี (1) การชำระอัตโนมัติ (2) @IBInspectableและ (3) แทนที่จะให้ฉันแนะนำคุณกับความมหัศจรรย์ของawakeAfter:การNSObjectวิธีการ

awakeAfterให้คุณสลับวัตถุที่ตื่นขึ้นมาจริง ๆ จาก NIB / Storyboard ด้วยวัตถุอื่นโดยสิ้นเชิง จากนั้นวัตถุนั้นจะถูกนำไปผ่านกระบวนการไฮเดรชั่นซึ่งถูกawakeFromNibเรียกบนมันจะถูกเพิ่มเป็นมุมมอง ฯลฯ

เราสามารถใช้สิ่งนี้ในคลาสย่อย "กระดาษแข็งที่ถูกตัดออก" ในมุมมองของเราจุดประสงค์เพียงอย่างเดียวคือการโหลดมุมมองจาก NIB และส่งคืนเพื่อใช้ใน Storyboard คลาสย่อยที่ฝังได้นั้นจะถูกระบุในตัวตรวจสอบข้อมูลประจำตัวของมุมมองสตอรีบอร์ดแทนที่จะเป็นคลาสดั้งเดิม ไม่จำเป็นต้องเป็นคลาสย่อยเพื่อให้สามารถใช้งานได้ แต่การทำให้คลาสย่อยเป็นสิ่งที่ทำให้ IB สามารถเห็นคุณสมบัติ IBInspectable / IBOutlet ใด ๆ

หม้อไอน้ำพิเศษนี้อาจดูเหมือนไม่ดี - และในแง่ที่มันเป็นเพราะUIStoryboardจะจัดการกับสิ่งนี้ได้อย่างราบรื่น - แต่มันมีข้อได้เปรียบในการปล่อยให้ NIB ดั้งเดิมและUIViewคลาสย่อยไม่มีการแก้ไขอย่างสมบูรณ์ บทบาทที่เล่นนั้นโดยพื้นฐานแล้วคือคลาสอะแดปเตอร์หรือบริดจ์และใช้ได้อย่างสมบูรณ์แบบออกแบบอย่างชาญฉลาดในฐานะคลาสเพิ่มเติมแม้ว่ามันจะน่าเศร้าก็ตาม ในทางกลับกันหากคุณต้องการที่จะให้ความสำคัญกับชั้นเรียนของคุณโซลูชันของ @ BenPatch นั้นทำงานโดยใช้โปรโตคอลที่มีการเปลี่ยนแปลงเล็กน้อยอื่น ๆ คำถามที่ว่าวิธีแก้ปัญหาแบบไหนดีกว่ากันในเรื่องของโปรแกรมเมอร์สไตล์: ไม่ว่าใครจะชอบองค์ประกอบของวัตถุหรือมรดกหลายอย่าง

หมายเหตุ: ชุดคลาสบนมุมมองในไฟล์ NIB ยังคงเหมือนเดิม ประเภทรองฝังเป็นเพียงใช้ในสตอรี่บอร์ด ไม่สามารถใช้คลาสย่อยเพื่อสร้างอินสแตนซ์ของมุมมองในโค้ดดังนั้นจึงไม่ควรมีตรรกะเพิ่มเติมใด ๆ มันควรเพียง แต่มีawakeAfterตะขอ

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

draw ข้อเสียเปรียบที่สำคัญประการหนึ่งคือถ้าคุณกำหนดข้อ จำกัด ความกว้างความสูงหรืออัตราส่วนในกระดานเรื่องราวที่ไม่เกี่ยวข้องกับมุมมองอื่นต้องทำการคัดลอกด้วยตนเอง ข้อ จำกัด ที่เกี่ยวข้องกับสองมุมมองถูกติดตั้งบนบรรพบุรุษร่วมที่ใกล้ที่สุดและมุมมองจะถูกปลุกจากกระดานเรื่องราวจากด้านใน - ด้านนอกดังนั้นเมื่อถึงเวลาที่มีข้อ จำกัด เหล่านี้จะถูกทำให้ชุ่มอยู่บนหน้าผู้ดูแลการแลกเปลี่ยนเกิดขึ้นแล้ว ข้อ จำกัด ที่เกี่ยวข้องกับมุมมองที่เป็นปัญหาจะถูกติดตั้งโดยตรงบนมุมมองนั้นและทำให้ถูกโยนเมื่อมีการสลับเกิดขึ้นยกเว้นว่ามีการคัดลอก

โปรดทราบว่าสิ่งที่เกิดขึ้นที่นี่คือข้อ จำกัด ที่ติดตั้งบนมุมมองในกระดานเรื่องราวจะถูกคัดลอกไปยังมุมมองที่สร้างอินสแตนซ์ใหม่ซึ่งอาจมีข้อ จำกัด ของตัวเองอยู่แล้วซึ่งกำหนดไว้ในไฟล์ปลายปากกา สิ่งเหล่านั้นไม่ได้รับผลกระทบ

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNibUIViewเป็นส่วนขยายของชนิดที่ปลอดภัยในการ สิ่งที่มันทำคือวนลูปผ่านวัตถุของ NIB จนกว่าจะพบวัตถุที่ตรงกับประเภท โปรดทราบว่าประเภททั่วไปเป็นค่าตอบแทนดังนั้นจะต้องมีการระบุประเภทที่ไซต์การโทร

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}

นั่นคือความเชื่อที่เหลือเชื่อ ถ้าฉันไม่เข้าใจผิดมันก็ใช้ได้แค่ "บนสตอรี่บอร์ด" - ถ้าคุณพยายามสร้างคลาสในโค้ดตอนรันไทม์ฉันไม่คิดว่ามันจะทำงานได้ ฉันเชื่อ.
Fattie

คลาสย่อยควรทำงานในรหัสเช่นเดียวกับคลาสต้นฉบับสำหรับทุกเจตนาและวัตถุประสงค์ หากคุณต้องการโหลดมุมมองจากปลายปากกาในโค้ดคุณจะต้องยกตัวอย่างมุมมองโดยตรงโดยใช้เทคนิคเดียวกัน คลาสย่อยทั้งหมดใช้โค้ดเพื่อสร้างอินสแตนซ์มุมมองจากปลายปากกาและวางลงในเบ็ดเพื่อให้กระดานเรื่องราวใช้
Christopher Swasey

ที่จริงฉันผิด - มันก็ใช้ได้เหมือนกันถ้าคุณสามารถยกตัวอย่างได้ แต่คุณทำไม่ได้เพราะมุมมองใน NIB จะมีซูเปอร์คลาสเป็นประเภทของมันดังนั้นมันinstantiateViewFromNibจะไม่คืนสิ่งใดเลย ไม่ใช่เรื่องใหญ่อย่างใดอย่างหนึ่ง IMO คลาสย่อยเป็นเพียงการวางแผนที่จะขอเข้าสู่กระดานเรื่องราวโค้ดทั้งหมดควรอยู่ในชั้นเรียนดั้งเดิม
Christopher Swasey

1
ที่ดี! มีสิ่งหนึ่งที่ทำให้ฉันสะดุดเพราะฉันมีประสบการณ์น้อยในการใช้ xibs (ฉันเคยทำงานกับสตอรีบอร์ดและวิธีการเขียนโปรแกรม) การออกจากที่นี่ในกรณีที่ช่วยใครซักคน: ในไฟล์. xib คุณต้องเลือกมุมมองระดับสูงสุดและตั้งค่า MyCustomViewประเภทชั้นเลิศ ใน xib ของฉันแถบด้านซ้ายด้านซ้ายหายไปโดยปริยาย หากต้องการเปิดใช้งานจะมีปุ่มอยู่ถัดจาก "ดูเป็น: iPhone 7" คุณสมบัติควบคุมใกล้ด้านล่าง / ซ้าย
xaphod

5
มันเบรกข้อ จำกัด เมื่อมันถูกแทนที่ด้วยวัตถุอื่น :(
invoodoo

6

ทางออกที่ดีที่สุดในปัจจุบันคือใช้ตัวควบคุมมุมมองแบบกำหนดเองที่มีมุมมองที่กำหนดไว้ใน xib และเพียงแค่ลบคุณสมบัติ "มุมมอง" ที่ Xcode สร้างขึ้นภายในกระดานเรื่องราวเมื่อเพิ่มตัวควบคุมมุมมองลงไป (อย่าลืมตั้งชื่อ แม้ว่าคลาสที่กำหนดเอง)

นี่จะทำให้รันไทม์ค้นหา xib และโหลดโดยอัตโนมัติ คุณสามารถใช้เคล็ดลับนี้สำหรับมุมมองคอนเทนเนอร์ใด ๆ หรือมุมมองเนื้อหา


5

ฉันคิดเกี่ยวกับalternativeการใช้XIB viewsจะใช้ในกระดานแยกต่างหากView Controller

จากนั้นในกระดานเรื่องราวหลักแทนที่มุมมองที่กำหนดเองใช้container viewด้วยEmbed Segueและต้องStoryboardReferenceใช้ตัวควบคุมมุมมองแบบกำหนดเองนี้ซึ่งมุมมองควรจะอยู่ในมุมมองอื่นในกระดานเรื่องราวหลัก

แล้วเราสามารถตั้งค่าการมอบหมายและการสื่อสารระหว่างนี้ฝัง ViewController และควบคุมมุมมองหลักผ่านการเตรียมความพร้อมสำหรับทำต่อ วิธีการนี้แตกต่างจากนั้นแสดง UIView แต่สามารถใช้งานได้ง่ายขึ้นและมีประสิทธิภาพมากขึ้น (จากมุมมองการเขียนโปรแกรม) เพื่อให้บรรลุเป้าหมายเดียวกันคือมีมุมมองที่กำหนดเองที่นำมาใช้ซ้ำได้ซึ่งปรากฏในกระดานเรื่องราวหลัก

ข้อดีเพิ่มเติมคือคุณสามารถใช้ตรรกะในคลาส CustomViewController และตั้งค่าการมอบหมายและการเตรียมการดูทั้งหมดโดยไม่ต้องสร้างคลาสคอนโทรลเลอร์ (หายากในโครงการ) แยกจากกันและไม่ต้องวางโค้ดสำเร็จรูปใน UIViewController หลักโดยใช้ Component ฉันคิดว่านี่เป็นสิ่งที่ดีสำหรับส่วนประกอบที่ใช้ซ้ำได้เช่น องค์ประกอบเครื่องเล่นเพลง (วิดเจ็ตที่ชอบ) ที่ฝังอยู่ในมุมมองอื่น ๆ


วิธีการแก้ปัญหาจริงเศร้ามากขึ้นง่ายกว่าการใช้มุมมอง Xib กำหนดเอง :(
วิลสัน

1
หลังจากเข้าไปในแวดวงที่มีห่วงโซ่การสร้างวงจรชีวิตของ xib ของ Apple บน UIView ที่กำหนดเองนี่คือวิธีที่ฉันจะไป
LightningStryk

ในกรณีที่คุณต้องการเพิ่มมุมมองที่กำหนดเองแบบไดนามิกเรายังคงต้องใช้ตัวควบคุมมุมมองแยกต่างหาก (ส่วนใหญ่ XIB ที่เหมาะสมกว่า)
Satyam

5

แม้ว่าคำตอบที่ได้รับความนิยมสูงสุดก็ใช้งานได้ดี แต่พวกเขาคิดผิด พวกเขาทั้งหมดใช้File's ownerเป็นการเชื่อมต่อระหว่างร้านของคลาสและส่วนประกอบ UI File's ownerควรจะใช้สำหรับวัตถุระดับบนเท่านั้นไม่ใช่UIViews ตรวจสอบเอกสารของนักพัฒนาแอปเปิ้ล การมี UIView File's ownerนำไปสู่ผลลัพธ์ที่ไม่พึงประสงค์เหล่านี้

  1. คุณถูกบังคับให้ใช้สถานที่ที่คุณควรจะใช้contentView selfมันไม่เพียง แต่น่าเกลียด แต่ยังมีโครงสร้างที่ผิดเพราะมุมมองระดับกลางช่วยป้องกันโครงสร้างข้อมูลจากการถ่ายทอดโครงสร้าง UI มันเหมือนกับสิ่งที่ตรงกันข้ามกับ UI ที่เปิดเผย
  2. คุณสามารถมี UIView ได้เพียงหนึ่งต่อ Xib Xib นั้นควรจะมี UIV วิวหลายอัน

File's ownerมีวิธีที่สง่างามที่จะทำมันโดยไม่ต้องใช้เป็น กรุณาตรวจสอบโพสต์บล็อกนี้ มันอธิบายวิธีการทำอย่างถูกวิธี


มันไม่สำคัญเลยคุณไม่ได้อธิบายว่าทำไมมันถึงไม่ดี ฉันไม่เห็นสาเหตุ ไม่มีผลข้างเคียง
JBarros35

1

นี่คือคำตอบที่คุณต้องการมาโดยตลอด คุณสามารถสร้างCustomViewชั้นเรียนของคุณมีตัวอย่างหลักของมันในรูปแบบ xib พร้อมด้วยหัวข้อย่อยและร้านค้าทั้งหมด จากนั้นคุณสามารถใช้คลาสนั้นกับอินสแตนซ์ใดก็ได้ในกระดานเรื่องราวหรือ xibs อื่น ๆ

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

แค่ทำสิ่งนี้:

  1. เฟรมเวิร์กการนำเข้า BFWControls
  2. เปลี่ยนซูเปอร์คลาสของคุณจากUIViewเป็นNibView(หรือจากUITableViewCellเป็นNibTableViewCell)

แค่นั้นแหละ!

มันยังทำงานร่วมกับ IBDesignable เพื่ออ้างอิงมุมมองที่กำหนดเองของคุณ (รวมถึงการแสดงตัวอย่างจาก xib) ในขณะออกแบบในกระดานเรื่องราว

คุณสามารถอ่านเพิ่มเติมได้ที่นี่: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

และคุณสามารถรับเฟรมเวิร์กของ BFWControls โอเพ่นซอร์สได้ที่นี่: https://github.com/BareFeetWare/BFWControls

และนี่คือการแยกNibReplaceableรหัสที่ขับง่าย ๆในกรณีที่คุณสงสัย:
 https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

ทอม👣


ทางออกที่ดีขอบคุณ!
avee

ฉันไม่ต้องการที่จะติดตั้งกรอบเมื่อมันควรจะให้กำเนิด
JBarros35

0

โซลูชันนี้สามารถใช้ได้แม้ว่าคลาสของคุณจะไม่มีชื่อเดียวกันกับ XIB ตัวอย่างเช่นถ้าคุณมีตัวควบคุมคลาสมุมมองฐาน controllerA ซึ่งมีชื่อ XIB controllerA.xib และคุณซับคลาสนี้ด้วย controllerB และต้องการสร้างอินสแตนซ์ของ controllerB ในกระดานเรื่องราวคุณสามารถ:

  • สร้างตัวควบคุมมุมมองในกระดานเรื่องราว
  • ตั้งค่าคลาสของคอนโทรลเลอร์เป็นคอนโทรลเลอร์ B
  • ลบมุมมองของ controllerB ในกระดานเรื่องราว
  • ลบล้างมุมมองโหลดใน controllerA ไปที่:

* * * *

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}

0

โซลูชั่นสำหรับวัตถุประสงค์ -C ตามขั้นตอนที่อธิบายไว้ในการตอบสนองของเบน Patch

ใช้ส่วนขยายสำหรับ UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

สร้างไฟล์MyView.h, และMyView.mMyView.xib

ก่อนอื่นให้เตรียมของคุณMyView.xibตามคำตอบของ Ben Patchดังนั้นให้ตั้งค่าคลาสMyViewสำหรับเจ้าของไฟล์แทนที่จะเป็นมุมมองหลักภายใน XIB นี้

MyView.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m:

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

และในภายหลังเพียงแค่สร้างมุมมองของคุณโดยใช้โปรแกรม:

MyView* view = [[MyView alloc] init];

คำเตือน! ตัวอย่างของมุมมองนี้จะไม่ปรากฏใน Storyboard หากคุณใช้ WatchKit Extension เนื่องจากข้อผิดพลาดนี้ใน Xcode> = 9.2: https://forums.developer.apple.com/thread/95616


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