当 UICollectionView 和 UICollectionViewCell 在 XIB 文件中时,子视​​图为零

Subviews are nil when UICollectionView and UICollectionViewCell are in XIB files

Hi there! I've created a sample project that shows my problem exactly. Click to download!

我创建了一个包含 UICollectionViewCell 子类的应用程序,它包含一个 ImageView 作为子视图。它还有一个包含视图的 XIB 文件。

当我在直接嵌入 ViewControllerUICollectionView 中使用此 UICollectionViewCell 时,单元格工作正常。这是 WorkingViewController 的代码,其中包含注册单元格的代码:

class WorkingViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
  @IBOutlet weak var collectionView: UICollectionView!

  override func viewDidLoad() {
    super.viewDidLoad()

    let cellNib = UINib(nibName: "ImageCell", bundle: nil)
    collectionView.register(cellNib, forCellWithReuseIdentifier: "ImageCell")

    collectionView.reloadData()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }

  func randomInt(min: Int, max: Int) -> Int {
    return Int(arc4random_uniform(UInt32(max - min + 1))) + min
  }

  func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 1
  }

  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 100
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as? ImageCell else {
      fatalError("Could not load cell")
    }

    let randomNumber = randomInt(min: 1, max: 10)
    cell.imageView.image = UIImage(named: "stockphoto\(randomNumber)")

    return cell
  }
}

现在将 故事板入口点 移动到 BuggyViewController。这里的 UICollectionView 不是直接嵌入的。它包含在一个 UIView 中,该 UIView 也有一个 XIB。这是该视图的代码,其中包含之前注册的单元格:

class DetailView: UIView, UICollectionViewDelegate, UICollectionViewDataSource {
  @IBOutlet var contentView: UIView!
  @IBOutlet weak var collectionView: UICollectionView!

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

    setup()
  }

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

    setup()
  }

  private func setup() {
    Bundle.main.loadNibNamed("DetailView", owner: self, options: nil)

    self.addSubview(self.contentView)
    self.contentView.frame = self.bounds
    self.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    collectionView.delegate = self
    collectionView.dataSource = self

    let cellNib = UINib(nibName: "ImageCell", bundle: nil)
    collectionView.register(cellNib, forCellWithReuseIdentifier: "ImageCell")
    collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")

    collectionView.reloadData()
  }

  func randomInt(min: Int, max: Int) -> Int {
    return Int(arc4random_uniform(UInt32(max - min + 1))) + min
  }

  func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 1
  }

  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 100
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as? ImageCell else {
      fatalError("Could not load cell")
    }

    let randomNumber = randomInt(min: 1, max: 10)
    cell.imageView.image = UIImage(named: "stockphoto\(randomNumber)")

    return cell
  }
}

但这会导致 DetailView 的第 55 行出错:

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

所以这里单元格的 imageView 突然变成了 nil,这让我觉得我在注册单元格或实例化它时做错了。但是我就是解决不了。

您应该删除注册单元格的行 class:

collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")

您确实已经在 Nib 中定义了您的单元格,应该通过 func register(_ nib: UINib?, forCellWithReuseIdentifier identifier: String) 方法注册它。通过cellClass方法注册会return nil.

正如 Apple 文档所述:

If you previously registered a class or nib file with the same reuse identifier, the class you specify in the cellClass parameter replaces the old entry. You may specify nil for cellClass if you want to unregister the class from the specified reuse identifier.

这解释了为什么你得到的是零。