【问题标题】:How to reload tableView data after data is passed by a SegueSegue传递数据后如何重新加载tableView数据
【发布时间】:2016-10-26 07:57:46
【问题描述】:

我有两个表视图。一个是用户点击的,一个是显示数据的。当用户单击第一个表视图中的单元格时,将对我的 firebase 数据库进行查询,并将查询存储在数组中。然后我通过 segue 传递数据。我使用了一个属性观察器,所以我知道正在设置变量。通过使用断点,我能够确定我的变量在 cellForRowAtIndexPath 方法之前获得了它的值。我需要帮助在我的表格视图中显示数据。我不知道在哪里重新加载数据以使用我的数据更新表格视图。我正在使用 Swift。

编辑 2:我已经解决了我的问题。我将发布我的第一个和第二个表格视图,以便您查看我的解决方案。

第一个表视图

import UIKit
import Firebase
import FirebaseDatabase

class GenreTableViewController: UITableViewController {

let dataBase = FIRDatabase.database()

var genreArray = ["Drama","Classic,Comic/Graphic novel","Crime/Detective","Fable,Fairy tale","Fantasy","Fiction narrative", "Fiction in verse","Folklore","Historical fiction","Horror","Humour","Legend","Magical realism","Metafiction","Mystery","Mythology","Mythopoeia","Realistic fiction","Science fiction","Short story","Suspense/Thriller","Tall tale","Western,Biography","Autobiography","Essay","Narrative nonfiction/Personal narrative","Memoir","Speech","Textbook","Reference book","Self-help book","Journalism", "Religon"]

var ResultArray: [NSObject] = []
var infoArray:[AnyObject] = [] 

override func viewDidLoad() {
    super.viewDidLoad()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return genreArray.count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)

    // Configure the cell...

    cell.textLabel?.text = genreArray[indexPath.row]

    return cell
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

    let DestViewController: ResultTableViewController = segue.destinationViewController as! ResultTableViewController

    if segue.identifier == "letsGo" {
        if let indexPath = self.tableView.indexPathForSelectedRow {
            let tappedItem = self.genreArray[indexPath.row]
            DestViewController.someString = tappedItem 
        }  
    }
}

}

import UIKit
import Firebase
import FirebaseDatabase

class ResultTableViewController: UITableViewController {


let dataBase = FIRDatabase.database()
var SecondResultArray: [FIRDataSnapshot]! = []
var someString: String?{
    didSet {
      print("I AM A LARGE TEXT")
      print(someString)
    }
}

override func viewDidLoad() {

    let bookRef = dataBase.reference().child("books")

    bookRef.queryOrderedByChild("Genre")
        .queryEqualToValue(someString)
        .observeSingleEventOfType(.Value, withBlock:{ snapshot in
            for child in snapshot.children {

                self.SecondResultArray.append(child as! FIRDataSnapshot)
                //print(self.ResultArray)
            }

            dispatch_async(dispatch_get_main_queue()) {
                self.tableView.reloadData()
            }

        })

    super.viewDidLoad()

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem()
}

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

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return SecondResultArray.count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath)

    // Configure the cell...

    let bookSnapShot: FIRDataSnapshot! = self.SecondResultArray[indexPath.row]

    let book = bookSnapShot.value as! Dictionary<String, String>

    let Author = book["Author"] as String!
    let Comment = book["Comment"] as String!
    let Genre = book["Genre"] as String!
    let User = book["User"] as String!
    let title = book["title"] as String!

    cell.textLabel?.numberOfLines = 0
    cell.textLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping

    cell.textLabel?.text = "Author: " + Author + "\n" + "Comment: " + Comment + "\n" + "Genre: " + Genre + "\n" + "User: " + User + "\n" +  "Title: " + title

    let photoUrl = book["bookPhoto"], url = NSURL(string:photoUrl!), data = NSData(contentsOfURL: url!)
        cell.imageView?.image = UIImage(data: data!)

    return cell
}

}

为了更好的上下文和故障排除,这里是我当前应该显示数据的 tableView 代码:

    import UIKit

    class ResultTableViewController: UITableViewController {

        var SecondResultArray: Array<NSObject> = []{
            willSet(newVal){ 
                print("The old value was \(SecondResultArray) and the new value is \(newVal)")
            }
            didSet(oldVal){
               print("The old value was \(oldVal) and the new value is \(SecondResultArray)")
               self.tableView.reloadData()        
            }
        }
        override func viewDidLoad() {
            print ("I have this many elements\(SecondResultArray.count)")
            super.viewDidLoad()
        }
        // MARK: - Table view data source
        override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
            // #warning Incomplete implementation, return the number of sections
            return 1
        }

        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            // #warning Incomplete implementation, return the number of rows
            return SecondResultArray.count
        }

        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath)
            cell.textLabel?.text = SecondResultArray[indexPath.row] as? String
            return cell
        }
    }

编辑:

这是我的第一个表格视图控制器。我曾尝试使用完成处理程序,但我无法正确调用它,而且我的查询发生在 didSelectRowAtIndexPath 方法中这一事实使我受到限制。请帮忙。

import UIKit
import Firebase
import FirebaseDatabase

class GenreTableViewController: UITableViewController {


    let dataBase = FIRDatabase.database()

    var genreArray = ["Drama","Classic,Comic/Graphic novel","Crime/Detective","Fable,Fairy tale","Fantasy","Fiction narrative", "Fiction in verse","Folklore","Historical fiction","Horror","Humour","Legend","Magical realism","Metafiction","Mystery","Mythology","Mythopoeia","Realistic fiction","Science fiction","Short story","Suspense/Thriller","Tall tale","Western,Biography","Autobiography","Essay","Narrative nonfiction/Personal narrative","Memoir","Speech","Textbook","Reference book","Self-help book","Journalism", "Religon"]

    var ResultArray: [NSObject] = []
    var infoArray:[AnyObject] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
       // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }

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

   // MARK: - Table view data source

   override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return genreArray.count
}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)

    cell.textLabel?.text = genreArray[indexPath.row]
    return cell
}


override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    typealias CompletionHandler = (result:NSObject?, error: NSError?) -> Void

    func getData(completionHandeler: CompletionHandler){
        let bookRef = self.dataBase.reference().child("books")
        let GenreSelector = self.genreArray[indexPath.row]
        bookRef.queryOrderedByChild("Genre")
            .queryEqualToValue(GenreSelector)
            .observeSingleEventOfType(.Value, withBlock:{ snapshot in
                for child in snapshot.children {
                    print("Loading group \((child.key!))")

                    self.ResultArray.append(child as! NSObject)
                }
                print(self.ResultArray)

                self.performSegueWithIdentifier("letsGo", sender: self)
                self.tableView.reloadData()
            })
    }
}


override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var DestViewController: ResultTableViewController = segue.destinationViewController as! ResultTableViewController
    DestViewController.SecondResultArray = self.ResultArray
}

【问题讨论】:

  • 属性观察者可以调用reloadData
  • 我认为更多数据将有助于澄清答案。您是否在单个 viewController 中有两个 tableView,或者您是否有两个 viewController,每个 viewController 中都有一个 tableView?我问是因为您的问题是您的第一个 ViewController,但它被定义为 UITableViewController。此外,如果数据被正确传递,您可能不需要观察者。您是否在某处使用了代表对象变量?
  • 我有两个 tableview,每个都有自己的 viewController(在这种情况下,每个都是 UITableViewController。)我不确定表示的 Object 变量是什么。如果这就是你的意思,我的名为 ResultArray 的变量的类型是 NSObject。
  • @Jay 这次我一直解决这个问题,结果是可重复的。我将发布代码。
  • @TaylorSimpson 现在您的代码已发布,我知道问题出在哪里。首先,在使用 Firebase 时避免使用 dispatch_async(dispatch_get_main_queue())。让 Firebase 以自己的方式进行异步调用,并在这些调用返回时呈现您的 UI。但是,问题是:让 DestViewController: ResultTableViewController = 在您的第一个控制器中。发生的事情是 DestViewController 在该函数结束时超出范围!您需要在类级别创建控制器变量,这样它们就不会超出范围。我为更多数据添加了另一个答案。

标签: ios swift uitableview firebase


【解决方案1】:

您可以在prepareForSegue 的第一个UIViewController 方法中将数据注入目标viewController 并在viewDidAppear 中重新加载您的UITableView。如果您要异步获取数据,请使用 completionHandler 并将其重新加载到 completionHandler 中。这是一个例子。

  func fetchDataWithCompletion(response: (NSDictionary?, error:NSError?)-> Void) -> Void {
    //make the API call here 
    }

【讨论】:

  • 我会在 prepareForSegue 方法中调用完成处理程序吗?
  • 是的。用户期望的是,当您在第一个屏幕上选择一个单元格时,您已经在第二个屏幕上。所以把他带到第二个屏幕,显示一个加载指示器,在你的第二个 ViewController 的 viewDidLoad 中,你会获取所需的数据,并在完成回调时隐藏加载指示器
  • 很抱歉,我无法正确处理。你能给我一个更深入的例子吗?
【解决方案2】:

这个怎么样:

假设您有一个从 Firebase 填充并存储在第一个 tableViewController 中的数组 (myArray)。还有第二个 tableViewController 和一个连接它们的 segue。

我们希望能够点击第一个 tableviewController 中的项目,让应用从 Firebase(“数据”节点)检索项目的详细数据,并在第二个 tableViewController 中显示详细数据。

Firebase 结构

some_node
   child_node_0
     data: some detailed data about child_node_0
   child_node_1
     data: some detailed data about child_node_1

第二个tableViewContoller内:

var passedObject: AnyObject? {
    didSet {
      self.configView() // Update the view.
     }
}

点击第一个 tableView 中的项目调用以下函数

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if segue.identifier == "showListInSecondTable" {
          if let indexPath = self.tableView.indexPathForSelectedRow {
              let tappedItem = myArray[indexPath.row] as! String
              let keyOfTappedItem = tappedItem.firebaseKey //child_node_0 for example
              doFirebase(keyOfTappedItem)
            }
        }
    }

prepareForSegue 然后调用以下命令,从 firebase 加载数据,当快照在块内返回时,它会在第二个 tableView 中填充 passObject 属性

func doFirebase(firebaseKey: String) {

  ref = myRootRef.childByAppendingPath("\(firebaseKey)/data")
  //if we want the detailed data for child_node_0 this would resolve
  //  to    rootRef/child_node_0/data
  ref.observeSingleEventOfType(.Value, { snapshot in
     let detailObjectToPass = snapshot.Value["data"] as! NSArray or string etc

     let controller = (segue.destinationViewController as! UINavigationController).myViewController as! SecondViewController
     controller.passedObject = detailObjectToPass
}

当然在 secondController 中,设置passedArray 调用didSet 并设置视图,并告诉tableView 重新加载自身,显示传递的数组。

func configView() {
   //set up the view and buttons
   self.reloadData()
}

我做得非常快,所以请忽略错别字。模式是正确的并且满足问题。 (并且无需观察者启动!)

附:这有点过度编码,但我想演示流程并在数据在块内有效时利用对 firebase 的异步调用来加载第二个 tableView。

【讨论】:

  • 一件小事:您需要确保在prepareForSegue 内定义func doFirebase(tappedItem:String) 或将sender(或直接SecondViewController)作为参数。否则它不会很好的 segue 并且无法访问第二个控制器。如果 segue 设置正确,您也可以执行let controller = segue.destinationViewController as! SecondViewController,无需查看导航控制器(实际上这可能根本不起作用)。
  • 应该是let controller = (segue.destinationViewController as! UINavigationController).myViewController as! SecondViewController controller.passedArray = arrayToPass 而不是let controller = (segue.destinationViewController as! UINavigationController).myViewController as! SecondViewController controller.passedArray = passedArray 吗?
  • 我也对逻辑有点困惑。根据您的描述,myArray 是存储来自 firebase 的数据的数组,但在函数中,您拥有它存储来自被点击项目的数据,这将是来自genreArray 的项目。我还尝试让 arrayToPass 只尝试您的代码,并且在解包时它一直返回 nil 。感谢您到目前为止的帮助。我得到了流程,但到目前为止实施还没有成功:(
  • @TaylorSimpson 是的,只是一个错字。我添加了更多信息并稍微更新了代码以使其更通用。这个想法是,当一个项目被点击时,您从 Firebase 获取项目详细信息,然后将该数据分配给第二个控制器中的传递对象属性,以便 tableView 可以访问它。一旦完成分配,数据就有效,并且可以重新加载 tableView 以显示数据。仍然存在拼写错误,但数据流是重要的部分。
  • FirebaseKey 会是我要访问的节点吗?
【解决方案3】:

尝试更新您的闭包以包含以下内容:

dispatch_async(dispatch_get_main_queue()) {
    self.tableView.reloadData()
}

【讨论】:

  • 我更新了它,但我不确定如何调用该函数。它不断要求内部的参数。
  • 紧跟在self.performSegueWithIdentifier("letsGo", sender: self)之后
  • 对不起,我没说清楚,我的意思是我不知道如何正确调用闭包。
【解决方案4】:

编辑:

在第二次阅读时,您正在已经在使用完成处理程序,但我认为您没有看到它。让我稍微更正一下您的代码:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {


    let bookRef = self.dataBase.reference().child("books")
    let GenreSelector = self.genreArray[indexPath.row]
    bookRef.queryOrderedByChild("Genre")
        .queryEqualToValue(GenreSelector)
        .observeSingleEventOfType(.Value, withBlock:{ snapshot in
            // This here is your completion handler code!
            // I assume it is called asynchronously once your DB is done
            for child in snapshot.children {
                print("Loading group \((child.key!))")

                self.ResultArray.append(child as! NSObject)
            }
            print(self.ResultArray)

            self.performSegueWithIdentifier("letsGo", sender: self)
            // self.tableView.reloadData() // is this really needed
        })
    }
}

您定义了一个闭包,但根本没有调用它。无论如何,我看不出有什么原因,假设一旦数据库为您提供结果,该块就会被调用。我错过了什么吗?


这已经是一个好的开始,但我认为您在这方面还没有完全了解如何使用完成处理程序,但我当然可能错了。

我建立在user3861282 的答案之上,并在my github 创建了一个小型演示项目。

简而言之:您可以在您的第一个表视图控制器的prepareForSegue: 方法中进行所有表间通信。在那里配置第二个表视图控制器(通过它的变量)。那里还没有任何闭包/完成处理程序。

然后在第二个视图控制器的viewWillAppear: 方法中,开始加载(如果需要,包括动画)。我建议像NSURLSession 这样已经定义了一个完成处理程序。在您使用远程数据时,停止任何加载动画就可以了。

如果完成处理程序必须在第一个表视图控制器中定义,您甚至可以在第二个表视图控制器中将其设置为 var。这样你就可以“移交”闭包,即“一段代码”。

或者,您可以在第一个表视图控制器中启动动画和远程请求,然后在完成后performSegueWithIdentifier。在您的问题中,您写道您想加载第二个表格视图控制器,但是,如果我理解正确的话。


您上面的代码正确地定义了一个需要完成处理程序的闭包(这也是一个闭包,因此您想要的东西加倍),但您实际上从未在某个地方调用它。您也不会在闭包中调用完成处理程序。请参阅我的演示,了解它是如何工作的。

我写的项目只说明了一种方法(减去动画,没有足够的时间)。它还展示了如何定义自己的函数来期待完成处理程序,但正如我所说,框架中的标准远程连接无论如何都提供了一个。

【讨论】:

  • 稍后我会查看您的 git hub 帖子。回答您的编辑:我认为当我的数据库完成时会调用该块。我遇到的问题是将信息传递到另一个表视图,以便用户可以看到查询的结果。
  • 好的,虽然我的例子可能不像我最初想的那样适用。
  • (该死,回车太快了)我认为你上面的 DB 调用没有被调用,不过:typealias CompletionHandler = (result:NSObject?, error: NSError?) -&gt; Void 只是定义了闭包/块/completionHandler 的类型。你实际上不需要那个。 ` func getData(completionHandeler: CompletionHandler){ ... }` 定义了一个闭包。您缺少 getData { (result, error) in /* your lines of code for the handler go here /* } 来实际调用它。基本上,您定义了一个函数(只是在另一个函数中,这就是块/闭包),但您从未真正调用它。
【解决方案5】:

根据添加到帖子中的其他代码,问题是控制器变量超出范围。

问题来了

class MyClass {

  func setUpVars {
    let x = 1
  }

  func doStuff {
    print(x)
  }
}

创建一个类并尝试打印 x 的值

let aClass = MyClass()
aClass.setUpVars
aClass.doStuff

这将不会打印任何内容(从概念上讲),因为一旦 setUpVars 结束,“x”变量就会超出范围。

class MyClass {

  var x: Int

  func setUpVars {
    x = 1
  }

  func doStuff {
    print(x)
  }
}

将打印 x 的值,1。

因此,真正的解决方案是您的 viewController 需要在您的课程(或应用程序)期间“保持活跃”。

这是模式。在 MasterViewController 中

import UIKit

    class MasterViewController: UITableViewController {

        var detailViewController: DetailViewController? = nil

然后在您的 MasterViewController viewDidLoad(或任何地方)中,创建 detailViewController

 override func viewDidLoad() {
    super.viewDidLoad()
    let controllers = split.viewControllers //this is from a splitViewController
    self.detailViewController = 
        controllers[controllers.count-1].topViewController as? DetailViewController
 }

从那里你有它......使用 prepareForSegue 将数据“发送”到 detailViewController

只是想发布此内容以供将来参考。

【讨论】:

    【解决方案6】:

    您可以使用[tableView reloadData]; 重新加载 TableView。

    【讨论】:

    • 我已经通过 tableView 控制器文件尝试了各种功能,但似乎都没有更新表格。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-22
    • 2014-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    相关资源
    最近更新 更多