在Swift中在视图控制器之间传递数据(从表格视图到详细视图控制器)

3
我有两个文件,一个是MyTableViewController,另一个是myViewController。我在MyTableVIewController中的TableCell上设置了UIImageView,而myViewController没有任何内容。我创建了一个名为ImageArray的数组,其中包含一组图像。
我的目标是,当我在myTableViewController中的TableCell上单击图像时,我希望所选图像出现在myViewController中,并且还要在图像旁边显示一些关于所选图像的描述。我想使用myViewController让用户获取所选图像的详细信息。
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
    var ImageView = cell.viewWithTag(1) as! UIImageView
    ImageView.image = UIImage(named: ImageArray[indexPath.row])
    ImageView.contentMode = .ScaleAspectFit

    var TextView = cell.viewWithTag(2) as! UILabel
    TextView.text = ImageArray[indexPath.row]
    return cell
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    performSegueWithIdentifier("next", sender: indexPath)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "next") {
        let destination = segue.destinationViewController as! myViewController
    }
}

我不知道该怎么做才能实现它。如果你能帮我想个办法的话,我会非常感激!谢谢!

3个回答

4

首先,我假设您的 MyTableViewController 类遵循 UITableViewDataSource UITableViewDelegate 协议,并且已经通过代码或Storyboard设置了MyTableViewController类为委托。

有多种方法可实现所需的结果。 您可以在独立类中声明您的 ImageArray ,并在 MyTableViewController 类中调用它,使用tableView代理方法将它们索引到tableView上,最后使用 prepareForSegue 方法将您的图像推送到您的myViewController中。 或者,您可以在 MyTableViewController 类顶部声明和初始化您的ImageArray,如下所示:

var ImageArray = [("Moscow Russia.jpg", "Europe"),
    ("London England.jpg", "Europe")]  

在上面的ImageArray中,请确保您的图像名称与您导入到Xcode项目中的资产名称完全匹配。
然后,我们使用下面所需的方法指定我们需要在tableView中占用多少行部分ImageArray(即基本上将我们的ImageArray计入我们的TableView)。
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return ImageArray.count ?? 0
}

接下来,您需要使用tableView:cellForRowAtIndexPath:方法在每个单元格的每行中呈现您的ImageArray。 关于您的TableCell的侧记:希望您的TableCell是从UITableViewCell子类化的,并且您已经声明并连接了两个IBOutlets,比如imageView和textLabel。同时,请确保您的TableCell已正确链接到Storyboard中的原型单元格(在Identity Inspector下)。您的TableCell类应该如下所示:
import UIKit
class CustomTableViewCell: UITableViewCell {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var textLabel: UILabel!
}

现在回到你的MyTableVIewController类。从你的代码中,我看到你将'let cell = ...'这行代码强制转换为'UITableViewCell'。但由于你正在子类化它,所以你应该将其强制转换为'TableCell'。请按照以下方式实现tableView:cellForRowAtIndexPath:方法:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{        
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! TableCell

//Note that I'm using tuples here. Pretty cool huh. Got to love Swift!
  let (imageName, textNameToGoWithImage) = ImageArray[indexPath.row]

    cell.textLabel.text = textNameToGoWithImage
    cell.imageView.image = UIImage(named: imageName)
    cell.imageView.contentMode = .ScaleAspectFit

// You could have also used 'if let' in optional binding to safely unwrap your image if you want like below.
   //    if let image = UIImage(named: imageName){
   //        cell.imageView?.image = image
  //         cell.imageView.contentMode = .ScaleAspectFit
  //     }

    return cell
}

看起来你对何时何地使用performSegueWithIdentifier方法与使用- prepareForSegue方法感到有些困惑。甚至不知道何时使用 tableView:didSelectRowAtIndexPath 方法
让我在这里简要解释一下。当你没有在故事板中从一个视图控制器场景拖动一个转换时,你需要使用performSegueWithIdentifier方法。这样,只要指定正确的标识符(你在“属性检查器”下设置的标识符),就可以在视图控制器场景之间移动。
如果你正在使用故事板,则不需要使用tableView:didSelectRowAtIndexPath方法。 tableView:didSelectRowAtIndexPath方法告诉代理选定了指定的行,我们可以在代码体内做一些事情(例如将图像或文本推到另一个视图控制器场景中,就像你正在尝试的那样)。但是当你使用转换时,这变得冗余。你所要做的就是在MyTableViewController场景上从 table cell进行control-drag转换到你的myViewController场景。选择“Show”,并给segue命名一个标识符名称,就像你用“next”做的那样。
(一个小提示:如果你希望在运行应用程序时显示顶部导航栏中的 Back 按钮功能,只需将 MyTableViewController嵌入到 UINavigationController 中即可获得该'Back'按钮功能。选择故事板中的MyTableViewController场景,转到顶部菜单并选择Editor>> Embed In >> Navigation Controller。然后就大功告成了!)
现在让我们实现我们的tableView:prepareForSegue方法如下:
 override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "next" {
        //Note that, originally, destinationViewController is of Type UIViewController and has to be casted as myViewController instead since that's the ViewController we trying to go to. 
        let destinationVC = segue.destinationViewController as! myViewController


        if let indexPath = self.tableView.indexPathForSelectedRow{

            let selectedRow = ImageArray[indexPath.row]

            destinationVC.imageName2 = selectedRow.0
            destinationVC.textName2 = selectedRow.1
}

从上面的代码中,确保在访问 'destinationVC' 之前,你需要先在你的 myViewController 类中设置 'imageName' 和 'textName' 作为属性。这两个属性将保存我们从 MyTableViewController 类传递到 myViewController 类的数据。我们使用数组索引相应地传递数据到这两个属性。
然后,您可以通过将设置的 'imageName2' 和 'textName2' 属性传递到您的 IBOutlets(或任何 UI 控件)来创建两个 IBOutlets 来显示您的图像和文本。
现在,为什么您必须先在 myViewController 类中设置属性,然后再将它们传递给其他元素(例如 UI 元素、闭包、另一个 VC 等)呢?因为当您从 MyTableViewController 场景点击 tableView 单元格跳转到下一个 ViewController 场景(即您的 myViewController 场景)时,iOS 尚未实例化第二个场景。因此,您需要首先有一个属性来保存您要传递到第二个场景 View Controller 的数据,以便在该类最终加载时稍后使用它。
因此,您的 myViewController 类应该如下所示:
import UIKit

class myViewController : UIViewController {

   //Your two strings to initially hold onto the data 
  //being passed to myViewController class
var imageName2 : String?
var textName2 : String?

@IBOutlet weak var detailImageView: UIImageView!
@IBOutlet weak var detailTextNameLabel: UITextField!

override func viewDidLoad() {
    super.viewDidLoad()

    detailTextNameLabel.text = textName2!

    if let image = UIImage(named: imageName2!) {
        self.detailImageView.image = image
    }
}

就是这样了!

关于Swift的标记和约定需要注意以下几点:

  • 类名应以大写字母开头(即class ViewController() {})
  • 类和属性应捕捉它们所代表的含义。我建议您根据它们真正的含义或功能来更改您的 MyTableViewControllermyViewController 类,例如使用 'MainTableViewController' 和 'DetailViewController'。
  • 属性和方法的标记应该使用驼峰命名法。(出于不想让您困惑太多的考虑,我使用了您在问题中提供的标签。)

    祝您愉快!


你好!非常感谢你详细的解释,这对我帮助很大!我会继续努力学习并希望自己能够有更大的进步!再次感谢你!!! :) - Ryohei Arai
没问题。让我知道你的事情进展如何!祝你好运! - Vick Swift

2
这应该会有所帮助:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "next") {
        let destination = segue.destinationViewController as! myViewController
        destination.imageView.image = UIImage(named: ImageArray[tableView.indexPathForSelectedRow?.row])
        destination.textView.text = ImageArray[tableView.indexPathForSelectedRow?.row]
    }

}

(其中imageView和textView是新视图控制器中的视图。)

注意:

  1. tableView.indexPathForSelectedRow?.row应该给出所选行,正如名称所示,但它可能为nil,因此要小心。
  2. 此外,Swift变量命名约定是驼峰式,因此imageView是正确的方式,而ImageView是不正确的。

谢谢您的帮助!但是尽管我写得完全正确,它似乎并没有起作用。对于[tableView.indexPathForSelectedRow?.row]的最后两行代码,我收到的错误是“无法使用'NSIndexPath?'类型的索引访问类型为'[(String)]'的值”。我该如何修复这些错误?提前感谢您的帮助! - Ryohei Arai
你介意包括一下如何初始化图片数组以及如何填充它,这样我就知道如何访问它了吗? - Acoop

1
在Swift 3中,您可以这样做:
在您的MyTableViewController类中:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! UITableViewCell

    // for imageview

    var ImageView : UIImageView = cell.contentView.viewWithTag(1) as! UIImageView
    ImageView.image = UIImage(named: ImageArray[indexPath.row])


    // for text

    var TextView : UILabel = cell.contentView.viewWithTag(2) as! UILabel
    ImageView.text = ImageArray[indexPath.row]

    return cell
}

在你的 didSelectRow 方法中:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
   let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "myViewController") as! myViewController
   nextVC.myImgView.image = UIImame(named: ImageArray[indexPath.row])
   nextVC.myLabel.text = ImageArray[indexPath.row]
   self.present(nextVC!, animated: true)
}

在我的视图控制器类中:

myViewController 类:

class myViewController: UIViewController
{
    let myImageView = UIImageView()
    let myLabel = UILabel()
}

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