Search code examples
iosswiftuikitalamofire

I can't get data from api with Alamofire


I'm trying to make an IMDb-like application using the tmdb API, I've completed the first parts of the application, but I can't get the data from the API, I've been trying for more than a day, but I couldn't solve the problem.

print(viewModel.movie?.results?[indexPath.item].originalTitle ?? "") in cellForItemAt in homeViewController As a result of this code, the originalTitle should be printed, but it does not print.

  • NetworkHelper
    import Foundation
    enum  HTTPMethods: String{
        case get = "GET"
        case post = "POST"
    }
    enum    ErrorTypes: String,Error{
        case invalidData = "Invalid data"
        case invalidURL = "Invalid url"
        case generalError = "An error happened"
    }
    
    
    class NetworkHelper{
        static let shared = NetworkHelper()
        
        private let baseURL = "https://api.themoviedb.org/3/"
        private let apiKey = "509ef93252a59761a3c353f9ea114de0"
        
        func requestUrl(url: String) -> String {
       return baseURL + url + "?api_key=\(apiKey)"
           
            
        }
        
        
    }
  • NetworkManager
    import Alamofire
    
    class NetworkManager {
        static let shared = NetworkManager()
        
        func request<T: Codable>(type: T.Type,
                                 url: String,
                                 method: HTTPMethod,
                                 completion: @escaping((Result<T, ErrorTypes>)->())) {
            AF.request(url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? " ", method: method).responseData { response in
                switch response.result {
                case .success(let data):
                    self.handleResponse(data: data) { response in
                        completion(response)
    
                    }
                case .failure(_):
                    completion(.failure(.generalError))
                }
            }
            
            
        }
        
        fileprivate func handleResponse<T: Codable>(data: Data,completion: @escaping((Result<T, ErrorTypes>)->())){
            do{
                let result = try JSONDecoder().decode(T.self, from: data)
                completion(.success(result))
            }
            catch{
                completion(.failure(.invalidData))
            }
        }
        
    }

  • HomeHelper
    import Foundation
    
    enum HomeEndpoint: String {
        case popular = "movie/popular"
        
        var path: String {
            switch self{
            case.popular:
                return NetworkHelper.shared.requestUrl(url: HomeEndpoint.popular.rawValue)
    
            }
        }
    }

-HomeManager

import Foundation

protocol HomeManagerProtocol {
    func getCatagoryMovies(complete:@escaping((Movie?,Error?)->()))
}

class HomeManager{
    static let shared = HomeManager()
    
    func getCatagoryMovies(complete:@escaping((Movie?,Error?)->())){
        NetworkManager.shared.request(type: Movie.self,
                                      url: HomeEndpoint.popular.path,
                                      method:.get) { response in
            switch response{
            case .success(let data):
                complete(data,nil)
            case .failure(let error):
                complete(nil,error)
            }
        }
    }

}

-Movie

import Foundation

// MARK: - Movie
struct Movie: Codable {
    let page: Int?
    let results: [MovieResult]?
    let totalPages, totalResults: Int?

    enum CodingKeys: String, CodingKey {
        case page, results
        case totalPages = "total_pages"
        case totalResults = "total_results"
    }
}

// MARK: - MovieResult
struct MovieResult: Codable  {
    let adult: Bool?
    let backdropPath: String?
    let genreIDS: [Int]?
    let id: Int?
    let originalLanguage, originalTitle, overview: String?
    let popularity: Double?
    let posterPath, releaseDate, title: String?
    let video: Bool?
    let voteAverage: Double?
    let voteCount: Int?
    let character, creditID: String?
    let order: Int?
    let department, job: String?
}

-HomeViewModel

import Foundation


class HomeViewModel{
    let manager = HomeManager.shared
    
    var movie: Movie?
    var errorCallBack: ((String)->())?
    var successCallBack: (()->())?
    
    func getCatagoryItems(){
        manager.getCatagoryMovies { [weak self ]Movie, error in
            if let error = error{
                self?.errorCallBack?(error.localizedDescription)

            }else{
                self?.movie = Movie
                self?.successCallBack?()
            }
        }
    }
   
    
    func numberOfItems() -> Int{
        movie?.results?.count ?? 0
        
    }
}
 

-HomeViewController

import UIKit

class  HomeViewController: UIViewController {
    
    @IBOutlet private weak var collectionView: UICollectionView!
    let viewModel = HomeViewModel()
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionSetup()
        viewModelConfiguration()
        
        
    }
    
    fileprivate func collectionSetup(){
        collectionView.registerCell(type: VerticalCollectionViewCell.self)
    }
    
    fileprivate func viewModelConfiguration(){
        
        viewModel.getCatagoryItems()
        viewModel.errorCallBack = { [weak self] errorMesage in
            print("error: \(errorMesage)")
        }
        viewModel.successCallBack = { [weak self] in
            self?.collectionView.reloadData()
            
        }
    }


}
extension HomeViewController:UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel.numberOfItems()
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell: VerticalCollectionViewCell = collectionView.dequeueCell(for: indexPath)
        print(viewModel.movie?.results?[indexPath.item].originalTitle ?? "")
        cell.backgroundColor = .red
        return cell
        
    }
        
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.frame.width * 327 / 375, height: 120)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        CGSize(width: collectionView.frame.width, height: 365)

    }
    
    
}

Solution

  • You can't get the originalTitle to display because you are not decoding the server response with the correct data model, specifically MovieResult.

    Try this, works for me:

     struct MovieResult: Identifiable, Codable {  // <-- here
         let id: Int
         let adult, video: Bool
         let genreIDS: [Int]
         let originalLanguage, originalTitle, overview: String
         let posterPath, releaseDate, title, backdropPath: String
         let popularity, voteAverage: Double
         let voteCount: Int
    
         enum CodingKeys: String, CodingKey {  // <-- here
             case adult, id, title, video, overview, popularity
             case backdropPath = "backdrop_path"
             case genreIDS = "genre_ids"
             case originalLanguage = "original_language"
             case originalTitle = "original_title"
             case posterPath = "poster_path"
             case releaseDate = "release_date"
             case voteAverage = "vote_average"
             case voteCount = "vote_count"
         }
     }