简体   繁体   中英

Table view cell checkmark changes position when scrolling

I am new to Swift. I have created a simple list in a tableview. When the user long presses on a row, that row will get checked. It's working perfectly fine. But when I scroll down, check mark changes its position. I also tried to store position in NSMutableSet. But still it's not working. Maybe I am doing something wrong.

This is my code:

This method gets called on long press.

func longpress(gestureRecognizer: UIGestureRecognizer)
{
    let longpress = gestureRecognizer as! UILongPressGestureRecognizer
    let state = longpress.state
    let locationInview = longpress.location(in: tableview1)
    var indexpath=tableview1.indexPathForRow(at: locationInview)

    if(gestureRecognizer.state == UIGestureRecognizerState.began)
    {
        if(tableview1.cellForRow(at: indexpath!)?.accessoryType == 
        UITableViewCellAccessoryType.checkmark)
        {
            tableview1.cellForRow(at: indexpath!)?.accessoryType = 
                UITableViewCellAccessoryType.none
        }
        else{
            tableview1.cellForRow(at: indexpath!)?.accessoryType = 
                UITableViewCellAccessoryType.checkmark
        }
    }
}

The problem is that cells are reused and when you update a checkmark, you're updating a cell, but not updating your model. So when a cell scrolls out of view and the cell is reused, your cellForRowAt is obviously not resetting the checkmark for the new row of the table.

Likewise, if you scroll the cell back into view, cellForRowAt has no way of knowing whether the cell should be checked or not. You have to

  • when you detect your gesture on the cell, you have to update your model to know that this row's cell should have a check; and

  • your cellForRowAt has to look at this property when configuring the cell.

So, first make sure your model has some value to indicate whether it is checked/selected or not. In this example, I'll use "Item", but you'd use a more meaningful type name:

struct Item {
    let name: String
    var checked: Bool
}

Then your view controller can populate cells appropriately in cellForRowAt :

class ViewController: UITableViewController {

    var items: [Item]!

    override func viewDidLoad() {
        super.viewDidLoad()

        addItems()
    }

    /// Create a lot of sample data so I have enough for a scrolling view

    private func addItems() {
        let formatter = NumberFormatter()
        formatter.numberStyle = .spellOut
        items = (0 ..< 1000).map { Item(name: formatter.string(from: NSNumber(value: $0))!, checked: false) }
    }
}

extension ViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
        cell.delegate = self
        cell.textLabel?.text = items[indexPath.row].name
        cell.accessoryType = items[indexPath.row].checked ? .checkmark : .none
        return cell
    }
}

Now, I generally let the cell handle stuff like recognizing gestures and inform the view controller accordingly. So create a UITableViewCell subclass, and specify this as the base class in the cell prototype on the storyboard. But the cell needs some protocol to inform the view controller that a long press took place:

protocol ItemCellDelegate: class {
    func didLongPressCell(_ cell: UITableViewCell)
}

And the table view controller can handle this delegate method, toggling its model and reloading the cell accordingly:

extension ViewController: ItemCellDelegate {
    func didLongPressCell(_ cell: UITableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else { return }

        items[indexPath.row].checked = !items[indexPath.row].checked
        tableView.reloadRows(at: [indexPath], with: .fade)
    }
}

Then, the UITableViewCell subclass just needs a long press gesture recognizer and, upon the gesture being recognized, inform the view controller:

class ItemCell: UITableViewCell {

    weak var delegate: CellDelegate?

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

        let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
        addGestureRecognizer(longPress)
    }

    @IBAction func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
        if gesture.state == .began {
            delegate?.didLongPressCell(self)
        }
    }
}

By the way, by having the gesture on the cell, it avoids confusion resulting from "what if I long press on something that isn't a cell". The cell is the right place for the gesture recognizer.

You are not storing the change anywhere.

To avoid using too much memory, the phone reuses cells and asks you to configure them in the TableView 's dataSource .

Let's say you have an array called data that has some structs that store what you want to show as cells. You would need to update this array and tell the tableView to reload your cell.

func userDidLongPress(gestureRecognizer: UILongPressGestureRecognizer) {

    // We only care if the user began the longPress
    guard gestureRecognizer.state == UIGestureRecognizerState.began else {
        return
    }

    let locationInView = gestureRecognizer.location(in: tableView)

    // Nothing to do here if user didn't longPress on a cell
    guard let indexPath = tableView.indexPathForRow(at: locationInView) else {
        return
    }

    // Flip the associated value and reload the row
    data[indexPath.row].isChecked = !data[indexPath.row].isChecked
    tableView.reloadRows(at: [indexPath], with: .automatic)
}

And always set the accessory when you configure a cell:

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

    let cell = tableView.dequeueReusableCell(
        withIdentifier: "someIdentifier",
        for: indexPath
    )

    cell.accessoryType = data[indexPath.row].isChecked ? .checkmark : .none
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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