Search code examples
iosswiftxcodeurl

How can I get my iOS app connected with API?


I am a beginner in iOS development. I was trying to use an api URl: https://www.arbeitnow.com/api/job-board-api in my job search iOS app. But nothing shows on my app. I tested the URL in POSTMAN and it returns json(but HTML in description part?). I wrote the code:

func getResults(completed: @escaping (Result<[Results], ErrorMessage>) -> Void) { 
    let urlString = "https://www.arbeitnow.com/api/job-board-api"
    guard let url = URL(string: urlString) else {return}    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in    
        if let _ = error {
            completed(.failure(.invalidData))
            return
        }   
        guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
            completed(.failure(.invalidResponse))
            return
        }
        guard let data = data else {
            completed(.failure(.invalidData))
            return
        } 
        do {
            let deconder = JSONDecoder()
            deconder.keyDecodingStrategy = .convertFromSnakeCase
            let results = try deconder.decode([Results].self, from: data)
            completed(.success(results))     
        } catch {
            completed(.failure(.invalidData))
        }
    }
    task.resume()
}

struct Results: Codable {
    let slug, companyName, title, resultsDescription: String
    let remote: Bool
    let url: String
    let tags, jobTypes: [String]
    let location: String
    let createdAt: Int
    enum CodingKeys: String, CodingKey {
        case slug
        case companyName = "company_name"
        case title
        case resultsDescription = "description"
        case remote, url, tags
        case jobTypes = "job_types"
        case location
        case createdAt = "created_at"
    }
}

I used the code in HomeViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    title = "Home"
    collectionView.backgroundColor = UIColor(named: "backgroundMain")
    collectionView.register(SearchViewCell.self, forCellWithReuseIdentifier: cellId)
    setupSearchBar()
    Service.shared.getResults() { [weak self] result in
        switch result {
        case .success(let results):
            print(results)
            self?.jobResults = results
            DispatchQueue.main.async {
                self?.collectionView.reloadData()
            }   
        case .failure(let error):
            print(error)
        }
    }
}

888

I don't know what is wrong with my code. Can anyone help?


Solution

  • You are discarding all meaningful error information, which will make this hard to diagnose. If you get an Error object, you should return that:

    enum WebServiceError: Error {
        case httpError(Data, Int)
    }
    
    func getResults(completion: @escaping (Result<[Results], Error>) -> Void) {
        let urlString = "https://www.arbeitnow.com/api/job-board-api"
        guard let url = URL(string: urlString) else {
            completion(.failure(URLError(.badURL)))
            return
        }
    
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let data = data,
                let response = response as? HTTPURLResponse,
                error == nil
            else {
                completion(.failure(error ?? URLError(.badServerResponse)))
                return
            }
    
            guard 200 ..< 300 ~= response.statusCode else {
                completion(.failure(WebServiceError.httpError(data, response.statusCode)))
                return
            }
    
            do {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                let results = try decoder.decode([Results].self, from: data)
                completion(.success(results.data))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    }
    

    So, that will,

    • if there was a URLSession error, tell you what the error was;
    • if there was a non-2xx status code, tell you what the code was (and return the body of the response, too, in case you want to look at that); and
    • if there was a parsing error, tell you what the parsing error was.

    Without something like this, that captures the salient error information, you are flying blind.


    In this case, the error is that you are parsing for [Results], but the structure is a dictionary, whose key is data and whose value is a [Results]. You are missing an object for this dictionary that wraps the [Results].

    struct ResponseObject: Decodable {
        let data: [Posting]
        let links: Links
        let meta: Meta
    }
    
    struct Posting: Decodable {
        let slug, companyName, title, description: String
        let remote: Bool
        let url: String
        let tags, jobTypes: [String]
        let location: String
        let createdAt: Int
    }
    
    struct Links: Decodable {
        let first: URL?
        let last: URL?
        let prev: URL?
        let next: URL?
    }
    
    struct Meta: Decodable {
        let currentPage: Int
        let path: URL
        let perPage: Int
        let from: Int
        let to: Int
        let terms: String
        let info: String
    }
    
    func getResults(completion: @escaping (Result<[Posting], Error>) -> Void) {
        let urlString = "https://www.arbeitnow.com/api/job-board-api"
        guard let url = URL(string: urlString) else {
            completion(.failure(URLError(.badURL)))
            return
        }
    
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let data = data,
                let response = response as? HTTPURLResponse,
                error == nil
            else {
                completion(.failure(error ?? URLError(.badServerResponse)))
                return
            }
    
            guard 200 ..< 300 ~= response.statusCode else {
                completion(.failure(WebServiceError.httpError(data, response.statusCode)))
                return
            }
    
            do {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                let results = try decoder.decode(ResponseObject.self, from: data)
                completion(.success(results.data))
            } catch {
                completion(.failure(error))
            }
        }
        task.resume()
    }