Home > other >  Swift UIKIT - I can't run methods sequentially using closure
Swift UIKIT - I can't run methods sequentially using closure

Time:10-26

I'm new to Swift. When the API requests are finished, I want to show them on the UI, but I don't know how to use these 3 functions (getMovieDeatails, loadImage, updateUI) in order. I can transfer the imdbID variable from the previous page without any problems and do both the api query and the image upload process without any problems. But I'm getting an error because I couldn't make the function order correctly when displaying it in the ui. The error I'm getting is:

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful. 2022-10-25 03:43:53.276551 0300 Invio_Movie_World[9944:288248] [Invio_Movie_World] seek:413: *** IIOScanner::seek reached EOF Invio_Movie_World/DetailViewController.swift:51: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value 2022-10-25 03:44:04.101415 0300 Invio_Movie_World[9944:287942] Invio_Movie_World/DetailViewController.swift:51: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value (lldb)

Could you please help?

This is DetailViewController.swift :


class DetailViewController: UIViewController {
    
    //MARK: UI Elements
    @IBOutlet var imageMovie: UIImageView!
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var yearLabel: UILabel!
    @IBOutlet var releasedLabel: UILabel!
    @IBOutlet var runtimeLabel: UILabel!
    @IBOutlet var genreLabel: UILabel!
    @IBOutlet var directorLabel: UILabel!
    @IBOutlet var writerLabel: UILabel!
    @IBOutlet var actorsLabel: UILabel!
    @IBOutlet var plotLabel: UILabel!
    @IBOutlet var languageLabel: UILabel!
    @IBOutlet var countryLabel: UILabel!
    @IBOutlet var awardsLabel: UILabel!
    @IBOutlet var imdbRatingLabel: UILabel!
    @IBOutlet var imdbIDLabel: UILabel!
    
    var imdbID = ""
    private var omdbApi = OmdbApi(apiKey: "31dd4179")
    private var selectedMovie : MovieDetail!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        updateUI()
        
    }
    
    func getMovieDetails(completion : @escaping (_ movieDetail: MovieDetail) ->()) {
        self.omdbApi.getMovieDetail(imdbID: self.imdbID) { (movieDetail) in
            if let movieDetail = movieDetail {
                completion(movieDetail)
                self.selectedMovie = movieDetail
                print("getmoviedetails ici \(self.selectedMovie!)")
            }
        }
    }
    
    func loadImage (completion : @escaping ()->()){
        self.imageMovie.load(urlString: self.selectedMovie.poster)
    }
    
    func updateUI(){
        getMovieDetails { (movieDetail) in
            self.loadImage {
                self.titleLabel.text = "\(self.selectedMovie.title)"
                self.yearLabel.text = "Year : \(self.selectedMovie.year)"
                self.releasedLabel.text = "Released : \(self.selectedMovie.released)"
                self.runtimeLabel.text = "Runtime : \(self.selectedMovie.runtime)"
                self.genreLabel.text = "Genre : \(self.selectedMovie.genre)"
                self.directorLabel.text = "Director : \(self.selectedMovie.director)"
                self.writerLabel.text = "Writer : \(self.selectedMovie.writer)"
                self.actorsLabel.text = "Actors : \(self.selectedMovie.actors)"
                self.plotLabel.text = "Plot : \(self.selectedMovie.plot)"
                self.languageLabel.text = "Language : \(self.selectedMovie.language)"
                self.countryLabel.text = "Country : \(self.selectedMovie.country)"
                self.self.awardsLabel.text = "Awards : \(self.selectedMovie.awards)"
                self.imdbRatingLabel.text = "IMDB Rating : \(self.selectedMovie.imdbRating)"
                self.imdbIDLabel.text = "IMDB ID : \(self.selectedMovie.imdbID)"
            }
        }
    }
}

This is the load extension in loadimage:

import Foundation
import UIKit

extension UIImageView {
    func load(urlString : String) {
        guard let url = URL(string: urlString)else {
            return
        }
        DispatchQueue.global().async { [weak self] in
            if let data = try? Data(contentsOf: url) {
                if let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self?.image = image
                        
                    }
                }
            }
        }
    }
}

This is MovieDetail struct

import Foundation

struct MovieDetail: Codable  {
    let title, year, rated, released: String
    let runtime, genre, director, writer: String
    let actors, plot, language, country: String
    let awards: String
    let poster: String
    let metascore, imdbRating, imdbVotes, imdbID: String

    enum CodingKeys: String, CodingKey {
        case title = "Title"
        case year = "Year"
        case rated = "Rated"
        case released = "Released"
        case runtime = "Runtime"
        case genre = "Genre"
        case director = "Director"
        case writer = "Writer"
        case actors = "Actors"
        case plot = "Plot"
        case language = "Language"
        case country = "Country"
        case awards = "Awards"
        case poster = "Poster"
        case metascore = "Metascore"
        case imdbRating, imdbVotes, imdbID
    }
}

CodePudding user response:

It looks to me like it is failing on:

self.imageMovie.load(urlString: self.selectedMovie.poster)

I would guess this is because it references self.selectedMovie which is implicitly unwrapped. You call the completion for getMovieDetail which calls loadImage before you set selectedMovie. If you flip the lines in getMovieDetail like this it should fix this issue:

self.selectedMovie = movieDetail // do this first
completion(movieDetail) // then call this

I would recommend not implicitly unwrapping your data like this as it will let the compiler help you find these bugs easier (you'll get warnings/errors instead of crashes). I would also say that if you want to have nested layers of closures like this, you should pass the data directly as an argument instead of referencing properties on self. This will help make the flow of the logic a little clearer and ensure you're not working with out of date data. Something like this:

func getMovieDetails(completion : @escaping (_ movieDetail: MovieDetail) ->()) {
    omdbApi.getMovieDetail(imdbID: self.imdbID) { (movieDetail) in
        if let movieDetail = movieDetail {
            self.selectedMovie = movieDetail
            completion(movieDetail)
            print("getmoviedetails ici \(self.selectedMovie!)")
        }
    }
}

func loadImage (for movieDetail: MovieDetail, completion : @escaping ()->Void) {
    imageMovie.load(urlString: movieDetail.poster)
}

func updateUI(){
    getMovieDetails { movieDetail in
        self.loadImage(for: movieDetail) {
            self.titleLabel.text = "\(movieDetail.title)"
            self.yearLabel.text = "Year : \(movieDetail.year)"
            self.releasedLabel.text = "Released : \(movieDetail.released)"
            self.runtimeLabel.text = "Runtime : \(movieDetail.runtime)"
            self.genreLabel.text = "Genre : \(movieDetail.genre)"
            self.directorLabel.text = "Director : \(movieDetail.director)"
            self.writerLabel.text = "Writer : \(movieDetail.writer)"
            self.actorsLabel.text = "Actors : \(movieDetail.actors)"
            self.plotLabel.text = "Plot : \(movieDetail.plot)"
            self.languageLabel.text = "Language : \(movieDetail.language)"
            self.countryLabel.text = "Country : \(movieDetail.country)"
            self.self.awardsLabel.text = "Awards : \(movieDetail.awards)"
            self.imdbRatingLabel.text = "IMDB Rating : \(movieDetail.imdbRating)"
            self.imdbIDLabel.text = "IMDB ID : \(movieDetail.imdbID)"
        }
    }
}
  • Related