我正在开发一个简单的动漫应用项目,您可以在其中查看热门动漫列表、搜索任何动漫、将它们添加到已观看、要观看或正在观看的部分以及查看信息(例如概要)例如)点击动画时。大多数情况下,除了从搜索结果中查看动漫信息外,其他一切都正常,例如在搜索结果中点击火影忍者的信息时无法查看它。
这是我的 AnimeDetailsViewController.swift 代码
import UIKit
class AnimeDetailsViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var episodesLabel: UILabel!
@IBOutlet weak var posterView: UIImageView!
@IBOutlet weak var synopsisLabel: UILabel!
var animeItem: AnimeFromTop! //we get the data for this animeItem from the DiscoverViewController during segue. Anime objectthat the user selected from the top anime list
var animeSynopsis : String?
override func viewDidLoad() {
super.viewDidLoad()
// let animeID = animeItem.mal_id//we need the anime id to get the synopsis of the anime
// getSynopsis(anime_ID: animeID)
getSynopsis(anime_ID: animeItem.mal_id) { (synopsis, error) in
DispatchQueue.main.async {
self.synopsisLabel.text = synopsis
print(self.synopsisLabel.text as Any)
}
}
//set up layout
synopsisLabel.sizeToFit()
if animeItem.episodes == 0 {
episodesLabel.text = "Null"
} else {
episodesLabel.text = "\(animeItem.episodes ?? 0) episodes"
}
titleLabel.text = animeItem.title
let imgUrlString = animeItem.image_url //image url in string
let imgURL = URL(string: imgUrlString)!
posterView.af.setImage(withURL: imgURL)
// setupLayout()
// Do any additional setup after loading the view.
}
//The purpose of this function is to make an API call to get the synopsis of our anime, which is not provided by the top anime endpoint. This function gets an anime object from the API but only with the synopsis because we don't need the rest of the huge data.
func getSynopsis(anime_ID : Int, synopsisCompletionHandler: @escaping (String?, Error?) -> Void) {
let animeID = anime_ID //we need the anime id to get the synopsis of the anime
//API CALL to get the synopsis
let urlString = "https://api.jikan.moe/v3/anime/\(animeID)" //url String
guard let url = URL(string: urlString) else {
//if not able to create a url from urlString, just return
print("Not able to create url object from url String")
return
}
//Create a Data Task, which is how you perform actual API calls and networking tasks
let task = URLSession.shared.dataTask(with: url, completionHandler: {
data, _, error in
guard let data = data, error == nil else {
return
}
//do - catch function because the decoder function can actually throw an error so we want to account for that too
var anime: AnimeSynopsis?
do {
anime = try JSONDecoder().decode(AnimeSynopsis.self, from: data)
if let synopsis = anime?.synopsis {
synopsisCompletionHandler(synopsis, nil)
}
} catch {
print("Failed to Decode Error")
}
})
task.resume() //starts the API call
//this is where synopsisCompletionHandler(synopsis, nil) is called
}
/*
// 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.destination.
// Pass the selected object to the new view controller.
}
*/
}
这是我的 DiscoverViewController.swift 代码(它显示了最热门的动漫,并让我在点击时查看动漫的信息)
import UIKit
import AlamofireImage
class DiscoverViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
@IBOutlet weak var topTableView: UITableView!
var filteredAnimes: SearchResults? //this property will hold the animes that user searches for
// topResults will contain the array of dictionaries that represent the top animes returned to us by an API call
var topResults : TopAnimes?
//By initializing UISearchController with a nil value for searchResultsController, you’re telling the search controller that you want to use the same view you’re searching to display the results. If you specify a different view controller here, the search controller will display the results in that view controller instead.
var searchController: UISearchController!
//In order for the DiscoverViewController to respond to the search bar, it must implement UISearchResultsUpdating. This protocol defines methods to update search results based on information the user enters into the search bar. Do this outside of the main DiscoverViewController function at the bottom.
private var searchResultsTableController : SearchResultsViewController! //creates an instance of the class SearchViewController
var anime_with_synopsis : String?
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchBar()
// Loading data for the top anime view controller in Discover View
topTableView.dataSource = self
topTableView.delegate = self
let group = DispatchGroup()
group.enter()
getTopAnimes(group) //API call to get an array of dictionaries of top animes
group.wait()
topTableView.reloadData()//calls on the table view functions to reload data with data received from the API call
}
//MARK: - API Call and table view configurations for the Top Anime table view controller
func getTopAnimes(_ group: DispatchGroup) {
let urlString = "https://api.jikan.moe/v3/top/anime" //url String
//Create a url object from the url String. Use guard so that if cannot be created as an url object, then provide optional error message. Created as an optional
guard let url = URL(string: urlString) else {
//if not able to create a url from urlString, just return
print("Not able to create url object from url String")
return
}
//Create a Data Task, which is how you perform actual API calls and networking tasks
//the completionHandler returns 3 optional parameters, but we only care about the Data and Error so we will do _ for discardable for the 2nd parameter URLResponse
let task = URLSession.shared.dataTask(with: url, completionHandler: { [self]
data, _, error in
guard let data = data, error == nil else {
return
}
//do - catch function because the decoder function can actually throw an error so we want to account for that too
var results: TopAnimes?
do {
results = try JSONDecoder().decode(TopAnimes.self, from: data)
} catch {
print("Failed to Decode Error")
}
guard let final = results else {
return
} //final is the top results array
// print(final.top)
topResults = final
group.leave()
})
task.resume() //starts the API call
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50 //return 50 because get request to the top/anime/ endpoint returns a list of 50 anime
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//reuse cell prototype. Reuse the cell of class TopAnimeTableViewCell with identifier of same name
let cell = tableView.dequeueReusableCell(withIdentifier: "TopCell", for: indexPath) as! TopAnimeTableViewCell
guard let topAnimes = self.topResults else { //to ensure topAnimes is not nil
return cell
}
let anime = topAnimes.top[indexPath.row]
let title = anime.title
let animeId = anime.mal_id
cell.TopAnimeTitle.text = title
let imgUrlString = anime.image_url //image url in string
let imgURL = URL(string: imgUrlString)! //convert the image url from string to url so that we can download it. Since imgURL is an optional type, must unwrap it so use force-unwrap using '!' to abort execution if the optional value contains 'nil'
//this function .af.setImage(withURL: URL) from the pod AlomofireImage downloads the images from the imgURL and sets it to the UIImageView.
cell.TopImage.af.setImage(withURL: imgURL)
let item: AnimelistItem! = AnimelistItem(mal_id: animeId, image_url: imgUrlString, title: title, synopsis: anime.synopsis, episodes: anime.episodes)
cell.item = item
cell.viewController = self
return cell
}
//Segues to AnimeDetailsViewController when a cell is selected
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// if let cell = tableView.cellForRow(at: indexPath) as? TopAnimeTableViewCell {
// cell.didSelect(indexPath: indexPath as NSIndexPath)
// }
// print("selected")
let anime = topResults?.top[indexPath.row]
performSegue(withIdentifier: "showAnimeDetails", sender: anime)
}
func setUpSearchBar() {
searchResultsTableController = (storyboard?.instantiateViewController(withIdentifier: "SearchResultsViewController") as! SearchResultsViewController)
// Initializing with searchResultsController set to searchResultTableController means that
// UIsearchController will use this view controller to display the search results
searchController = UISearchController(searchResultsController: searchResultsTableController)
//Makes the Search Results Updater the searchResultTableController of class SearchResultsViewController. So whenever user types in the search bar, the updateSearchResults(for searchController: UISearchController) function in the SearchResultsViewController is called which will update the content in the SearchResultsViewController.
searchController.searchResultsUpdater = searchResultsTableController
// If we are using this same view controller to present the results
// dimming or obscuring it out wouldn't make sense. Should probably only set
// this to yes if using another controller to display the search results.
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.sizeToFit()
searchController.searchBar.enablesReturnKeyAutomatically = false
self.navigationItem.searchController = searchController //set search controller bar into the navigation bar
self.navigationItem.hidesSearchBarWhenScrolling = false //allow users to see the search bar even when scrolling
// Sets this view controller as presenting view controller for the search interface
definesPresentationContext = true //displays the search bar in the view controller properly
searchController.searchBar.placeholder = "Search anime by name"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showAnimeDetails" {
let vc = segue.destination as! AnimeDetailsViewController
vc.animeItem = (sender as! AnimeFromTop)
}
}
}
最后,这里是 SearchResultsViewController.swift 的代码
import UIKit
import AlamofireImage
class SearchResultsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
@IBOutlet weak var tableView: UITableView!
var searchResults: SearchResults? //property to store the array of dictionaries for animes that the search query returns
//Whenever user types in the search bar, thiss function is called to update the Search Results
override func viewDidLoad() {
super.viewDidLoad()
print("called")
}
func updateSearchResults(for searchController: UISearchController) {
if let inputText = searchController.searchBar.text, !inputText.isEmpty {
getSearchResults(searchQuery: inputText) //call
// filteredResults = searchResults
}
tableView.reloadData()
}
func getSearchResults(searchQuery: String) {
let urlString = "https://api.jikan.moe/v3/search/anime?q=\(searchQuery)" //url String
//Create a url object from the url String. Use guard so that if cannot be created as an url object, then provide optional error message. Created as an optional
// print(urlString)
guard let url = URL(string: urlString) else {
//if not able to create a url from urlString, just return
print("Not able to create url object from url String")
return
}
//Create a Data Task, which is how you perform actual API calls and networking tasks
//the completionHandler returns 3 optional parameters, but we only care about the Data and Error so we will do _ for discardable for the 2nd parameter URLResponse
print(url)
let task = URLSession.shared.dataTask(with: url, completionHandler: { [self]
data, _, error in
guard let data = data, error == nil else {
return
}
//do - catch function because the decoder function can actually throw an error so we want to account for that too
var results: SearchResults?
do {
results = try JSONDecoder().decode(SearchResults.self, from: data)
} catch {
print("Failed to Decode Error")
}
guard let final = results else {
return
} //final is the search query results array
// print(final.results)
searchResults = final
})
task.resume() //starts the API call
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults?.results.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchCell") as! SearchCell
guard let filteredResults = searchResults else {
return cell
}
let anime = filteredResults.results[indexPath.row]
let title = anime.title
let animeId = anime.mal_id
cell.searchAnimeTitle.text = title
let imgUrlString = anime.image_url //image url in string
let imgURL = URL(string: imgUrlString)!
cell.searchImage.af.setImage(withURL: imgURL)
let item: AnimelistItem! = AnimelistItem(
mal_id: animeId,
image_url: imgUrlString,
title: title,
synopsis: anime.synopsis,
episodes: anime.episodes ?? 0
)
cell.item = item
cell.viewController = self
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showAnimeDetails" {
let vc = segue.destination as! AnimeDetailsViewController
vc.animeItem = (sender as! AnimeFromTop)
}
}
}
在我的主故事板中,我有搜索结果视图控制器和发现场景视图控制器,它们都通过 segue 连接到动画视图详细信息视图控制器(目前都设置为模态)。同样,我试图让应用程序允许我在通过搜索结果找到动漫后,在点击时查看动漫的信息,就像我从发现选项卡中点击动漫时一样。我们将不胜感激。