Search code examples
swiftiboutlet

How can I refer to self during initialization when using IBOutlets for UIViews?


I have this custom UITableViewCell class backing my cells in a table view (I learned this approach from a talk by Andy Matuschak who worked at Apple on the UIKit team).

In the project I'm trying to apply this to now, I'm having a problem initializing the class because of a couple of @IBOutlets that are linked to UIView elements that won't ever have values like the UILabels get upon initialization:

class PublicationTableViewCell: UITableViewCell {

  @IBOutlet weak var publicationTitle: UILabel!
  @IBOutlet weak var authorName: UILabel!
  @IBOutlet weak var pubTypeBadgeView: UIView!
  @IBOutlet weak var pubTypeBadge: UILabel!
  @IBOutlet weak var topHighlightGradientStackView: UIStackView!
  @IBOutlet weak var bottomBorder: UIView!

  struct ViewData {
    let publicationTitle: UILabel
    let authorName: UILabel
    let pubTypeBadge: UILabel
  }

  var viewData: ViewData! {
    didSet {
      publicationTitle = viewData.publicationTitle
      authorName = viewData.authorName
      pubTypeBadge = viewData.pubTypeBadge
    }

  // I have @IBOutlets linked to the views so I can do this:
  override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    super.setHighlighted(highlighted, animated: animated)

    if isSelected || isHighlighted {
      topHighlightGradientStackView.isHidden = true
      bottomBorder.backgroundColor = UIColor.lightGrey1
    } else {
      topHighlightGradientStackView.isHidden = false
    }
  }
}

extension PublicationTableViewCell.ViewData {
  init(with publication: PublicationModel) {

    // Xcode complains here about referring to the properties 
    // on the left before all stored properties are initialized

    publicationTitle.text = publication.title
    authorName.text = publication.formattedAuthor.name
    pubTypeBadge.attributedText = publication.pubTypeBadge
  }
}

I then initialize it in cellForRow by passing in a publication like this:

cell.viewData = PublicationTableViewCell.ViewData(with: publication)

Xcode is complaining that I'm using self in init(with publication: PublicationModel) before all stored properties are initialized which I understand, but I can't figure out how to fix.

If these weren't UIView properties I might make them optional or computed properties perhaps, but because these are IBOutlets, I think they need to be implicitly unwrapped optionals.

Is there some other way I can get this to work?


Solution

  • First of all:

    struct ViewData {
       let publicationTitle: String
       let authorName: String
       let pubTypeBadge: NSAttributedString
    }
    

    Then simply:

    extension PublicationTableViewCell.ViewData {
      init(with publication: PublicationModel) 
        publicationTitle = publication.title
        authorName = publication.formattedAuthor.name
        pubTypeBadge = publication.pubTypeBadge
      }
    } 
    

    It's a data model, it shouldn't hold views, it should hold only data.

    Then:

    var viewData: ViewData! {
        didSet {
           publicationTitle.text = viewData.publicationTitle
           authorName.text = viewData.authorName
           pubTypeBadge.attributedString = viewData.pubTypeBadge
       }
    }
    

    However, I think it would be simpler if you just passed PublicationModel as your cell data. There is no reason to convert it to another struct.

    var viewData: PublicationModel! {
        didSet {
           publicationTitle.text = viewData.publicationTitle
           authorName.text = viewData.authorName
           pubTypeBadge.attributedString = viewData.pubTypeBadge
       }
    }