UITableView 中重复使用的单元格会导致发生不需要的操作

Reused Cells in UITableView Cause Unwanted Actions to Occur

我无法在网上找到答案,也不知道确切的问题,我也无法正确地表述问题。

我曾经有一个具有 "a custom UIView subview that contains a subview UIButton in x:2, y:2 coordinates" 的表格视图,并且该子视图 UIButton 有一个空白的星形图像,在用户触摸时切换为填充的星形图像。

图一:

图二:

它曾经工作得很好,直到我在这里和那里稍微修改了代码。

现在出现的问题是,当我触摸第1行的按钮时,例如,星号在第1行填满,但在第13行也填满。上图是单次触摸的结果"Acetate" 行旁边的星号。 有趣的是,当设备更改为 iPad Air 2 或具有其他屏幕尺寸的任何设备时,响应单元格会更改(例如,第 1 行响应第 22 行)。

我一直收到其他人的评论,说问题源于单元格被重用,但我对 UITableViewCell 缺乏了解,不知道发生了什么。我知道动态单元格是重复使用的,但这是否意味着某些单元格是彼此的克隆?

代码如下:

class WordListTableViewController: UITableViewController {


@IBOutlet weak var starButtonView: StarButton!

override func viewDidLoad() {
    super.viewDidLoad()

    //setting the wordList of wordListObject


    ChemQuizCollection.wordListObject.quizList.sortInPlace()

    ChemQuizCollection.formulaImages.sortInPlace()

    tableView.estimatedRowHeight = 30

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

// MARK: - Table view data source


override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return 25
}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = self.tableView.dequeueReusableCellWithIdentifier("WordListTableCell", forIndexPath: indexPath) as! WordListTableViewCell

    let row = indexPath.row

    cell.starButtonView.buttonRow = row

    cell.formulaLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
    cell.formulaLabel.text = ChemQuizCollection.wordListObject.quizList[row]
    cell.formulaImage.image = UIImage(named: ChemQuizCollection.formulaImages[row])


    if (FavoritesManager.favoritesList.objectForKey("\(row)") != nil) {
        cell.starButtonView.buttonSelected = true
    }


    return cell
}

class StarButton: UIView {

var buttonRow : Int = 0
var buttonSelected : Bool = false

override init (frame : CGRect)
{
    super.init(frame : frame)
    initStar()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    initStar()
}

func initStar() {

    let filledStarImage = UIImage(named: "filledStar")
    let emptyStarImage = UIImage(named: "emptyStar")

    let button = UIButton(frame: CGRect(x: 2, y: 2, width: 33, height: 33))

    button.userInteractionEnabled = true
    button.addTarget(self, action: #selector(StarButton.fillingStar(_:)), forControlEvents: UIControlEvents.TouchUpInside)

    button.setImage(emptyStarImage, forState: .Normal)
    button.setImage(filledStarImage, forState: .Selected)

    if buttonSelected == true {
        button.selected = true
    }

    addSubview(button)
}

//Could have various errors
func fillingStar(sender: UIButton) {
    if (sender.selected) == false {
        FavoritesManager.favoritesList.setObject(ChemQuizCollection.wordListObject.quizList[buttonRow], forKey: "\(buttonRow)")
        sender.selected = !sender.selected
        FavoritesManager.favoritesList.synchronize()
    } else {
        FavoritesManager.favoritesList.removeObjectForKey("\(buttonRow)")
        sender.selected = !sender.selected
        FavoritesManager.favoritesList.synchronize()
    }

}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
    for view in subviews
    {
        view.touchesBegan(touches, withEvent: event)
    }
}

override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
{
    return true
}

struct FavoritesManager {
     static var favoritesList = NSUserDefaults.standardUserDefaults()

     static func resetFavorites() {
         NSUserDefaults.standardUserDefaults().removePersistentDomainForName(NSBundle.mainBundle().bundleIdentifier!)
     }

}

提前谢谢大家。学校马上就要开学了,这个问题已经困扰了我一个星期了,所以我很想得到任何帮助:)。如果有哪位能留个方法让我可以不断的向他请教,感激不尽

是的,UITableViewCells 被重用 - 这意味着您的单元格数量有限 - 当一个单元格从屏幕上消失时,另一个当前不可见的单元格将进入屏幕,使用 tableView:cellForRowAtIndexPath

在你的情况下,你应该使用 tableView:didSelectRowAtIndexPath 方法来触发单元格的状态更改,并响应此更改
通常,你应该避免使用按钮在细胞中,当它真的不可避免时

希望这可能有所帮助

为避免此问题,如果未在 cellForRowAtIndexPath 中选择按钮,则需要将星形图标设置为其默认状态。也就是说,要重复使用的单元格需要设置星号为默认状态。

您正在维护选定单元格的列表,因此当调用 cellForRowAtIndexPath 时,所有选定单元格都应设置为突出显示其星形图标,所有其他单元格应设置为默认图标。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
     ...

     if (FavoritesManager.favoritesList.objectForKey("\(row)") != nil) {
          cell.starButtonView.buttonSelected = true
     } else {
          cell.starButtonView.buttonSelected = false
     }

     ...
}

在包含按钮的星视图中,您需要根据您在上述方法中设置的内容将按钮设置为取消选择。

class StarButton: UIView {
...

     func initStar(){
          ...

          if buttonSelected == true {
              button.selected = true
          } else { 
              button.selected = false
          }

          ...
     }   
...  

}