0

Problem:

I need to top populate the tableView in order to keep the content of existing cells loaded such as playing a video.

ref.child(live_mode).queryOrderedByKey().queryLimited(toLast: 200).observe(.childAdded, with: {snapshot in

    self.posts.insert(PostFeed(uid: uid , profile_image_url: profile_image_url, profile_name: profile_name, post_id: post_id, post_title: post_title, post_text: post_text, post_type: post_type, youtube_video_url: youtube_video_url, youtube_video_id: youtube_video_id, youtube_image_url: youtube_image_url, time_stamp: time_stamp, like_count: "0"), at: 0)
            
    self.tableView.insertRows(at: [IndexPath.init(row: 0, section: 0)], with: .automatic)

})

The problem with the above method is that the cell index paths are all the same when assigning tags in

extension ChatViewController: UITableViewDelegate, UITableViewDataSource {


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return posts.count
}

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        if(posts[indexPath.row].post_type == "text"){
            
            let textCell = tableView.dequeueReusableCell(withIdentifier: "TextTableViewCell", for: indexPath) as! TextTableViewCell
            
            textCell.mainContentView.backgroundColor = .appBackgroundColor
            
            let profileImageURL = posts[indexPath.row].profile_image_url
            
            textCell.profileImageView.layer.borderWidth = 1.0
            textCell.profileImageView.layer.masksToBounds = false
            textCell.profileImageView.layer.borderColor = UIColor.white.cgColor
            textCell.profileImageView.layer.cornerRadius = textCell.profileImageView.frame.size.width / 2
            textCell.profileImageView.clipsToBounds = true
            textCell.profileImageView.af_setImage(withURL: URL(string: profileImageURL)!, placeholderImage: avatar_placeholder, filter: nil,  imageTransition: .crossDissolve(0.5), runImageTransitionIfCached: true, completion: nil)
            
            textCell.usernameLabel.text = posts[indexPath.row].profile_name
            textCell.textBodyTextView.text = posts[indexPath.row].post_text
            
            textCell.usernameLabel.font = .sfProMedium(ofSize: 14)
            textCell.usernameLabel.textColor = .systemGray
            textCell.textBodyTextView.font = .sfProSemiBold(ofSize: 15)
            textCell.textBodyTextView.textColor = .label
            
            textCell.shareButton.addTarget(self, action: #selector(shareButtonTap(sender:)), for: .touchUpInside)
            textCell.shareButton.tag = indexPath.row
            
            textCell.elipsesButton.addTarget(self, action: #selector(elipsesButtonTap(sender:)), for: .touchUpInside)
            textCell.elipsesButton.tag = indexPath.row


            return textCell
            
        }else{
            
            print("print loadign cll: ", indexPath.row)
            
            let videoCell = tableView.dequeueReusableCell(withIdentifier: "VideoTableViewCell", for: indexPath) as! VideoTableViewCell
            
            
            videoCell.mainContentView.backgroundColor = .appBackgroundColor
            let profileImageURL = posts[indexPath.row].profile_image_url
            let youtubeImageURL = posts[indexPath.row].youtube_image_url
            let yt_video_id = posts[indexPath.row].youtube_video_id
            
            
            videoCell.profileImageView.layer.borderWidth = 1.0
            videoCell.profileImageView.layer.masksToBounds = false
            videoCell.profileImageView.layer.borderColor = UIColor.white.cgColor
            videoCell.profileImageView.layer.cornerRadius = videoCell.profileImageView.frame.size.width / 2
            videoCell.profileImageView.clipsToBounds = true
            
            videoCell.usernameLabel.text = posts[indexPath.row].profile_name
            videoCell.textBodyTextView.text = posts[indexPath.row].post_title
            
            videoCell.usernameLabel.font = .sfProMedium(ofSize: 14)
            videoCell.usernameLabel.textColor = .systemGray
            videoCell.textBodyTextView.font = .sfProSemiBold(ofSize: 15)
            videoCell.textBodyTextView.textColor = .label
            videoCell.yt_thumbnail_image.isHidden = false
            
            
            let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleYTTap(_:)))
            videoCell.yt_thumbnail_image.tag = indexPath.row
            videoCell.yt_thumbnail_image.isUserInteractionEnabled = true
            videoCell.yt_thumbnail_image.addGestureRecognizer(recognizer)
            recognizer.delegate = self
            recognizer.view?.tag = indexPath.row
            
            videoCell.shareButton.addTarget(self, action: #selector(shareButtonTap(sender:)), for: .touchUpInside)
            videoCell.shareButton.tag = indexPath.row
            
            videoCell.elipsesButton.addTarget(self, action: #selector(elipsesButtonTap(sender:)), for: .touchUpInside)
            videoCell.elipsesButton.tag = indexPath.row
            videoCell.elipsesButton.isUserInteractionEnabled = true
            
            videoCell.logoLoadingImageView.isHidden = true
            
            videoCell.playerView.isHidden = true
            videoCell.playerView.delegate = self
            videoCell.playerView.tag = indexPath.row
            
            videoCell.profileImageView.af_setImage(withURL: URL(string: profileImageURL)!, placeholderImage: avatar_placeholder, filter: nil,  imageTransition: .crossDissolve(0.5), runImageTransitionIfCached: true, completion: nil)
            
           videoCell.yt_thumbnail_image.af_setImage(withURL: URL(string: youtubeImageURL)!, placeholderImage: thumbnail_placeholder, filter: nil,  imageTransition: .crossDissolve(0.5), runImageTransitionIfCached: false, completion: nil)
            
            return videoCell
            
        }
       
        return UITableViewCell()   
    }

OUTPUT:

cell index path: [0, 0] cell index path: [0, 0] cell index path: [0, 0] cell index path: [0, 0] cell index path: [0, 0] cell index path: [0, 0] cell index path: [0, 0] cell index path: [0, 0]

What is the proper way to top load the tableView by using insertRows but maintaining the unique indexPath.row value?

tableView.reloadData() gives me the proper unique index's but doesnt give the ability to top load the tableView to avoid cell data refreshing such as playing a video in the cell.

Pseudocode:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    customCounter = customCounter + 1
    let cellTag = customCounter
    print("cell index tag: ", cellTag)
}

I have also tried self.posts.count - 1, but indexes are not shown properly on initial load. When i scroll to the bottom then back to the top they are properly in place.

self.tableView.insertRows(at: [IndexPath.init(row: self.posts.count - 1, section: 0)], with: .automatic)
  • Why do you need to give each cell a unique number? Most likely there is a better solution. – HangarRash Feb 27 '23 at 17:01
  • indexPath.row represents a tag for an input on the cell. @HangarRash – adalovelacy Feb 27 '23 at 17:03
  • That's not clear enough. Perhaps you should update your question with your complete `cellForRowAt` implementation. If appropriate, also show your custom cell class. – HangarRash Feb 27 '23 at 17:05
  • 1
    So why are you using the indexPath as a tag on so many views? That's always the wrong solution. Show enough relevant code for why you are setting those view tags to the row so a better solution can be given. Also, as a side note, you should not have so much code in `cellForRowAt`. You should be passing data to the two custom cells and let the custom cells setup their own views. – HangarRash Feb 27 '23 at 17:19
  • @HangarRash so much code? Its mainly UI changes and button recognizers based on cell index? – adalovelacy Feb 27 '23 at 17:25
  • 1
    Just post relevant code for one place you are using the indexPath row as a tag. Once you get a solution for that you can apply it yourself to all of your other uses. – HangarRash Feb 27 '23 at 17:30

1 Answers1

1

When you call

self.tableView.insertRows(at: [IndexPath.init(row: 0, section: 0)], with: .automatic)

You specify that all of your index paths are at 0,0 - hence why you see the result in the cellForRow method. You are always inserting cell at index 0 and this is expected and is the intended behaviour.

When you call reloadData as you've said it will reload all cells and you will get the proper indices. However, you're relying on the index path of a value within the currently dequeued cell. This won't work since you should not be adding a relationship between the cells and the index paths.

From the docs :

A table view maintains a queue or list of UITableViewCell objects that the data source has marked for reuse. Call this method from your data source object when asked to provide a new cell for the table view. This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered.

Which means that when you do

textCell.shareButton.tag = indexPath.row

Your cell shareButton.tag will contain the current index path, but cells are being reused and at some point later you will set a tag that won't represent the actual location in the model (which is what you'd expect).

So instead of trying to rely on the tag to determine which button was tapped you could rely on the cell that the button is contained in. If you can obtain the cell then you can call indexPathForCell which will give you the current index path of the given cell - which then you can use to make a map towards your model.

Check this answer for additional information.

Petar
  • 2,241
  • 1
  • 24
  • 38
  • 1
    [here](https://stackoverflow.com/questions/28659845/how-to-get-the-indexpath-row-when-an-element-is-activated/38941510) are other solutions. Using the `tag` is never a good idea – Paulw11 Feb 27 '23 at 18:57