简体   繁体   中英

TableView cells keep changing order

Something weird is happening with a timer app I am making:

Every 0.25 seconds I update each cell's contents, but I sometimes get this weird glitch/bug (the table view cells keep changing order):

https://streamable.com/tta56

Code that updates timer:

Timer:

timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(processTimer), userInfo: nil, repeats: true)

processTimer() function:

@objc func processTimer() {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "TimersData")
    request.returnsObjectsAsFaults = false

    do {
        let results = try context.fetch(request)

        var i = 0
        if results.count > 0 {
            for result in results as! [NSManagedObject] {
                if let activityIndicator = result.value(forKey: "activity") as? String {
                    if activityIndicator == "active" {
                        if let startDate = result.value(forKey: "startDate") as? Date {
                            var seconds = startDate.timeIntervalSinceNow
                            seconds = seconds * -1
                            if let timerSeconds = result.value(forKey: "timerSeconds") as? Int {
                                if let timerProgress = result.value(forKey: "timerProgress") as? Double {
                                    var newSeconds: Double = timerProgress - seconds
                                    if newSeconds < 0 {
                                        newSeconds = 0
                                    }
                                    result.setValue(newSeconds, forKey: "timerProgress")
                                    let now = Date()
                                    result.setValue(now, forKey: "startDate")
                                    do {
                                        try context.save()
                                    }
                                    catch {
                                        print("error")
                                    }
                                    result.setValue(Date(), forKey: "startDate")
                                    // TEST OUT datesArray[i] = Date()
                                }
                            }
                            else {
                                result.setValue(Date(), forKey: "startDate")
                                // TEST OUT datesArray[i] = Date()
                            }
                        }
                        else {
                            result.setValue(Date(), forKey: "startDate")
                        }
                    }
                    else {
                        result.setValue(nil, forKey: "startDate")
                        // TEST OUT datesArray[i] = nil
                    }
                }
                i += 1
            }
        }

    }
    catch {

    }

    // tableView.reloadData()
    let indexPathsArray = tableView.indexPathsForVisibleRows
    for indexPath in indexPathsArray! {

        var activity = ""

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let context = appDelegate.persistentContainer.viewContext
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "TimersData")
        request.returnsObjectsAsFaults = false

        do {
            let results = try context.fetch(request)

            if results.count > 0 {
                if segmentedControl.selectedSegmentIndex == 0 {

                    let result = (results as! [NSManagedObject])[indexPath.row]
                    if let activityIndicator = result.value(forKey: "activity") as? String {
                        activity = activityIndicator
                    }

                    var timerString = ""
                    var hourString = ""
                    var minuteString = ""
                    var secondString = ""
                    if let seconds = result.value(forKey: "timerSeconds") as? Int {
                        if let secondsLeftDouble = result.value(forKey: "timerProgress") as? Double {
                            let secondsLeft: Int = Int(floor(1*secondsLeftDouble)/1)
                            if seconds < 1 {
                            }
                            else if seconds >= 5 && seconds < 60 { // ss
                                timerString = "\(secondsLeft)s"
                            }
                            else if seconds >= 60 && seconds < 3600 { // mm:ss
                                let minute = secondsLeft / 60
                                minuteString = "\(minute)"
                                if minute < 10 {

                                    minuteString = "0\(minute)"

                                }
                                let second = secondsLeft % 60
                                var secondString: String = "\(second)"
                                if second < 10 {

                                    secondString = "0\(second)"

                                }
                                timerString = "\(minuteString):\(secondString)"

                            }
                            else if seconds >= 3600 && seconds <= 86400 { // hh:mm:ss
                                let hourString: String = "\(secondsLeft / 3600)"
                                let minute = (secondsLeft % 3600) / 60
                                var minuteString: String = "\(minute)"
                                if minute < 10 {

                                    minuteString = "0\(minute)"

                                }
                                let second = (secondsLeft % 3600) % 60
                                var secondString: String = "\(second)"
                                if second < 10 {

                                    secondString = "0\(second)"

                                }
                                timerString = "\(hourString):\(minuteString):\(secondString)"
                            }
                        }
                    }

                    var timerTitle = ""
                    if let title = result.value(forKey: "timerName") as? String {
                        timerTitle = title
                    }

                    let cell = tableView.cellForRow(at: indexPath) as! activeTableViewCell
                    cell.configureCell(name: timerTitle, time: timerString, activityIndicator: activity, indexPathToSend: indexPath.row)

                    /* if activity == "active" {
                        let cell = tableView.cellForRow(at: indexPath) as! activeTableViewCell
                        cell.configureCell(name: timerTitle, time: timerString)
                        cell.sendIndexPath(indexPathToSend: indexPath.row)
                    }
                    else if activity == "paused" {
                        let cell = tableView.cellForRow(at: indexPath) as! pausedTableViewCell
                        cell.configureCell(name: timerTitle, time: timerString)
                    }
                    else if activity == "none" {
                        let cell = tableView.cellForRow(at: indexPath) as! nonactiveTableViewCell
                        cell.configureCell(name: timerTitle, time: timerString)
                    }
                    else {
                        let cell = tableView.cellForRow(at: indexPath) as! nonactiveTableViewCell
                        cell.configureCell(name: timerTitle, time: timerString)
                    } */

                }
                else {
                    var i = 0
                    activeTimersArray = []
                    for result in results as! [NSManagedObject] {
                        if let activityIndicator = result.value(forKey: "activity") as? String {
                            if activityIndicator == "active" {
                                activeTimersArray.append(i)
                            }
                        }
                        i += 1
                    }

                    if indexPath.row < activeTimersArray.count {
                        let result = (results as! [NSManagedObject])[activeTimersArray[indexPath.row]]

                        var timerString = ""
                        var hourString = ""
                        var minuteString = ""
                        var secondString = ""
                        if let seconds = result.value(forKey: "timerSeconds") as? Int {
                            if let secondsLeftDouble = result.value(forKey: "timerProgress") as? Double {
                                let secondsLeft: Int = Int(round(secondsLeftDouble))
                                if seconds < 1 {
                                }
                                else if seconds >= 5 && seconds < 60 { // ss
                                    timerString = "\(secondsLeft)s"
                                }
                                else if seconds >= 60 && seconds < 3600 { // mm:ss
                                    let minute = secondsLeft / 60
                                    minuteString = "\(minute)"
                                    if minute < 10 {

                                        minuteString = "0\(minute)"

                                    }
                                    let second = secondsLeft % 60
                                    var secondString: String = "\(second)"
                                    if second < 10 {

                                        secondString = "0\(second)"

                                    }
                                    timerString = "\(minuteString):\(secondString)"

                                }
                                else if seconds >= 3600 && seconds <= 86400 { // hh:mm:ss
                                    let hourString: String = "\(secondsLeft / 3600)"
                                    let minute = (secondsLeft % 3600) / 60
                                    var minuteString: String = "\(minute)"
                                    if minute < 10 {

                                        minuteString = "0\(minute)"

                                    }
                                    let second = (secondsLeft % 3600) % 60
                                    var secondString: String = "\(second)"
                                    if second < 10 {

                                        secondString = "0\(second)"

                                    }
                                    timerString = "\(hourString):\(minuteString):\(secondString)"
                                }
                            }
                        }

                        var timerTitle = ""
                        if let title = result.value(forKey: "timerName") as? String {
                            timerTitle = title
                        }

                        let cell = tableView.cellForRow(at: indexPath) as! activeTableViewCell
                        cell.configureCell(name: timerTitle, time: timerString, activityIndicator: "active", indexPathToSend: indexPath.row)
                    }
                    else {
                        let cell = tableView.cellForRow(at: indexPath) as! activeTableViewCell
                        cell.configureCell(name: "Processing", time: "Processing", activityIndicator: "active", indexPathToSend: indexPath.row)
                    }


                }

            }
            else {
                // let cell = tableView.cellForRow(at: indexPath) as! nonactiveTableViewCell
                // cell.configureCell(name: "Error", time: "Error")
                let cell = tableView.cellForRow(at: indexPath) as! activeTableViewCell
                cell.configureCell(name: "Error", time: "Error", activityIndicator: "none", indexPathToSend: indexPath.row)
            }
        }
        catch {
            // let cell = tableView.cellForRow(at: indexPath) as! nonactiveTableViewCell
            // cell.configureCell(name: "Error", time: "Error")

            let cell = tableView.cellForRow(at: indexPath) as! activeTableViewCell
            cell.configureCell(name: "Error", time: "Error", activityIndicator: "none", indexPathToSend: indexPath.row)
        }
    }
}

Code for TableView data source:

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

    var noRows = 0

    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "TimersData")
    request.returnsObjectsAsFaults = false

    do {
        let results = try context.fetch(request)

        if segmentedControl.selectedSegmentIndex == 0 {
            noRows = results.count
        }
        else {
            for result in results as! [NSManagedObject] {
                if let activityIndicator = result.value(forKey: "activity") as? String {
                    if activityIndicator == "active" {
                        noRows += 1
                    }
                }
            }
        }

    }
    catch {

    }

    return noRows
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    var activity = ""

    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "TimersData")
    request.returnsObjectsAsFaults = false

    do {
        let results = try context.fetch(request)

        if results.count > 0 {
            if segmentedControl.selectedSegmentIndex == 0 {

                let result = (results as! [NSManagedObject])[indexPath.row]
                if let activityIndicator = result.value(forKey: "activity") as? String {
                    activity = activityIndicator
                }

                var timerString = ""
                var hourString = ""
                var minuteString = ""
                var secondString = ""
                if let seconds = result.value(forKey: "timerSeconds") as? Int {
                    if let secondsLeftDouble = result.value(forKey: "timerProgress") as? Double {
                        let secondsLeft: Int = Int(round(secondsLeftDouble))
                        if seconds < 1 {
                        }
                        else if seconds >= 5 && seconds < 60 { // ss
                            timerString = "\(secondsLeft)s"
                        }
                        else if seconds >= 60 && seconds < 3600 { // mm:ss
                            let minute = secondsLeft / 60
                            minuteString = "\(minute)"
                            if minute < 10 {

                                minuteString = "0\(minute)"

                            }
                            let second = secondsLeft % 60
                            var secondString: String = "\(second)"
                            if second < 10 {

                                secondString = "0\(second)"

                            }
                            timerString = "\(minuteString):\(secondString)"

                        }
                        else if seconds >= 3600 && seconds <= 86400 { // hh:mm:ss
                            let hourString: String = "\(secondsLeft / 3600)"
                            let minute = (secondsLeft % 3600) / 60
                            var minuteString: String = "\(minute)"
                            if minute < 10 {

                                minuteString = "0\(minute)"

                            }
                            let second = (secondsLeft % 3600) % 60
                            var secondString: String = "\(second)"
                            if second < 10 {

                                secondString = "0\(second)"

                            }
                            timerString = "\(hourString):\(minuteString):\(secondString)"
                        }
                    }
                }

                var timerTitle = ""
                if let title = result.value(forKey: "timerName") as? String {
                    timerTitle = title
                }

                let cell = tableView.dequeueReusableCell(withIdentifier: "active", for: indexPath) as! activeTableViewCell
                cell.configureCell(name: timerTitle, time: timerString, activityIndicator: activity, indexPathToSend: indexPath.row)
                return cell

                /* if activity == "active" {
                    let cell = tableView.dequeueReusableCell(withIdentifier: "active", for: indexPath) as! activeTableViewCell
                    cell.configureCell(name: timerTitle, time: timerString)
                    return cell
                }
                else if activity == "paused" {
                    let cell = tableView.dequeueReusableCell(withIdentifier: "paused", for: indexPath) as! pausedTableViewCell
                    cell.configureCell(name: timerTitle, time: timerString)
                    return cell
                }
                else if activity == "none" {
                    let cell = tableView.dequeueReusableCell(withIdentifier: "inactive", for: indexPath) as! nonactiveTableViewCell
                    cell.configureCell(name: timerTitle, time: timerString)
                    return cell
                }
                else {
                    let cell = tableView.dequeueReusableCell(withIdentifier: "inactive", for: indexPath) as! nonactiveTableViewCell
                    cell.configureCell(name: timerTitle, time: timerString)
                    return cell
                } */

            }
            else {
                var i = 0
                activeTimersArray = []
                for result in results as! [NSManagedObject] {
                    if let activityIndicator = result.value(forKey: "activity") as? String {
                        if activityIndicator == "active" {
                            activeTimersArray.append(i)
                        }
                    }
                    i += 1
                }

                if indexPath.row < activeTimersArray.count {
                    let result = (results as! [NSManagedObject])[activeTimersArray[indexPath.row]]

                    var timerString = ""
                    var hourString = ""
                    var minuteString = ""
                    var secondString = ""
                    if let seconds = result.value(forKey: "timerSeconds") as? Int {
                        if let secondsLeftDouble = result.value(forKey: "timerProgress") as? Double {
                            let secondsLeft: Int = Int(round(secondsLeftDouble))
                            if seconds < 1 {
                            }
                            else if seconds >= 5 && seconds < 60 { // ss
                                timerString = "\(secondsLeft)s"
                            }
                            else if seconds >= 60 && seconds < 3600 { // mm:ss
                                let minute = secondsLeft / 60
                                minuteString = "\(minute)"
                                if minute < 10 {

                                    minuteString = "0\(minute)"

                                }
                                let second = secondsLeft % 60
                                var secondString: String = "\(second)"
                                if second < 10 {

                                    secondString = "0\(second)"

                                }
                                timerString = "\(minuteString):\(secondString)"

                            }
                            else if seconds >= 3600 && seconds <= 86400 { // hh:mm:ss
                                let hourString: String = "\(secondsLeft / 3600)"
                                let minute = (secondsLeft % 3600) / 60
                                var minuteString: String = "\(minute)"
                                if minute < 10 {

                                    minuteString = "0\(minute)"

                                }
                                let second = (secondsLeft % 3600) % 60
                                var secondString: String = "\(second)"
                                if second < 10 {

                                    secondString = "0\(second)"

                                }
                                timerString = "\(hourString):\(minuteString):\(secondString)"
                            }
                        }
                    }

                    var timerTitle = ""
                    if let title = result.value(forKey: "timerName") as? String {
                        timerTitle = title
                    }

                    let cell = tableView.dequeueReusableCell(withIdentifier: "active", for: indexPath) as! activeTableViewCell
                    cell.configureCell(name: timerTitle, time: timerString, activityIndicator: "active", indexPathToSend: indexPath.row)
                    return cell
                }
                else {
                    let cell = tableView.dequeueReusableCell(withIdentifier: "active", for: indexPath) as! activeTableViewCell
                    cell.configureCell(name: "Processing", time: "Processing", activityIndicator: "active", indexPathToSend: indexPath.row)
                    return cell
                }


            }

        }
        else {
            /* let cell = tableView.dequeueReusableCell(withIdentifier: "inactive", for: indexPath) as! nonactiveTableViewCell
            cell.configureCell(name: "Error", time: "Error")
            return cell */

            let cell = tableView.dequeueReusableCell(withIdentifier: "active", for: indexPath) as! activeTableViewCell
            cell.configureCell(name: "Error", time: "Error", activityIndicator: "none", indexPathToSend: indexPath.row)
            return cell
        }
    }
    catch {
        /* let cell = tableView.dequeueReusableCell(withIdentifier: "inactive", for: indexPath) as! nonactiveTableViewCell
        cell.configureCell(name: "Error", time: "Error")
        return cell */

        let cell = tableView.dequeueReusableCell(withIdentifier: "active", for: indexPath) as! activeTableViewCell
        cell.configureCell(name: "Error", time: "Error", activityIndicator: "none", indexPathToSend: indexPath.row)
        return cell
    }

}

Ignore any comments - they are just me trying to fix other problems.

Core Data saves records unordered for performance reasons.

If you want a specific order you need to apply an appropriate sort descriptor

let request = NSFetchRequest<NSFetchRequestResult>(entityName: "TimersData")
request.sortDescriptors = [NSSortDescriptor(key:"startDate", ascending: true)]

Replace startDate with the desired attribute name.

No comment about the questionable necessity to fetch Core Data objects 4 times a second and in each call of cellForRow 😉

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