title: How to properly ensure a custom UITableViewCell can be reused
body: <p>I have a <code>UITableViewController</code> that is rendering out a custom <code>UITableViewCell</code>'.</p>
  <p>This cells are related to chat messages, as such the config is almost identical, apart from how the elements are constrained.</p>
  <blockquote>
  <p>bot cell is: <em>avatar &gt; message</em></p>
  <p>user cell is <em>message &lt; avatar</em></p>
  </blockquote>
  <p>I was hoping to combine these in a single custom cell and simply switch on an <code>origin</code> property on the model, allowing me to choose which constraints I am applying.</p>
  <p>This worked for 5 or 6 messages, until however I ran a test with 30 messages and some cells started to inherit both sets of anchors or even just random properties that should be assigned to the other cell.</p>
  <p>I can see the errors suggest the constraints are invalid and I believe this is due to the cell not being prepared for reuse correctly.</p>
  <pre><code>(
      &quot;&lt;NSLayoutConstraint:0x600002533930 UIImageView:0x7fb401514d40.leading == UILayoutGuide:0x600003f18e00'UIViewLayoutMarginsGuide'.leading   (active)&gt;&quot;,
      &quot;&lt;NSLayoutConstraint:0x600002526990 UITextView:0x7fb40200a200'I am a Person.'.leading == UILayoutGuide:0x600003f18e00'UIViewLayoutMarginsGuide'.leading + 15   (active)&gt;&quot;,
      &quot;&lt;NSLayoutConstraint:0x6000025271b0 UITextView:0x7fb40200a200'I am a Person.'.trailing == UIImageView:0x7fb401514d40.leading - 15   (active)&gt;&quot;
  )
  </code></pre>
  <p><strong>ChatMessageCell</strong></p>
  <pre><code>class ChatMessageCell: UITableViewCell {
      fileprivate var content: ChatMessage? {
          didSet {
              guard let text = content?.text else { return }
              messageView.text = text
              
              guard let origin = content?.origin else { return }
              setupSubViews(origin)
          }
      }
      
      fileprivate var messageAvatar: UIImageView = {
          let imageView = UIImageView(frame: .zero)
          imageView.layer.cornerRadius = 35 / 2
          imageView.layer.masksToBounds = true
          return imageView
      }()
      
      fileprivate var messageView: UITextView = {
          let textView = UITextView()
          textView.isScrollEnabled = false
          textView.isSelectable = false
          textView.sizeToFit()
          textView.layoutIfNeeded()
          textView.contentInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
          textView.layer.cornerRadius = 10
          textView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
          textView.translatesAutoresizingMaskIntoConstraints = false
          return textView
      }()
      
      override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
          super.init(style: style, reuseIdentifier: reuseIdentifier)
          
          backgroundColor = UIColor.clear
      }
      
      required init?(coder aDecoder: NSCoder) {
          fatalError(&quot;init(coder:) has not been implemented&quot;)
      }
      
      func setContent(as content: ChatMessage) {
          self.content = content
      }
      
      override func prepareForReuse() {
          content = nil
      }
  }
  
  extension ChatMessageCell {
      fileprivate func setupSubViews(_ origin: ChatMessageOrigin) {
          let margins = contentView.layoutMarginsGuide
          
          [messageAvatar, messageView].forEach { v in contentView.addSubview(v) }
          
          switch origin {
          case .system:
              messageAvatar.image = #imageLiteral(resourceName: &quot;large-bot-head&quot;)
              messageAvatar.anchor(
                  top: margins.topAnchor, leading: margins.leadingAnchor, size: CGSize(width: 35, height: 35)
              )
              messageView.anchor(
                  top: margins.topAnchor, leading: messageAvatar.trailingAnchor, bottom: margins.bottomAnchor, trailing: margins.trailingAnchor,
                  padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
              )
          case .user:
              let userContentBG = UIColor.hexStringToUIColor(hex: &quot;00f5ff&quot;)
              messageAvatar.image = UIImage.from(color: userContentBG)
              messageAvatar.anchor(
                  top: margins.topAnchor, trailing: margins.trailingAnchor, size: CGSize(width: 35, height: 35)
              )
              messageView.layer.backgroundColor = userContentBG.cgColor
              messageView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
              messageView.anchor(
                  top: margins.topAnchor, leading: margins.leadingAnchor, bottom: margins.bottomAnchor, trailing: messageAvatar.leadingAnchor,
                  padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
              )
          }
      }
  }
  </code></pre>
  <p><strong>ChatController</strong></p>
  <pre><code>class ChatController: UITableViewController {
      lazy var viewModel: ChatViewModel = {
          let viewModel = ChatViewModel()
          return viewModel
      }()
      
      fileprivate let headerView: UIView = {
          let view = UIView(frame: .zero)
          view.backgroundColor = .white
          return view
      }()
      
      override func viewDidLoad() {
          super.viewDidLoad()
          
          viewModel.reloadData = { [weak self] in
              DispatchQueue.main.async {
                  self?.tableView.reloadData()
              }
          }
  
          configureViewHeader()
          configureTableView()
          registerTableCells()
      }
      
      override func viewDidLayoutSubviews() {
          super.viewDidLayoutSubviews()
          tableView.contentInset = UIEdgeInsets(top: 85, left: 0, bottom: 0, right: 0)
      }
      
      override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
          return viewModel.history.count
      }
      
      override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
          let item = viewModel.history[indexPath.row]
          let cell = tableView.dequeueReusableCell(withClass: ChatMessageCell.self)
          cell.setContent(as: item)
          cell.layoutSubviews()
          return cell
      }
  }
  
  extension ChatController {
      fileprivate func configureViewHeader() {
          let margins = view.safeAreaLayoutGuide
          view.addSubview(headerView)
          headerView.anchor(
              top: margins.topAnchor, leading: margins.leadingAnchor, trailing: margins.trailingAnchor,
              size: CGSize(width: 0, height: 70)
          )
      }
      
      fileprivate func configureTableView() {
          tableView.tableFooterView = UIView()
          tableView.allowsSelection = false
          tableView.rowHeight = UITableView.automaticDimension
          tableView.estimatedRowHeight = 200
          tableView.separatorStyle = .none
          tableView.backgroundColor = UIColor.clear
      }
      
      fileprivate func registerTableCells() {
          tableView.register(cellWithClass: ChatMessageCell.self)
      }
  }
  </code></pre>
  <p>An example of how the view changes on scroll can be seen here....
  <a href="https://i.stack.imgur.com/ilA05.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/ilA05.png" alt="Pre scroll" /></a>
  <a href="https://i.stack.imgur.com/sg3Zd.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/sg3Zd.png" alt="Post scroll" /></a></p>
  <p>My Extensions are applied with</p>
  <pre><code>  @discardableResult
      func anchor(top: NSLayoutYAxisAnchor? = nil, leading: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, trailing: NSLayoutXAxisAnchor? = nil, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -&gt; AnchoredConstraints {
          translatesAutoresizingMaskIntoConstraints = false
          var anchoredConstraints = AnchoredConstraints()
          
          if let top = top {
              anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
          }
          
          if let leading = leading {
              anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
          }
          
          if let bottom = bottom {
              anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
          }
          
          if let trailing = trailing {
              anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
          }
          
          if size.width != 0 {
              anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
          }
          
          if size.height != 0 {
              anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
          }
          
          [anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach { $0?.isActive = true }
          
          return anchoredConstraints
      }
  </code></pre>