TableView reloadData() не показывает все данные, хотя массив со значениями заполнен (быстро)

У меня есть табличное представление, которое я заполняю результатами http-вызова. Данные передаются в массив с именем tripTimes. triptimes содержит информацию об определенных поездках, совершенных в течение данного дня. Время начала каждой поездки в этот день передается в self.tableData и вставляется в tableView.

После этого перезагружаю таблицу и... странные результаты.

Массив tableData заполняется правильно, но после того, как я вызываю self.tableView.reloadData(), почти все ячейки заполняются правильно. Однако первая ячейка остается пустой, пока я не коснусь ее или не подожду около 5 секунд, а последняя ячейка также будет пустой, но через то же время она получит то же значение, что и первая ячейка.

Я думаю, что это должно что-то делать с частью dispatch_async, но я новичок в Swift и iOS, поэтому понятия не имею, где искать. Когда я печатаю содержимое self.tableData внутри функции dispatch_async, массив содержит правильные значения.

Ниже приведен код моего viewController

import UIKit
import Foundation
import SwiftHTTP
class TripSelect: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var dateTextField: UITextField!
    @IBOutlet weak var tableView: UITableView!

    @IBAction func showTrips(sender: AnyObject) {

        //if data is already present in tableData, empty it first
        if self.tripTimes.count != 0 && self.tableData.count != 0 {
            for var i=0; i<self.tripTimes.count; i++ {
                self.tableData.removeObjectAtIndex(0)
                let indexPathToRemove = NSIndexPath(forRow: 0, inSection: 0)
                self.tableView.deleteRowsAtIndexPaths([indexPathToRemove], withRowAnimation: .Automatic)
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.tableView.reloadData()
            })
        }
        var request = HTTPTask()
        request.GET("<MY_URL>", parameters: ["date": dateTextField.text], success: {(response: HTTPResponse) in
            if let data = response.responseObject as? NSData {

                let str = NSString(data: data, encoding: NSUTF8StringEncoding) as String
                let str2 = "\"not enough data for this day\""

                if str != str2 {
                    let json:AnyObject = JSON.parse(str)

                    self.dayLatLongs = json["dayLatLongs"] as Array
                    self.tripTimes = json["tripTimes"] as Array

                    var tripLatLongs:[AnyObject] = self.dayLatLongs[0] as Array
                    var firstTrip:[AnyObject] = tripLatLongs[0] as Array
                    var tripStartTime:String = ""
                    for var i=0; i<self.tripTimes.count; i++ {
                        tripStartTime = self.tripTimes[i]["startTime"] as String
                        //println("\(tripStartTime)")
                        self.tableData.addObject(tripStartTime)
                        let indexPath = NSIndexPath(forRow: 0, inSection: 0)
                        self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
                    }
                    dispatch_async(dispatch_get_main_queue(), {
                        println(self.tableData)
                        self.tableView.reloadData()
                    })
                } else {println(str)}
            }
            },failure: {(error: NSError, response: HTTPResponse?) in
                println("error: \(error)")
        })
    }

    var dayLatLongs:[AnyObject] = []
    var tripTimes:[AnyObject] = []
    var tableData: NSMutableArray! = NSMutableArray()

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.tableData.count
    }
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
        cell.textLabel?.text = self.tableData.objectAtIndex(indexPath.row) as? String
        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        var backgroundView = UIView(frame: CGRectZero)
        self.tableView.tableFooterView = backgroundView
        self.tableView.backgroundColor = UIColor.clearColor()
    }

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


}

ОБНОВИТЬ:

var indexPaths:[NSIndexPath] = [NSIndexPath]()
for var i=0; i<self.tripTimes.count; i++ {
    tripStartTime = self.tripTimes[i]["startTime"] as String
    self.tableData.addObject(tripStartTime)
    indexPaths.append(NSIndexPath(forRow: i, inSection: 0))
}
self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)

Теперь результаты отображаются правильно, но только после того, как я пару раз "перетащу" таблицу.


person Eric Nijman    schedule 12.02.2015    source источник
comment
Блок success, скорее всего, все равно работает в основном потоке, поскольку блок showTrips, похоже, вызывается в основном потоке. Не нужно использовать reloadData после insertRowsAtIndexPaths и deleteRowsAtIndexPaths, но нужно убедиться, что ячейки вставляются и удаляются с правильными индексами и только в основном потоке: попробуйте заполнить массив со всеми индексами и сделать одиночную вставку/ удалить вызов, чтобы сделать процесс проще.   -  person A-Live    schedule 12.02.2015
comment
Хорошо, я попробовал что-то еще (обновил свой вопрос), поэтому теперь значения отображаются правильно, но только после того, как я пару раз «перетащу» таблицу.   -  person Eric Nijman    schedule 12.02.2015
comment
Попробуйте сделать то же самое для методов удаления, что и для потоков: вы, кажется, гарантируете, что только вызовы reloadData будут в основном потоке, сделайте то же самое для вызовов вставки/удаления: они очень похожи на reloadData в этом смысле и должен всегда вызываться в основном потоке. В этом случае вызовы reloadData могут быть удалены, так как таблица обновляется автоматически, и вы, вероятно, испортите анимацию.   -  person A-Live    schedule 12.02.2015
comment
Большое спасибо! Это решило проблему :) Анимация теперь отображается правильно, очень приятно! Я напишу ответ, чтобы его было легче найти.   -  person Eric Nijman    schedule 12.02.2015


Ответы (2)


Ваша проблема в первом предложении if:

 if self.tripTimes.count != 0 && self.tableData.count != 0 {
            for var i=0; i<self.tripTimes.count; i++ {
                self.tableData.removeObjectAtIndex(0)
                let indexPathToRemove = NSIndexPath(forRow: 0, inSection: 0)
                self.tableView.deleteRowsAtIndexPaths([indexPathToRemove], withRowAnimation: .Automatic)
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.tableView.reloadData()
            })
        }

Вы всегда удаляете только значение по индексу 0 и не используете i. Так что замените все жестко заданные 0 своим индексом i:

 if self.tripTimes.count != 0 && self.tableData.count != 0 {
            for var i=0; i<self.tripTimes.count; i++ {
                self.tableData.removeObjectAtIndex(i)
                let indexPathToRemove = NSIndexPath(forRow: i, inSection: 0)
                self.tableView.deleteRowsAtIndexPaths([indexPathToRemove], withRowAnimation: .Automatic)
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.tableView.reloadData()
            })
        }
person Christian    schedule 12.02.2015
comment
Спасибо за быстрый ответ, хотя проблема не в этом. Когда я заполняю таблицу в первый раз, программа не выполняет эту часть кода, потому что self.tripTimes.count и self.tableData.count в этот момент равны 0 (я проверял с помощью println) - person Eric Nijman; 12.02.2015

Вся заслуга в этом ответе принадлежит @A-Live.

Обновлено с предлагаемыми изменениями:

Чтобы удалить ячейки, я изменил функцию на

dispatch_async(dispatch_get_main_queue(), {
    if self.tripTimes.count != 0 && self.tableData.count != 0 {
        self.tableData = []
        self.tableView.deleteRowsAtIndexPaths(self.indexPaths, withRowAnimation: .Automatic)
        self.indexPaths = []
    }
})

Чтобы добавить ячейки, я изменил функцию на

dispatch_async(dispatch_get_main_queue(), {
    for var i=0; i<self.tripTimes.count; i++ {
        tripStartTime = self.tripTimes[i]["startTime"] as String
        self.tableData.addObject(tripStartTime)
        self.indexPaths.append(NSIndexPath(forRow: i, inSection: 0))
    }
    self.tableView.insertRowsAtIndexPaths(self.indexPaths, withRowAnimation: .Automatic)
})
person Eric Nijman    schedule 12.02.2015
comment
Рад, что это сработало, последний штрих: убедитесь, что вы изменяете источник данных в том же потоке, чтобы сделать все операции обновления потокобезопасными. В этом случае переместите цикл и tableData = [] внутрь блоков GCD, которые у вас уже есть. В противном случае возможно, что последующие вызовы showTrips попытаются изменить один и тот же массив (в целом источник данных) в (возможно) разных отдельных потоках. - person A-Live; 12.02.2015