Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
394 views
in Technique[技术] by (71.8m points)

json - Error - Resquest function with Alamofire and AlamofireObjectMapper (swift - iOS)

First of all, I'm a beginner and I'm trying to build an app that search a movie on OMDB API and return a list of movie (when searched by title) and return an specific movie when searched by imdbID. I have to make two types of request for the api, because the result for search by id have the same atributes that the search by title have but with more details (need this to show a view with a selected movie from this list of results).

So, it was recommended to me (here) to use AlamofireObjectMapper/ObjectMapper to do it better. I did mapped like this:

import Foundation
import AlamofireObjectMapper

class SearchResponse: Mappable {
    var isSuccess  : String?
    var searchArray: [Movie]?
    var searchCount: String?

    required init?(map: Map) {
    }

    func mapping(map: Map) {
        isSuccess   <- map["Response"]
        searchArray <- map["Search"]
        searchCount <- map["totalResults"]
    }
}

class Movie: Mappable {

    var posterURL  : String?
    var title      : String?
    var runtime    : String?
    var director   : String?
    var actors     : String?
    var genre      : String?
    var plot       : String?
    var production : String?
    var year       : String?
    var imdbID     : String?
    var imdbRating : String?

    required init?(map: Map) {

    }

    func mapping(map: Map) {
        posterURL  <- map["Poster"]
        title      <- map["Title"]
        runtime    <- map["Runtime"]
        director   <- map["Director"]
        actors     <- map["Actors"]
        genre      <- map["Genre"]
        plot       <- map["Plot"]
        production <- map["Production"]
        year       <- map["Year"]
        imdbID     <- map["imdbID"]
        imdbRating <- map["imdbRating"]
   }
}

I would like to do something like this:

//Get movie by title - the user will enter the title on a searchbar

let url = "https:www.omdbapi.com/?s=(imdbTitle)"

func getMoviesByTitle (imdbTitle: String) {
    /* The Alamofire function using ObjectMapper goes here */
    switch
    case .success():
        /*Something*/
        completionHandler(???)
    case .failure():
       /*Something*/
        completionHandler(???)
}


//Get movie by ID

let url = "https:www.omdbapi.com/?i=(imdbID)"

func getMovieByID(imdbID: String) {
    /* The Alamofire function using ObjectMapper goes here */
    if let response {
       completioHandler(???)
    } /* Something like this? */
}

I need some guide. When I search a movie by Title it returns an JSON with Response, Search("array" of movies) and totalResults. In this case, my Movie class only have four of those mapping attributes (Poster, Title, Year, imdbID).

  1. Is this mapping right?
  2. How can I make those requests for each case? I mean, what should I return? (Because my getMovie functions need a completionHandler, right?)

EDIT

So, I've tried this on my SearchTableViewController:

import UIKit
import Alamofire
import AlamofireObjectMapper
import ObjectMapper
import Kingfisher



class SearchTableViewController: UITableViewController, UISearchResultsUpdating {


@IBOutlet var searchTableView: UITableView!

@IBAction func showResults(_ sender: Any) {
    let searchController = UISearchController(searchResultsController: nil)
    self.present(searchController, animated: true, completion: nil)
    searchController.searchBar.barTintColor = self.searchTableView.backgroundColor!
    searchController.searchResultsUpdater = self

}


var movies = [Movie]()



override func viewDidLoad() {
    super.viewDidLoad()

    searchTableView.dataSource = self
    searchTableView.delegate = self

}

func updateSearchResults(for searchController: UISearchController) {

    if let searchText = searchController.searchBar.text {

        if searchText == "" {
            return
        }

        else {
            let movieSearched: String = searchText.replacingOccurrences(of: " ", with: "_")


                // MARK: Alamofire Get by Title

                let URL = "https://www.omdbapi.com/?s=(movieSearched)&type=movie"


                Alamofire.request(URL).responseObject{ (response: DataResponse<SearchResponse>) in

                    print("response is: (response)")

                    switch response.result {

                    case .success(let value):
                        let searchResponse = value
                        self.movies = (searchResponse.searchArray)!
                        self.searchTableView.reloadData()

                    case .failure(let error):
                        let alert = UIAlertController(title: "Error", message: "Error 4xx / 5xx: (error)", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }

                }



                DispatchQueue.main.async {
                    let spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
                    spinnerActivity.label.text = "Loading";
                    spinnerActivity.detailsLabel.text = "Searching movie..."
                    spinnerActivity.isUserInteractionEnabled = false;
                }




                DispatchQueue.main.async {
                    MBProgressHUD.hide(for: self.view, animated: true)
                }



        }

    }
}


// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return movies.count
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "SearchCellIdentifier", for: indexPath) as! SearchTableViewCell

    let movie = movies[indexPath.row]

    let imgStg: String = movie.posterURL!
    let imgURL: URL? = URL(string: imgStg)
    let imgSrc = ImageResource(downloadURL: imgURL!, cacheKey: imgStg)

    cell.titleLabel.text = movie.title
    cell.yearLabel.text = movie.year


    cell.posterImageView.layer.cornerRadius = cell.posterImageView.frame.size.width/2
    cell.posterImageView.clipsToBounds = true

    //image cache with KingFisher
    cell.posterImageView.kf.setImage(with: imgSrc)



    return cell
}


/*
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    // Return false if you do not want the specified item to be editable.
    return true
}
*/

/*
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        // Delete the row from the data source
        tableView.deleteRows(at: [indexPath], with: .fade)
    } else if editingStyle == .insert {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }    
}
*/

/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {

}
*/

/*
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    // Return false if you do not want the item to be re-orderable.
    return true
}
*/

/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/

}

The search is working, until I type 4 characters... Until the 3th character typed, the table view show in real time the results, but when I type the 4th, the app crashs. The error is this:

error with self.movies = (searchResponse.searchArray)! line

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The error you are getting is due to force unwrapping (the !). searchResponse.searchArray is probably returning empty once you type the 4th character.

Your movies var should be optional - meaning it could potentially be nil.

var movies:[Movie]?

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if let safeMovies = movies {
        return safeMovies.count
    } else {
        return 0 //no movies were returned.  you could eventually show an error here
    }
}

In your Alamofire responseObject closure (just showing the updated piece)

case .success(let value):
    let searchResponse = value
    self.movies = searchResponse.searchArray
    self.searchTableView.reloadData()

A few additional thoughts on your updateSearchResults method. You can use guard to unwrap and check searchText, so that you don't need a huge else { } statement. You should also remove your MBProgressHUD overlay once Alamofire completes, otherwise you are showing and then hiding at the same time.

func updateSearchResults(for searchController: UISearchController) {

    guard let searchText = searchController.searchBar.text, searchText != "" else {
        return
    }

    let movieSearched: String = searchText.replacingOccurrences(of: " ", with: "_")
    // MARK: Alamofire Get by Title
    let URL = "https://www.omdbapi.com/?s=(movieSearched)&type=movie"
    Alamofire.request(URL).responseObject{ (response: DataResponse<SearchResponse>) in
        print("response is: (response)")
        DispatchQueue.main.async {
            MBProgressHUD.hide(for: self.view, animated: true)
        }
        switch response.result {
        case .success(let value):
            let searchResponse = value
            self.movies = (searchResponse.searchArray)!
            self.searchTableView.reloadData()

        case .failure(let error):
            let alert = UIAlertController(title: "Error", message: "Error 4xx / 5xx: (error)", preferredStyle: UIAlertControllerStyle.alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }

    DispatchQueue.main.async {
        let spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
        spinnerActivity.label.text = "Loading";
        spinnerActivity.detailsLabel.text = "Searching movie..."
        spinnerActivity.isUserInteractionEnabled = false;
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...