简体   繁体   中英

How to visualise changes in tableViewCell when tapped on cell in Swift 4

I want to make tableView and after tap on cell to change text in cell and to do it inside one function. In one of first versions of Swift it worked and now it just changed after finishing

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

this is a sample of code:

import UIKit

class TableViewController: UITableViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        cell?.textLabel?.text = "cell \(indexPath.row + 1)"
        return cell!
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath)

        cell?.textLabel?.text = "hello"
        sleep(2)
        cell?.textLabel?.text = "bye"
        sleep(2)       
    }
}

I want it to work like this: tap on cell -> cell text changed to "hello", waits for 2 seconds, then changed to "bye", waits another 2 seconds and continues

But now it works like this: tap on cell -> waits for 4 seconds, changed to "bye" and continues

You can use DispatchQueue to get the better result:

  cell?.textLabel?.text = "hello"
  tableView.isUserInteractionEnabled = false
  DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
       // your code here
       cell?.textLabel?.text = "bye"
       tableView.isUserInteractionEnabled = true
    }

This is actually a more complicated task than it seems.

First off, never sleep on the main queue. This locks up the main thread and makes your app appear unresponsive.

You need to have a data model to keep track of the title for each row and in this case, a timer so known when to update a row.

You also need to deal with the user tapping a row and then scrolling that row off screen and back.

It would also help to allow a user to tap several rows and have each update as needed. And you need to deal with the user leaving the table view during the delay between row updates.

Here is one solution that supports all of these issues.

class TableViewController: UITableViewController {
    struct RowData {
        var title: String
        var timer: Timer?

        init(_ title: String) {
            self.title = title
        }
    }

    var rowData = [RowData]()

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell")

        let data = rowData[indexPath.row]

        cell.textLabel?.text = data.title

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        var data = rowData[indexPath.row]
        if data.timer == nil {
            // No timer for this row - set to "hello" and start a timer
            let timer = Timer(timeInterval: 2, target: self, selector: #selector(timerTick), userInfo: indexPath, repeats: false)
            RunLoop.main.add(timer, forMode: .commonModes) // Allow timer to work while scrolling
            data.title = "hello"
            data.timer = timer
            rowData[indexPath.row] = data

            tableView.reloadRows(at: [ indexPath ], with: .fade)
        }
    }

    @objc func timerTick(_ timer: Timer) {
        // Update the row to "bye" and reset the timer
        let indexPath = timer.userInfo as! IndexPath

        var data = rowData[indexPath.row]
        data.timer = nil
        data.title = "bye"
        rowData[indexPath.row] = data

        tableView.reloadRows(at: [ indexPath ], with: .fade)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Load test data
        for i in 1...50 {
            rowData.append(RowData("Row \(i)"))
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        if isMovingFromParentViewController || isBeingDismissed {
            // Cancel all timers
            for data in rowData {
                if let timer = data.timer {
                    timer.invalidate()
                }
            }
        }
    }
}

With a few changes you can also support the user tapping a row before it reaches the value of "bye" and have the row reset back to the original value. That is left as an exercise.

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