简体   繁体   中英

Passing data between View Controllers in Swift (From TableView to DetailViewController)

I have two files, MyTableViewController and myViewController. I set UIImageView on TableCell in MyTableVIewController. myViewController does not contain anything. And I created an array called ImageArray which contains an array of Images.

What I am aiming to do here is when I click an image on TableCell in myTableViewController, I want the clicked image to appear in the myViewController. And some description about the clicked image beside the image too. I want to use myViewController for users to get detailed information of the selected image.

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
    }
}

I don't know what to do to make it happen. I really appreciate if you could help me figure out! Thanks!

First and foremost, I'm assuming your MyTableViewController class conforms to both UITableViewDataSource and UITableViewDelegate protocols and that you've set your MyTableViewController class to be the delegate in code or via Storyboard.

With that sorted out,there are multiple ways to achieve the result you seek. You can declare your ImageArray in an independent class and call it inside your MyTableViewController class, index them onto a tableView using the tableView delegate methods, and finally using the prepareForSegue method to push your images onto your myViewController. Or you can simply declare and initialize your ImageArray at the top of your MyTableViewController class like below:

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

In the ImageArray above, ensure that your image name matches exactly as the asset name you've imported into your Xcode project.

Then we specify how many rows in section we need ImageArray to occupy on our tableView (ie basically count our ImageArray into our TableView) with below required method:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return ImageArray.count ?? 0
}

Next, you want to present your ImageArray in each row of of the cell using the tableView:cellForRowAtIndexPath: method.

Side Note on your TableCell: Hopefully your TableCell is subclassed from UITableViewCell and you have already declared and connected two IBOutlets, say, imageView and textLabel respectively. Also, ensure your TableCell is properly linked to your prototype cell in Storyboard under Identity Inspector ) Your TableCell class should look something like below:

import UIKit
class CustomTableViewCell: UITableViewCell {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var textLabel: UILabel!
}

Now back into your MyTableVIewController class. From your code, I see you're casting the line 'let cell = ...' as 'UITableViewCell. You should rather cast it as 'TableCell' instead since you're subclassing it. Implement the tableView:cellForRowAtIndexPath: method as follows:

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
}

It looks like you're a little confused about when and where to use performSegueWithIdentifier method as opposed to using - prepareForSegue method. And even when to use the tableView:didSelectRowAtIndexPath method .

Let me briefly explain here. You use the performSegueWithIdentifier method when you didn't control-drag a segue from one ViewController's scene to another in Storyboard. This way, using the performSegueWithIdentifier method will allow you to move between ViewController scenes as long as you specify the right identifier which you've set in Storyboard under ' Attributes Inspector. '

Now if you're using Storyboard instead, you wouldn't need the tableView:didSelectRowAtIndexPath method. What the tableView:didSelectRowAtIndexPath method does is that it tells the delegate that the specified row is now selected and we can do something within its code body (like push an image or a text onto another ViewController Scene like you're trying to do). But that becomes redundant when you use segues. All you have to do, is to control-drag a segue from the table cell on your MyTableViewController scene to your myViewController scene. Choose ' Show ' and give the segue an identifier name like you've done " next ". ( A little side note: if you want the Back button functionality to display at top navigator bar when you run your app, you simply embed your MyTableViewController in a UINavigationController to give you that ' Back ' button functionality. With your MyTableViewController Scene selected in Storyboard, Go to the top menu and select Editor >> Embed In >> Navigation Controller . Then walla!!)

Lets now go ahead and implement our tableView:prepareForSegue method like below:

 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
}

From the above code, make sure you set the ' imageName ' and ' textName ' as properties in your myViewController class first before you can access them with ' destinationVC ' which is now of type myViewController. These two properties will hold the data we are passing from MyTableViewController class to myViewController class. And we are using the array index to pass data to these two properties accordingly.

You can then create two IBOutlets to display your image and text by passing these set ' imageName2 ' and ' textName2 ' properties to your outlets (or any UI control for that matter).

  • Now the reason why you will have to set properties first in myViewController class before you pass them on or around (ie to a UI element, closure, another VC etc) is that, when you hit a tableView cell from MyTableViewController scene to segue onto your next ViewController scene (ie your myViewController scene), iOS hasn't instantiated that second scene just yet. And so you need a property to hold onto the data you're trying to pass onto your second scene View Controller first so that you can make use of it later when that class finally loads.

So your myViewController class should look something like below:

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
    }
}

And that's it!

Things to note on labelling and conventions in Swift:

  • Start naming classes with block letters (ie class ViewController() {})
  • Classes and properties should capture the meaning of what they represent. I will recommend you change your MyTableViewController and ' myViewController 'classes accordingly to reflect what they truly mean or do (You can go with 'MainTableViewController' and 'DetailViewController'. That will do just fine).
  • Use camelToe labelling for properties and methods. (I used the labels you provided in your question in order not to confuse you too much).

    Enjoy!

This should help out:

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]
    }

}

(Where imageView and textView are views in your new viewController.)

Note:

  1. tableView.indexPathForSelectedRow?.row should give you the selected row, as the name implies, but it can be nil , so be careful.
  2. In addition, Swift variable naming conventions are camelCase, so imageView is the correct way, while ImageView is incorrect.

In swift 3 you can do something like this:

In your MyTableViewController class:

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
}

And in your didSelectRow method:

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)
}

And in myViewController class:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM