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,

    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()
    }