I am working on a small UIView
component that displays hashtags in a UICollectionView
and for some reasons I needed to make it "editable" through the Interface Builder... Therefore, I made it IBDesignable
.
Everything works except when in prepareForInterfaceBuilder
I am trying to append a object to the datasource
, so the list displays something when integrated somewhere in the storyboard.
Arriving to the line self.hashtags.append(hashtag)
, this crashes. If I remove this line, the view is rendered properly on the storyboard, if I put it again ---> crash... After all my debugging it seems that IB can't access this property or sees it nil.
I can't debug anything, Xcode doesn't let me debug the selected views in Storyboard > Editor (being on macOS Mojave causes bugs). My view doesn't have a .xib
, and has both required init
's
I also checked the logs in the Console.app and theres nothing. Here is the main error message given by the Storyboard :
Main.storyboard: error: IB Designables: Failed to render and update auto layout status for PublishViewController (tSg-9E-Vz3): The agent threw an exception.
Code (what matters)
@IBDesignable
class HashtagView: UIView {
@IBOutlet weak var height: NSLayoutConstraint?
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
return view
}()
open var hashtags: [HashTag] = []
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
self.hashtags.append(HashTag(word: "it works")) < CRASH
self.collectionView.reloadData()
self.layoutSubviews()
}
}
Setup function()
func setup() {
self.backgroundColor = UIColor.clear
self.backgroundColor = UIColor.Custom.ligthGray
self.clipsToBounds = true
self.layer.cornerRadius = self.cornerRadius
let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top)
alignedFlowLayout.minimumLineSpacing = 5.0
alignedFlowLayout.minimumInteritemSpacing = 7.0
alignedFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
self.collectionView.collectionViewLayout = alignedFlowLayout
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.backgroundColor = UIColor.clear
self.collectionView.isScrollEnabled = false
self.collectionView.register(UINib(nibName: "RemovableTagCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "RemovableTagCollectionViewCell")
self.addSubview(self.collectionView)
self.collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
self.collectionView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
So the setup was definitely relevant. Apparently IB was crashing trying to load your cell for your UICollectionView. It could not load the Nib file. It did not have anything to do with the append it just crashed at the same time. I replicated your issue then created a cell with code and it all worked fine. Let me know if this helps and answers your question. All the required code is there.
import UIKit
struct HashTag {
var word:String?
}
class HashCollectionViewCell: UICollectionViewCell {
lazy var wordLabel : UILabel = {
let lbl = UILabel(frame: self.bounds)
lbl.font = UIFont.systemFont(ofSize: 17)
lbl.textColor = .black
return lbl
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(wordLabel)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(wordLabel)
}
func configureWithTag(tag:HashTag) {
wordLabel.text = tag.word
}
}
@IBDesignable
class HashTagView: UIView {
private var sizingLabel = UILabel(frame: .zero)
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
view.autoresizingMask = [.flexibleWidth,.flexibleHeight]
return view
}()
var hashtags: [HashTag] = [HashTag(word: "this works")]
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
sizingLabel.font = UIFont.systemFont(ofSize: 17)
self.addSubview(collectionView)
collectionView.frame = self.bounds
collectionView.backgroundColor = self.backgroundColor
collectionView.register(HashCollectionViewCell.self, forCellWithReuseIdentifier: "hashCell")
self.addSubview(collectionView)
for x in 0..<10{
addHashTag(tag: "This is more \(x)")
}
collectionView.delegate = self
collectionView.dataSource = self
}
func addHashTag(tag:String){
let newHash = HashTag(word: tag)
self.hashtags.append(newHash)
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.frame = self.bounds
}
}
extension HashTagView: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return hashtags.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hashCell", for: indexPath) as? HashCollectionViewCell{
let tag = self.hashtags[indexPath.item]
cell.configureWithTag(tag: tag)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let tag = self.hashtags[indexPath.item]
sizingLabel.text = tag.word
sizingLabel.sizeToFit()
return sizingLabel.frame.size
}
}
2) You can load a nib into the programmatically created cell and it will work. An example would look like this instead for the part that is the programatic cell.
Cell Code with View extension using nib inside cell:
extension UIView {
func loadNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nibName = type(of: self).description().components(separatedBy: ".").last!
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as! UIView
}
}
class HashCollectionViewCell: UICollectionViewCell {
var hashTagNib : HashTagNibView?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
if let htn = HashTagNibView().loadNib() as? HashTagNibView{
hashTagNib = htn
hashTagNib?.frame = self.bounds
hashTagNib?.autoresizingMask = [.flexibleWidth,.flexibleHeight]
self.addSubview(htn)
}
}
func configureWithTag(tag:HashTag) {
if let htn = hashTagNib{
htn.hashTagButtonLabel.setTitle(tag.word, for: .normal)
}
}
Nib setup would looks like this:
This might give you more flexibility depending on what you are doing and the result is similar except in the nib example I used a UIButton.