从搜索结果查看详细信息

时间:2021-05-28 04:56:49

标签: ios swift iphone xcode

我正在开发一个简单的动漫应用项目,您可以在其中查看热门动漫列表、搜索任何动漫、将它们添加到已观看、要观看或正在观看的部分以及查看信息(例如概要)例如)点击动画时。大多数情况下,除了从搜索结果中查看动漫信息外,其他一切都正常,例如在搜索结果中点击火影忍者的信息时无法查看它。

这是我的 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 连接到动画视图详细信息视图控制器(目前都设置为模态)。同样,我试图让应用程序允许我在通过搜索结果找到动漫后,在点击时查看动漫的信息,就像我从发现选项卡中点击动漫时一样。我们将不胜感激。

0 个答案:

没有答案