Issue with Prepare Segue Func

I am new to swift and have been experimenting with passing data between view controllers. I have been attempting to pass Json data from a view controller into a UITableViewCell, However once run my code has no effect.

DetailViewController (passes data to the libraryViewController)

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 
 let DestViewController: LibraryMovieViewController = segue.destination as! LibraryMovieViewController 
DestViewController.movieTitle = movieTitle 
DestViewController.movieRelease = movieReleaseDate  
}

UITableViewCell

class MovieSearchTableViewCell: UITableViewCell {  
 @IBOutlet weak var titleLabel: UILabel!  
@IBOutlet weak var posterView: UIImageView! 
 @IBOutlet weak var overviewLabel: UILabel!  
}

LibraryViewController

struct libMovie {
    //let mainImage: UIImage
    let title: String
    let release: String
    
}
class LibraryMovieViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
   
    var dataSource: [libMovie] = []
    var movieTitle: String!
    var movieRelease: String!
    
    
     override func viewDidLoad() {
         super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        
     // Do any additional setup after loading the view.
        loadDataSource()
    }
    func loadDataSource(){
        dataSource.append(libMovie(title: " \(String(describing: movieTitle))", release: " \(String(describing: movieRelease))"))
        tableView.reloadData()
    }
    /*
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
       
        let DestViewController:  LibraryDetailViewController = segue.destination as! LibraryDetailViewController
        
        DestViewController.detailTitle = movieTitle
        DestViewController.detailRelease = movieRelease
    }
    */
}
extension LibraryMovieViewController: UITableViewDataSource, UITableViewDelegate{
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 115
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->  UITableViewCell {
        
        guard let movieCell = tableView.dequeueReusableCell(withIdentifier: "libCell", for: indexPath) as? LibraryMovieTableViewCell else {
            return UITableViewCell()
        }
        let libMovie = dataSource[indexPath.row]
        
        movieCell.cellTitleLabel.text = "Movie Title:\(libMovie.title)"
        movieCell.cellReleaseLabel.text = "Release Date:\(libMovie.release)"
        return movieCell
    }
}

I would expect that when the app is run that movieTitle and movieReleaseDate are passed from the detail view controller and input into the library table cell, this is initiated by tapping a button on the detail view controller.

However this seems to have no affect on the program or simply returns blank cells. No errors are reported in console nor does the app crash

I have tried sending the data to the variables movieTitle and movieRelease and setting NumberOfRowsInSection to 1 but this returns nil

I receive an error: Value of type View Controller has no member libMovieDestViewController.libMovie.title = movieTitle

DestViewcontroller.datasource also produces and error -> Cannot assign value of type 'String?' to type '[libMovie]'

Accepted Reply

That means prepare is not called.


But you call the wrong function.


You should replace

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


by:


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     print(#function, "I prepare for segue with", movieTitle, movieReleaseDate)
     let destViewController: LibraryMovieViewController = segue.destination as! LibraryMovieViewController
     destViewController.movieTitle = movieTitle
     destViewController.movieRelease = movieReleaseDate 
}

Replies

Have you checked that prepare is called ?


Instrument as follows and tell what gou get in log


func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
     print(#function, "I prepare for segue with", movieTitle, movieReleaseDate)
     let destViewController: LibraryMovieViewController = segue.destination as! LibraryMovieViewController
     destViewController.movieTitle = movieTitle
     destViewController.movieRelease = movieReleaseDate 
}


If prepare is not called, please show code for the button action.

note: var or const name should start with lowercase.

On the other end, struct libMovie should start with Uppercase struct LibMovie


Also test here:


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->  UITableViewCell {
        print(#function, "indexPath", indexPath)
        guard let movieCell = tableView.dequeueReusableCell(withIdentifier: "libCell", for: indexPath) as? LibraryMovieTableViewCell else {
            return UITableViewCell()
        }
        let libMovie = dataSource[indexPath.row]
        print(#function, "libMovie", libMovie)      
        movieCell.cellTitleLabel.text = "Movie Title:\(libMovie.title)"
        movieCell.cellReleaseLabel.text = "Release Date:\(libMovie.release)"
        return movieCell
    }
}

Finally, instrument cellForRow.

That will check whether dequeue succeeds (check the identifier, including lower / upper case)


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->  UITableViewCell {
        print(#function, "Row", indexPath)
        guard let movieCell = tableView.dequeueReusableCell(withIdentifier: "libCell", for: indexPath) as? LibraryMovieTableViewCell else {
            return UITableViewCell()
        }
        let libMovie = dataSource[indexPath.row]
        print(#function, "libMovie", libMovie)

        movieCell.cellTitleLabel.text = "Movie Title:\(libMovie.title)"
        movieCell.cellReleaseLabel.text = "Release Date:\(libMovie.release)"
        return movieCell
    }
}

Many Thanks for your reply,


I have added the print statements you outlined above to the code, however the first;


print(#function, "I prepare for segue with", movieTitle, movieReleaseDate)


Seems to return no data to the log.


the second two statements both return

tableView(_:cellForRowAt:) indexPath [0, 0]

tableView(_:cellForRowAt:) libMovie libMovie(title: " nil", release: " nil")


I had added a few print("movieTitle") statements throughout the code and it does seem that the values for both movieTitle and movieReleaseDate are populated with data before the prepare statement, but then become 'nil' after the prepare for segue function


as for the the button action, it is simply as follows

@IBActionfunc addToLibrary(_ sender: Any) {
      
        self.performSegue(withIdentifier: "LibViewSegue", sender: self)
    }

I am unsure if i require more code in the button action for the prepare statement to be called, or if i need the prepare statement to be within the button action (which seems to have no effect on the code)

How did you connect the segue ?

It should be a segue from the viewController to the viewController, not a direct connection from the button to the destination. Otherwise, prepare is not called (in fact, segue triggers directly, without even executing IBAction).


You can check by instrumenting here:

@IBAction func addToLibrary(_ sender: Any) { 
       print(#function, "is called")
        self.performSegue(withIdentifier: "LibViewSegue", sender: self) 
    }

I connected the segue as you stated above, view controller to view controller, giving it the identifier "LibViewSegue"


after following your instruction to instrument, the following was output in Log: addToLibrary(_:) is called


If this means that the prepare is called, how can it be that the variables at the destination view controller return nil?

OK, that was the most critical.


Lesson here is that a segue connected from a button is fired immediately ; hence, IBAction is not called (hence the performSegue of the IBAction is not, nor the prepare).


Did you check that prepare is effectively called now ?

Do you see the log of

print(#function, "I prepare for segue with", movieTitle, movieReleaseDate)


Could you also add some print here:


    func loadDataSource() {
        print(#function, movieTitle, movieRelease, " \(String(describing: movieTitle))", " \(String(describing: movieRelease))")
        dataSource.append(libMovie(title: " \(String(describing: movieTitle))", release: " \(String(describing: movieRelease))"))
          
        let lib = libMovie(title: " \(String(describing: movieTitle))", release: " \(String(describing: movieRelease))")
        print("lib ", lib)
        tableView.reloadData()
    }

And tell what you get.


Also tell what you get from the prints in

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->  UITableViewCell {



why do you use String(describing:) and not simply:


        dataSource.append(libMovie(title:  movieTitle, release: movieRelease))


as they are String

So I added the prints, I also added one to

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
        return 1 
    }


The total output into log is as follows:

addToLibrary(_:) is called

loadDataSource() nil nil nil nil

tableView(_:numberOfRowsInSection:) 1

tableView(_:numberOfRowsInSection:) 1

tableView(_:cellForRowAt:) indexPath [0, 0]


Doesnt seem to be any output for:

print(#function, "I prepare for segue with", movieTitle, movieReleaseDate)

Does this mean that there is an error with my prepare statement if values at loadDataSource() are nil?

That means prepare is not called.


But you call the wrong function.


You should replace

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


by:


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     print(#function, "I prepare for segue with", movieTitle, movieReleaseDate)
     let destViewController: LibraryMovieViewController = segue.destination as! LibraryMovieViewController
     destViewController.movieTitle = movieTitle
     destViewController.movieRelease = movieReleaseDate 
}

Many Thanks! Your help has been truly Appreciated!