简体   繁体   English

UITableView CustomCell 复用(ImageView in CustomCell)

[英]UITableView CustomCell Reuse (ImageView in CustomCell)

I'm pretty new to iOS dev and I have an issue with UITableViewCell.我是 iOS 开发人员的新手,我对 UITableViewCell 有疑问。 I guess it is related to dequeuing reusable cell.我想这与出列可重用单元格有关。 I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse).我在我的自定义表格视图单元格中添加了一个 UIImageView,还添加了一个点击手势来制作喜欢/不喜欢 function(图像从空心(不喜欢)变为充满心(喜欢)作为点击和反转)。 The problem is when I scroll down, some of the cells are automatically tapped.问题是当我向下滚动时,一些单元格会被自动点击。 I found out why this is happening, but still don't know how to fix it appropriately.我发现了为什么会这样,但仍然不知道如何适当地修复它。 Below are my codes,以下是我的代码,

ViewController视图控制器

import UIKit 
struct CellData {
var title: String
var done: Bool
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {     
var models = [CellData]()

private let tableView: UITableView = {
    let table = UITableView()
    table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
    return table
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(tableView)
    tableView.frame = view.bounds
    tableView.delegate = self
    tableView.dataSource = self
    configure()
}

private func configure() {
    self.models = Array(0...50).compactMap({
        CellData(title: "\($0)", done: false)
    })
}

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let model = models[indexPath.row]
    guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
        return UITableViewCell()
    }
    
    cell.textLabel?.text = model.title

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    tableView.deselectRow(at: indexPath, animated: true)
    tableView.reloadData()

}
}

TableViewCell表格视图单元格

import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"

let likeImage: UIImageView = {
    let imageView = UIImageView()
    imageView.image = UIImage(systemName: "heart")
    imageView.tintColor = .darkGray
    imageView.isUserInteractionEnabled = true
    imageView.translatesAutoresizingMaskIntoConstraints = false
    return imageView
}()


override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    contentView.addSubview(likeImage)
    layout()
    //Tap Gesture Recognizer 실행하기
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
    likeImage.addGestureRecognizer(tapGestureRecognizer)
    
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
    super.layoutSubviews()
}
override func prepareForReuse() {
    super.prepareForReuse()
}

private func layout() {
    likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
    likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
    likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
    likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}

@objc func didTapImageView(_ sender: UITapGestureRecognizer) {        

    if likeImage.image == UIImage(systemName: "heart.fill"){
        likeImage.image = UIImage(systemName: "heart")
        likeImage.tintColor = .darkGray
        

    } else {
        likeImage.image = UIImage(systemName: "heart.fill")
        likeImage.tintColor = .systemRed

    }
    
}
}

This gif shows how it works now.这个 gif 显示了它现在是如何工作的。 enter image description here在此处输入图像描述

I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).我尝试使用 CellData 结构中的“完成”属性来捕获 uiimageview 的状态但失败了(不知道如何以正确的方式使用它)。 I would be so happy if anyone can help this!如果有人可以提供帮助,我将非常高兴!

You've already figured out that the problem is cell reuse.您已经发现问题出在细胞重复使用上。

When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:当您出列要显示的单元格时,您已经根据您的数据设置了单元格标签的文本:

cell.textLabel?.text = model.title

you also need to tell the cell whether to show the empty or filled heart image.需要告诉单元格是显示空心还是填充心形图像。

And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.而且,当用户点击该图像时,您的单元格需要告诉 controller 更新数据 model 的.done属性。

That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure .这可以通过协议/委托模式或更常见的方式(尤其是 Swift)使用闭包来完成。

Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:这是对您发布的代码的快速修改......评论应该让您对发生的事情有一个很好的了解:

struct CellData {
    var title: String
    var done: Bool
}

class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var models = [CellData]()
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
        return table
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.delegate = self
        tableView.dataSource = self
        configure()
    }
    
    private func configure() {
        self.models = Array(0...50).compactMap({
            CellData(title: "\($0)", done: false)
        })
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return models.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
        
        let model = models[indexPath.row]
        
        cell.myLabel.text = model.title
        
        // set the "heart" to true/false
        cell.isLiked = model.done
        
        // closure
        cell.callback = { [weak self] theCell, isLiked in
            guard let self = self,
                  let pth = self.tableView.indexPath(for: theCell)
            else { return }
            
            // update our data
            self.models[pth.row].done = isLiked
        }
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }

}

class ShinTableViewCell: UITableViewCell {
    
    // we'll use this closure to communicate with the controller
    var callback: ((UITableViewCell, Bool) -> ())?
    
    static let identifier = "TableViewCell"
    
    let likeImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(systemName: "heart")
        imageView.tintColor = .darkGray
        imageView.isUserInteractionEnabled = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    let myLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    // we'll load the heart images once in init
    //  instead of loading them every time they change
    var likeIMG: UIImage!
    var unlikeIMG: UIImage!
    
    var isLiked: Bool = false {
        didSet {
            // update the image in the image view
            likeImageView.image = isLiked ? likeIMG : unlikeIMG
            // update the tint
            likeImageView.tintColor = isLiked ? .systemRed : .darkGray
        }
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        // make sure we load the heart images
        guard let img1 = UIImage(systemName: "heart"),
              let img2 = UIImage(systemName: "heart.fill")
        else {
            fatalError("Could not load the heart images!!!")
        }
        
        unlikeIMG = img1
        likeIMG = img2
        
        // add label and image view
        contentView.addSubview(myLabel)
        contentView.addSubview(likeImageView)

        layout()
        
        //Tap Gesture Recognizer 실행하기
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
        likeImageView.addGestureRecognizer(tapGestureRecognizer)
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutSubviews() {
        super.layoutSubviews()
    }
    override func prepareForReuse() {
        super.prepareForReuse()
    }
    
    private func layout() {

        // let's use the "built-in" margins guide
        let g = contentView.layoutMarginsGuide
        
        // image view bottom constraint
        let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
        // this will avoid auto-layout complaints
        bottomConstraint.priority = .required - 1
        
        NSLayoutConstraint.activate([
            
            // constrain label leading
            myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            // center the label vertically
            myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
        
            // constrain image view trailing
            likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            
            // constrain image view to 30 x 30
            likeImageView.widthAnchor.constraint(equalToConstant: 30),
            likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
            
            // constrain image view top
            likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
            
            // activate image view bottom constraint
            bottomConstraint,

        ])

    }
    
    @objc func didTapImageView(_ sender: UITapGestureRecognizer) {
        
        // toggle isLiked (true/false)
        isLiked.toggle()
        
        // inform the controller, so it can update the data
        callback?(self, isLiked)
        
    }
    
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM