Home > database >  Custom UIView inside TableView Custom Cell "jumps" on table load
Custom UIView inside TableView Custom Cell "jumps" on table load

Time:04-19

I have a UITableView with custom cells inside it called NewsCell. Inside the NewsCell I have a custom UIView that is used as an action button.

Many times when the TableView is loading a cell, the custom action button appears at the top of the cell for a few seconds until it goes back down to its right spot at the bottom of the cell. Sometimes it returns to its right place only after I scroll the cell out of the viewport.

Any idea why this is happening?

Here is a photo to make things clearer: action button out of place

NewsCell was created using .xib file and here is the relevant codes:

protocol NewsCellDelegate {
    func favoriteIconDidPress(forArticle article: Article)
    func actionButtonDidPress(inside article: Article)
}

enum ArticleFavoriteMark {
    case selected
    case notSelected
}

class NewsCell: UITableViewCell, MainActionButtonDelegate{

    @IBOutlet weak var entireNewsCell: UIView!
    @IBOutlet weak var newsImage: UIImageView!
    @IBOutlet weak var favoriteIcon: UIImageView!
    
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var subjectTag: UIButton!
    @IBOutlet weak var moreSubjectsTag: UIButton!
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var authorLabel: UILabel!
    @IBOutlet weak var summaryLabel: UILabel!
    
    @IBOutlet weak var actionButton: MainActionButtonView!
    
    var delegate: NewsCellDelegate?
    var articleUrl = ""
    var articleID = ""
    var articleImageUrl = ""
    var isFavorite: Bool = false
    
    
    override func awakeFromNib() {
        super.awakeFromNib()

        setCellBorder()
        setCellColorsDesign()
        setImageRounded()
        setTagsRounded()
        setGestureRecognizer()
        
        actionButton.delegate = self
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()

        contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0))
    }
     

    func setGestureRecognizer() {
        favoriteIcon.addGestureRecognizer(UITapGestureRecognizer(target: favoriteIcon, action: #selector(favoriteIconPressed)))
        favoriteIcon.isUserInteractionEnabled = true
        let tapGestureRecognizer1 = UITapGestureRecognizer(target: self, action: #selector(favoriteIconPressed(tapGestureRecognizer:)))
        favoriteIcon.addGestureRecognizer(tapGestureRecognizer1)
    }
    
    
    @objc func favoriteIconPressed(tapGestureRecognizer: UITapGestureRecognizer) {
        let currentArticle = Article(id: articleID, articleTitle: titleLabel.text!, date: dateLabel.text ?? "", url: articleUrl, content: summaryLabel.text!, author: authorLabel.text ?? "", topic: subjectTag.currentTitle!, imageUrl: articleImageUrl , isFavorite: isFavorite)
        delegate?.favoriteIconDidPress(forArticle: currentArticle)
    }
    
    
    func actionButtonDidPress(btnText: String) {
        let currentArticle = Article(
            id: articleID,
            articleTitle: titleLabel.text ?? "",
            date: dateLabel.text ?? "",
            url: articleUrl,
            content: summaryLabel.text ?? "",
            author: authorLabel.text ?? "",
            topic: subjectTag.currentTitle ?? "",
            imageUrl: articleImageUrl,
            isFavorite: self.isFavorite)

        delegate?.actionButtonDidPress(inside: currentArticle)
    }
}

This is my NewsCell elements hierarchy:

news cell elements

And these are the constraints I put on the action button inside the NewsCell:

constraints on action button inside NewsCell

And this is how I constrained the ActionButton UIView: constraints inside the action button custom uiview

In my TableView I initiate each cell like this:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let currentCell = tableView.dequeueReusableCell(withIdentifier: Constants.TableCellsIdentifier.FAVORITES, for: indexPath) as! SavedArticleCell
        currentCell.delegate = self
        
        let currentKey = viewModel.keysArray[indexPath.row]
        let savedArticle = viewModel.savedArticles[currentKey]
        
        if let savedArticle = savedArticle {
            currentCell.articleID = savedArticle.id!
            currentCell.articleTitle.text = savedArticle.title
            currentCell.articleTopic.setTitle(savedArticle.topic, for: .normal)
            
            if let imageUrl = savedArticle.imageUrl {
                currentCell.articleImageURL = imageUrl
                currentCell.articleImage.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "light-gray-background"))
            } else {
                currentCell.articleImage.image = UIImage(named: "light-gray-background")
            }
        }
        
        return currentCell
    }

CodePudding user response:

You have to add contentView.layoutIfNeeded() in layoutSubViews() method of NewsCell

override func layoutSubviews() {
    super.layoutSubviews()
    
    contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0))
    contentView.layoutIfNeeded()
}

The purpose of layoutIfNeeded() is to force a synchronous (essentially "immediate") redraw of a view and its subviews. You have to call this because you have updated the layout in layoutsubviews() method. Regarding layoutIfNeeded() Here is what Apple says.

Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.

  • Related