首先,我是初学者,我正在尝试构建一个应用程序,在OMDB API上搜索电影并返回电影列表(按标题搜索时),并在按imdbID搜索时返回特定电影。我必须为api提出两种类型的请求,因为按ID搜索的结果具有与按标题搜索但具有更多详细信息相同的属性(需要此信息以显示包含此结果列表中所选电影的视图)
所以,我(这里)建议使用AlamofireObjectMapper / ObjectMapper来做得更好。我做了这样的映射:
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"]
}
}
我想做这样的事情:
//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? */
}
我需要一些指导。当我按标题搜索电影时,它返回一个带有响应,搜索(电影的“数组”)和totalResults的JSON。在这种情况下,我的Movie类只有四个映射属性(Poster,Title,Year,imdbID)。
所以,我在我的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.
}
*/
}
搜索工作正常,直到我键入4个字符...直到输入第3个字符,表格视图实时显示结果,但是当我输入第4个字符时,应用程序崩溃。错误是这样的:
答案 0 :(得分:1)
你得到的错误是由于强行打开(!)。输入第4个字符后,searchResponse.searchArray
可能会返回空白状态。
您的movies
var应该是可选的 - 这意味着它可能是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
}
}
在你的Alamofire responseObject闭包中(只显示更新的部分)
case .success(let value):
let searchResponse = value
self.movies = searchResponse.searchArray
self.searchTableView.reloadData()
关于updateSearchResults
方法的一些其他想法。您可以使用guard
打开并检查searchText,这样您就不需要大量的其他{}语句。 Alamofire完成后,您还应该删除MBProgressHUD叠加层,否则您将同时显示然后隐藏。
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;
}
}