嵌入在容器中的 UITableView 不刷新

Posted

技术标签:

【中文标题】嵌入在容器中的 UITableView 不刷新【英文标题】:UITableView embedded in container not refreshing 【发布时间】:2016-02-23 23:04:01 【问题描述】:

简而言之,我想确定用户的位置,从源中获取一些 JSON 数据(这需要首先知道位置),然后根据返回的数据创建一个表。

我在 Xcode 7 中有一个使用 Swift 的自定义 UITableView 和一个原型单元。当这个表视图是初始视图时,它加载得很好。

现在,我正在尝试 1) 将其嵌入到另一个 UIView 的容器中,并且 2) 在其数据更改时更新它。两者都不起作用......容器中的表格永远不会出现。我在这上面花了将近一个星期的时间,现在有点疯狂(这是我的第一个 ios 应用)。

我注意到的一些事情:

    reloadData、setNeedsDisplay 和 setNeedsLayout 似乎没有任何作用。 cellForRowAtIndexPath 未被调用,但 numberOfRowsInSection 被调用。 我尝试从父 UIView 和 TableViewController 调用项目符号 1 中列出的所有函数。

下面是代码当前的样子: 视图控制器

import UIKit
import MapKit
import CoreLocation
var currentLatLong: String = ""



class ViewController: UIViewController, CLLocationManagerDelegate  
    // global var for location
    let locationManager = CLLocationManager()
    var currentLocation = CLLocation()
    var lastLatLong: String = ""

    override func viewDidLoad() 
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // get permission and user's location
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        if CLLocationManager.locationServicesEnabled() 
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
            locationManager.startUpdatingLocation() 
         else 
            print("Location services are not enabled")
        
    

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

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
        if let location = locations.last 
            currentLocation = locations.last!
            currentLatLong = "\(currentLocation.coordinate.latitude)%2C\(currentLocation.coordinate.longitude)"
            if lastLatLong != currentLatLong 
                lastLatLong = currentLatLong
                NSNotificationCenter.defaultCenter().postNotificationName("updateOverview", object: nil)
                self.view.setNeedsDisplay()
                self.view.setNeedsLayout()
                print("Found user's location: \(location)")

            
            //print("Found user's location: \(location)")
        
    

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) 
             print("Failed to find user's location: \(error.localizedDescription)")
        
    

UITableViewController

import UIKit
import MapKit
import CoreLocation

class OverviewTableViewController: UITableViewController 

    var LocRepositories = [LocationJSON]()
    var TimesRepositories = [TimesJSON]()
    var retrieved = [dataModelDefn]()

    override func viewDidLoad() 
        super.viewDidLoad()


        NSNotificationCenter.defaultCenter().removeObserver(self, name: "updateOverview", object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "checkRes", name: "updateOverview", object: nil)


        // fetch location data

        let backgroundQueue:dispatch_queue_t = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(backgroundQueue, 
            self.getLocations()
            dispatch_async(dispatch_get_main_queue(), self.tableView.reloadData())
        )
    

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

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int 
            return 1
    

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
            return retrieved.count
        



    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 

        // initialize data & cell
        var cell = tableView.dequeueReusableCellWithIdentifier("EDOverviewCell", forIndexPath: indexPath) as! OverviewTableCell
        var rowData = retrieved[indexPath.row]


        /* update cell */

        // border for cell
        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
        self.tableView.separatorColor = UIColor(red:(0/255.0), green:(128/255.0), blue:(128/255.0), alpha:1)


        // alternating row colors
        if indexPath.row % 2 == 0 
            cell.backgroundColor = UIColor(red:(249/255.0), green:(238/255.0), blue:(188/255.0), alpha:0.5)
        
        else
            cell.backgroundColor = UIColor(red:(252/255.0), green:(244/255.0), blue:(220/255.0), alpha:0.5)
        

        return cell
    

    func checkRes()
    
        // fetch new data because location available
        getLocations()
        //self.tableView.reloadData()
        //dispatch_async(dispatch_get_main_queue(), self.tableView.reloadData() )
    

    func getLocations()
         // fetch demographic/location info

        // abort call if no location
        if currentLatLong == "" 
            return
        

        /* get JSON data */
    

以下是我试图模仿解决方案的一些相关问题: Loading data after one second in uitableview. Any alternate way to eliminate this delay

How to stop UITableView from loading until data has been fetched?

UITableView doesn't populate when waiting for CLLocationManagerDelegate 's didUpdateToLocation method on iPhone SDK

这篇文章很长,但如果我遗漏了任何必要的细节,请告诉我。感谢您的帮助。

【问题讨论】:

在 getLocations() 中 currentLatLong 是否曾经是一个非空字符串?当您在新视图中嵌入 uitableview 时,通常不会保留整个 uitableviewcontroller。您只需将 tableview 设置为第一个视图控制器的子视图,并根据需要实现 UITableViewDataSource 和 UITableViewDelegate。 试试Xcode View Debugger看看tableview在哪里。也许有一个约束迫使它离开屏幕。另外,从 numberOfRowsInSection 返回的值是否不是 0? @Clever - 我什至不知道它的存在。一旦我让表中的数据出现但它更小并且完全偏离目标,它确实有助于解决一个问题。这是一个超级有用的实用程序。 【参考方案1】:

您目前有以下代码:

    let backgroundQueue:dispatch_queue_t = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(backgroundQueue, 
        self.getLocations()
        dispatch_async(dispatch_get_main_queue(), self.tableView.reloadData())
    )

这将在后台线程中运行getLocations,然后在主线程上刷新表tableView。都好。然而,getLocations 本身做了一个 JSON 调用(目前只是一个注释),这无疑也是在另一个后台线程上运行的。因此,getLocations 将几乎立即返回,远在 JSON 完成之前。

您应该将完成处理程序传递给getLocations,当 JSON 完成时(在其完成处理程序中),调用您自己的完成处理程序。大致如下:

    let backgroundQueue:dispatch_queue_t = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(backgroundQueue, 
        self.getLocations(completion: 
            dispatch_async(dispatch_get_main_queue(), 
                self.tableView.reloadData()
            )
        )
    )

(我这里的大括号可能不匹配)。那么getLocations定义为:

func getLocations(completion completion: (() -> Void))
     // fetch demographic/location info

    // abort call if no location
    if currentLatLong == "" 
        return
    

    getTheJsonAndInItsCompletion(... success: 
        completion()
    )

实际的 JSON 实现取决于您使用的库。

【讨论】:

感谢@Michael 的解决方案以及回调地狱的简短介绍。自从您发布答案以来,我一直在阅读它。我没有为 JSON 使用 3rd-party 库,只是 Swift。但是该 JSON 调用中有另一个 JSON 调用,以及一个 ETA(MapKit 框架)调用。不知道如何正常处理,但一场噩梦。

以上是关于嵌入在容器中的 UITableView 不刷新的主要内容,如果未能解决你的问题,请参考以下文章

Swift:使用 prepareForSegue 将 UIView 容器的高度设置为嵌入式 UITableView 的高度

iOS开发UI篇—实现UITableview控件数据刷新

将 UITableView 作为容器视图嵌入到 UIViewController 中

iOS开发UI篇—实现UItableview控件数据刷新

静态 uitableview 中的 uitextfield 不会在容器视图中更新

在 UIView 内对 UITableView 设置约束