0

I'm pulling in some JSON data and displaying it in a UITableView (iOS/iPhone 8). It displays the image, title, and description of each value. I've successfully got that down, however, are having trouble pulling the image into a separate View Controller.

By that, I mean, when a cell on the first View Controller is tapped, another view controller opens to display just the information from that cell.

I've been able to make the title and description accessible via a global variable and an indexPath. But the same won't apply to an image, due to a conflict with strings.

I've listed below what I have successfully done with the title and description strings and then show my proposition (which doesn't work of course).

How can I get an image that has already been loaded and is in an array, to be accessible like I already have with the title and description, for use in another View Controller?

The code that formats and gathers values from the JSON:

if let jsonData = myJson as? [String : Any] { // Dictionary
    if let myResults = jsonData["articles"] as? [[String : Any]] {
        // dump(myResults)

        for value in myResults {
            if let myTitle = value["title"] as? String {
                // print(myTitle)
                myNews.displayTitle = myTitle
            }

            if let myDesc = value["description"] as? String {
                myNews.displayDesc = myDesc
            }

            if let mySrc = value["urlToImage"] as? String {
                // print(mySrc)
                myNews.src = mySrc
            }

            self.myTableViewDataSource.append(myNews)
            // dump(self.myTableViewDataSource)

            // GCD
            DispatchQueue.main.async {
                self.myTableView.reloadData()
            }

        }
    }
} 

Two variables I have outside of the class, in order to use them globally:

var petsIndex = ""
var petsDesc = "" 

The code that works with the UITableView and its cells:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let myCell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath)

     let myImageView = myCell.viewWithTag(2) as! UIImageView
     let myTitleLabel = myCell.viewWithTag(1) as! UILabel

     myTitleLabel.text = myTableViewDataSource[indexPath.row].displayTitle

    let myURL = myTableViewDataSource[indexPath.row].src
    loadImage(url: myURL, to: myImageView)

    return myCell
}  

The code that I'm using to send the JSON values to another View Controller. I achieve this by utilizing those global variables:

// If a cell is selected, view transitions into a different view controller.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    petsIndex = self.myTableViewDataSource[indexPath.row].displayTitle
    petsDesc = self.myTableViewDataSource[indexPath.row].displayDesc

    // Img needs to go here

     myIndex = indexPath.row
     performSegue(withIdentifier: "segue", sender: self)

 } 

Here's what I was doing to maybe solve my problem. I converted the string to a UIImage? or a UIImageView? and passed it as data. I left the variable empty and would change it as the data came available. That would occur, when the cell was clicked. Then inside of the second View Controller, I would utilize a an IBOutlet for the UIImageView:

// Variable outside of the class
var petsImg: UIImageView? = nil

petsImg = self.myTableViewDataSource[indexPath.row].src 

I'm stumped at this point. I have gotten errors about the image being a string and needed to be converted. And when it was converted, the variable always came back is empty or nil.

Update: I just tried doing this. It works and doesn't throw any errors. However, I still get a value of nil

petsImg = UIImage(named: self.myTableViewDataSource[indexPath.row].src)

  • What is the class that `myTableViewDataSource` contains? – Callam Jan 05 '19 at 22:17
  • @Callam I made a gist of the complete VC. It's a little messy btw lol. https://gist.github.com/rniller/ca2b9ada07b43cbc5f758478e6ad513c –  Jan 05 '19 at 22:26
  • https://gist.github.com/callam/b49da314dca2b209a1e7b91efc36b609 – Callam Jan 05 '19 at 23:44

3 Answers3

0

Quick and dirty solution

In your second view controller load the image using:

// With petsImg being the url to your image:
if let url = URL(string: petsImg), let imageData = Data(contentsOf: url) { 
    imagePost.image = UIImage(data: data)
}

Proper solution

You should not work with global variables to pass data from one view controller to another (read why). Rather look at this question's answer to find out how to transfer data (in your case: the myNews object) from the table view to a detail view: Send data from TableView to DetailView Swift

If this is too abstract, you can look at this tutorial. It covers what you want to do: https://www.raywenderlich.com/265-uisplitviewcontroller-tutorial-getting-started

Damiaan Dufaux
  • 4,427
  • 1
  • 22
  • 33
0

When you perform a segue, you can intercept the call so to prepare the view controller it is showing. The view of this controller at this point has not loaded yet, and so you will need to create properties inside your PostViewController; you could create properties for the title, description, and image.

However, it will be a lot easier passing this information around as your NewsInfo object, for example:

struct NewsInfo {
    let displayTitle: String
    let displayDesc: String
    let src: String

    var imageURL: URL { return URL(string: src)! }
}

As well as a custom cell class that takes a NewsInfo object as an argument to populate the outlets.

class NewsInfoTableViewCell: UITableViewCell {        
    var newsInfo: NewsInfo? {
        didSet {
            updateOutlets()
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        newsInfo = nil
    }

    private func updateOutlets() {
        textLabel?.text = newsInfo?.displayTitle
        detailTextLabel?.text = newsInfo?.displayDesc
        // loadImage()
    }
}

Setup the custom table cell class and set the newsInfo property after dequeuing.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath) as! NewsInfoTableViewCell
    cell.newsInfo = myTableViewDataSource[indexPath.row]
    return cell
}

When the cell is selected, you can pass it as the sender for performing the segue rather than setting the global variables to populate the PostViewController on viewDidLoad.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if let cell = tableView.cellForRow(at: indexPath) as? NewsInfoTableViewCell {
        performSegue(withIdentifier: "segue", sender: cell)
    }
}

You can remove this whole method if you replace your existing segue by ctrl-dragging from the prototype cell to the PostViewControllerin IB to make the cell the sender of the segue.

We want this because we will intercept the segue to prepare the destination view controller by passing it the NewsInfo object of the cell that was selected and triggered the segue.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch (segue.destination) {
    case (let controller as PostViewController, let cell as NewsInfoTableViewCell):
        controller.newsInfo = cell.newsInfo
    default:
        break
    }
}

Similar to how we pass a NewsInfo object to the cell to populate the outlets, you can do the same thing for the PostViewController.

Callam
  • 11,409
  • 2
  • 34
  • 32
0

It looks like your image is not an image but a url to an image. You can load images into image views using a library like nuke: https://github.com/kean/Nuke

Since network requests are called asynchronously, its a bit difficult to see at which point you're trying to configure your UIImageView. But once you have your network response you will do one of the two:

  1. If your view controller is not yet loaded, (ie you load it once your network response is complete) you can configure the UIImageView in the prepare for sequel method.
  2. If your view controller is already loaded (which is perfectly fine), you will need to set a reference to that view controller in the prepare for segue method. Then you can configure the view controller once the network request is made. I would make the reference to that VC weak, as the system (navigation stack) is already holding on to the VC strongly.

PS: I suggest you de-serialize your JSON response to an object. It will go a long way to help us understand your code. It's hard to see your issue when you're passing dictionary objects around. I suggest you use one of the following: 1. Codable protocol 2. ObjectMapper 3. MapCodableKit (this one is my library which I use personally)

PPS: I assumed you use storyboards.

Jacob
  • 1,052
  • 8
  • 10