Daniel Daniel - 19 days ago 5
Swift Question

Reused Cells in UITableView Cause Unwanted Actions to Occur

I couldn't find an answer online, nor could I phrase the question properly without knowing the exact problem.

I used to have a tableview that has "a custom UIView subview that contains a subview UIButton in x:2, y:2 coordinates", and that subview UIButton has a blank star image that switches to a filled up star image upon user's touch.

Image1:
Image1

Image2:
Image2

It used to work fine, until I touched the code a bit here and there.

The problem that occurs now is that when I touch the button in row 1, for example, the star fills up in row 1, but also in row 13. The pictures above are the results of a single touch on the star next to "Acetate" row.
Funnily enough, when the device changes to iPad Air 2, or any device with other screen size, the responding cells change (for example, row 1 responds with row 22).

I've been receiving comments from other people that the problem originates from cells being reused, but I lack understanding of UITableViewCell to know what is going on. I understand that dynamic cells are reused, but does that mean that certain cells are clones of each other?

The codes are as follows:

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
}


And

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
}


And

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

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

}


Thank you all in advance. School's starting soon, and this problem's been nagging me for a week, so I'm kind of desperate for any help :). If anyone could leave a way for me to continuously ask him questions, I would be genuinely grateful.

Answer

Yes, UITableViewCells are reused - that means that you have limited amount of cell - and when one cell is going off from screen, the another cell, that now is currently invisible, is going to the screen, using tableView:cellForRowAtIndexPath

In your case, you should use tableView:didSelectRowAtIndexPath method for trigger state changing of cell, and responds to this changes
Generally, you should avoid using button in cells, when it's really inevitable

Hope this may help

Comments