在Swift中,self.tableView.reloadData()无效

59

我试图同时学习Swift和基础的iOS开发,所以请容忍我的问题。我有一个 TableViewController 首先解析本地的JSON文件,将其简单的数据呈现到TableViewCell和SectionHeaderViews中。在同一个TableViewController中,我调用了一个JSON终端点,它返回了一些数据,我将这些数据设置为变量,这样我就可以访问我实际想要获取的内容(API结构不理想)。最后,我将正确的数据设置为self.tableData,然后调用self.tableView.reloadData(),但什么也没有发生。出了什么问题?

import UIKit

class BusinessTableViewController: UITableViewController {

    var data: NSMutableData = NSMutableData()
    var tableData: NSArray = NSArray()

    @lazy var Business: NSArray = {
        let pathTCT = NSBundle.mainBundle().pathForResource("TCT", ofType: "json")
        let data = NSData.dataWithContentsOfFile(pathTCT, options: nil, error: nil)
        return NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as NSArray
        }()

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.titleView = UIImageView(image: UIImage(named: "growler"))

        tableView.registerClass(BeerTableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.separatorStyle = .None

        fetchKimono()
    }

    override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
//        return Business.count
        return 1
    }

    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        let biz = Business[section] as NSDictionary
        let results = biz["results"] as NSDictionary
        let beers = results["collection1"] as NSArray
        return beers.count
    }

    override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
        let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as BeerTableViewCell
        if let path = indexPath {
            let biz = Business[path.section] as NSDictionary
            let results = biz["results"] as NSDictionary
            let beers = results["collection1"] as NSArray
            let beer = beers[path.row] as NSDictionary

            cell.titleLabel.text = beer["BeerName"] as String
        }

        return cell
    }

    override func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String! {
        let biz = Business[section] as NSDictionary
        return biz["name"] as String
    }

    override func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
        let biz = Business[section] as NSDictionary
        let view = LocationHeaderView()
        view.titleLabel.text = (biz["name"] as String).uppercaseString
        return view
    }

    override func tableView(tableView: UITableView!, heightForHeaderInSection section: Int) -> CGFloat {
        return 45
    }

    func fetchKimono() {
        var urlPath = "names have been changed to protect the innocent"
        var url: NSURL = NSURL(string: urlPath)
        var request: NSURLRequest = NSURLRequest(URL: url)
        var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)

        connection.start()
    }

    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
        // Recieved a new request, clear out the data object
        self.data = NSMutableData()
    }

    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
        // Append the recieved chunk of data to our data object
        self.data.appendData(data)
    }

    func connectionDidFinishLoading(connection: NSURLConnection!) {
        // Request complete, self.data should now hold the resulting info
        // Convert the retrieved data in to an object through JSON deserialization
        var err: NSError
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options:    NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        var results: NSDictionary = jsonResult["results"] as NSDictionary
        var collection: NSArray = results["collection1"] as NSArray
        if jsonResult.count>0 && collection.count>0 {
            var results: NSArray = collection as NSArray
            self.tableData = results
            self.tableView.reloadData()
        }
    }
}

所以,我的第一个错误是Business实际上需要是一个NSDictionary,以匹配被获取的JSON。这样就可以摆脱所有的biz变量了。第二个错误是我不需要self.tableData,我需要Business = jsonResults - 但现在我得到了一个内存错误。我需要dealloc或deinitialize吗? - chandlervdw
请发布您的最新代码。 - Dash
@Dash 在下面添加了我的答案。 - chandlervdw
10个回答

157

你需要通过以下方式在UI线程上重新加载表格:

//swift 2.3
dispatch_async(dispatch_get_main_queue(), { () -> Void in
    self.tableView.reloadData()
})

//swift 5
DispatchQueue.main.async{
    self.tableView.reloadData()
}

跟进: 连接至服务器的更简单方法是使用NSURLConnection.sendAsynchronousRequest(...)而非connection.start()方法。

//NSOperationQueue.mainQueue() is the main thread
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: url), queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in
    //check error
    var jsonError: NSError?
    let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &jsonError)
    //check jsonError
    self.collectionView?.reloadData()
}

这样做不能灵活地跟踪字节,例如您可能希望通过bytesDownloaded/bytesNeeded计算下载进度。


它正在调用这段代码(我正在运行一个 println),但仍然没有重新加载数据? - Haring10
1
确保您的委托正确设置,并且您的委托方法确实被调用。 - noobular
@noobular 为什么需要 dispatch_async,我完全不理解。这个答案帮了我很多(我在 Google 上搜索了 2 小时)。 - Kyslik
1
@Kyslik 注意,当您调用connection.start()时,委托方法会在后台线程上调用,否则主UI线程将被阻塞,用户体验会变得很糟糕。dispatch_async部分告诉系统在main_queue即ui线程上执行reload。 - noobular
1
另外,不要忘记通过在故事板上使用ctrl+单击TableView并将其拖动到视图顶部的小黄色方块并选择“dataSource”来使ViewController成为TableView的数据源。 - SMKS

21

您只需要输入:

首先是一个 IBOutlet:

@IBOutlet var appsTableView : UITableView

然后在一个动作函数中:

self.appsTableView.reloadData()

4

如果您的连接在后台线程中,则应按以下方式在主线程中更新UI:

self.tblMainTable.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)

如我在这里所提到的

Swift 4:


作为我在这里提到的替代方案
self.tblMainTable.performSelector(onMainThread: #selector(UICollectionView.reloadData), with: nil, waitUntilDone: true)

这个方法对我有帮助。为什么 self.collectionView?.reloadData() 对我没有作用?我在使用 NSURLSession。 - JJPP

2

在我的情况下,表格已经正确更新,但是图像的setNeedDisplay()方法并没有被调用,所以我错误地认为数据没有重新加载。


1
除了显而易见的从UI/主线程(不管苹果如何称呼)重新加载数据之外,在我的情况下,我还忘记更新“SECTIONS”信息。因此,它没有检测到任何新的部分!

1
因此,问题在于我试图不适当地使用@lazy,这导致我的Business变量实质上是一个常量,因此无法编辑。另外,我现在只加载从API返回的数据,而不是加载本地json。
import UIKit

class BusinessTableViewController: UITableViewController {

    var data: NSMutableData = NSMutableData()
    var Business: NSMutableArray = NSMutableArray()

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.titleView = UIImageView(image: UIImage(named: "growler"))

        tableView.registerClass(BeerTableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.separatorStyle = .None

        fetchKimono()
    }

    override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
        return Business.count
    }

    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        if (Business.count > 0) {
            let biz = Business[section] as NSDictionary
            let beers = biz["results"] as NSArray
            return beers.count
        } else {
            return 0;
        }
    }

    override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
        let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as BeerTableViewCell
        if let path = indexPath {
            let biz = Business[path.section] as NSDictionary
            let beers = biz["results"] as NSArray
            let beer = beers[path.row] as NSDictionary

            cell.titleLabel.text = beer["BeerName"] as String
        } else {
            cell.titleLabel.text = "Loading"
        }

        return cell
    }

    override func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
        let view = LocationHeaderView()
        let biz = Business[section] as NSDictionary
        if (Business.count > 0) {
            let count = "\(Business.count)"
            view.titleLabel.text = (biz["name"] as String).uppercaseString
        }
        return view
    }

    override func tableView(tableView: UITableView!, heightForHeaderInSection section: Int) -> CGFloat {
        return 45
    }

    func fetchKimono() {
        var urlPath = "names have been removed to protect the innocent"
        var url: NSURL = NSURL(string: urlPath)
        var request: NSURLRequest = NSURLRequest(URL: url)
        var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)

        connection.start()
    }

    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
        // Recieved a new request, clear out the data object
        self.data = NSMutableData()
    }

    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
        // Append the recieved chunk of data to our data object
        self.data.appendData(data)
    }

    func connectionDidFinishLoading(connection: NSURLConnection!) {
        // Request complete, self.data should now hold the resulting info
        // Convert the retrieved data in to an object through JSON deserialization
        var err: NSError
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        var results: NSDictionary = jsonResult["results"] as NSDictionary
        var collection: NSArray = results["collection1"] as NSArray
        if jsonResult.count>0 && collection.count>0 {
            Business = jsonResult
            tableView.reloadData()
        }
    }
}

@lazy的Swift文档

你必须始终将懒加载属性声明为变量(使用var关键字),因为其初始值可能要在实例初始化完成后才能检索到。常量属性必须在初始化完成之前始终具有值,因此不能声明为懒加载。


0
所有对UI的调用都应该是异步的,任何你在UI上做出的更改,比如更新表格或更改文本标签,都应该从主线程中完成。 使用DispatchQueue.main将会把你的操作添加到主线程的队列中。
Swift 4
DispatchQueue.main.async{
    self.tableView.reloadData()
}

0
尝试一下:tableView.reloadSections(IndexSet(integersIn: 0...0), with: .automatic) 它对我很有帮助。

0

在编程中,你必须仅在主线程中重新加载TableView。否则你的应用程序可能会崩溃或者需要一些时间才能更新。对于每个UI更新,推荐使用主线程。

//To update UI only this below code is enough
//If you want to do changes in UI use this
DispatchQueue.main.async(execute: {
    //Update UI
    self.tableView.reloadData()//Your tableView here
})

//Perform some task and update UI immediately.
DispatchQueue.global(qos: .userInitiated).async {  
    // Call your function here
    DispatchQueue.main.async {  
        // Update UI
        self.tableView.reloadData()  
    }
}

//To call or execute function after some time and update UI
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    //Here call your function
    //If you want to do changes in UI use this
    DispatchQueue.main.async(execute: {
        //Update UI
        self.tableView.reloadData()
    })
}

-3
我也遇到了同样的问题,我的错误是忘记添加


tableView.delegate = self    
tableView.dataSource = self

在viewDidLoad() {}方法中,这可能是self.tableView.reloadData()无法工作的原因之一。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接