Search code examples
iosjsonswiftalamofireobjectmapper

Alamofire and ObjectMapper to TableView in Swift


I am new to swift and trying to fetch json from an api and show it to UITableView but it won't show.

This is the json:

{
    "success": true,
    "user_id": "somestringhere",
    "devices": [
        {
            "id": "somestringhere",
            "source": "somestringhere",
            "source_text": "somestringhere",
            "logo_url": "somestringhere",
            "is_connected": "0"
        },
        {

For mapping the json data:

import Foundation
import ObjectMapper

class Devices: Mappable {
required init?(map: Map) {
}

    func mapping(map: Map) {
        id<-map["id"]
        source<-map["source"]
        sourceText<-map["source_text"]
        logo<-map["logo_url"]
        isConnected<-map["is_connected"]  
    }
   
    var id : String?
    var source : String?
    var sourceText : String?
    var logo : String?
    var isConnected : Bool?
    
}

My Controller:

import Foundation
import UIKit
import SwiftyJSON
import Alamofire
import ObjectMapper

class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var devices : [Devices]?

    override func viewDidLoad() {
        super.viewDidLoad()
      
        tableView.register(HomeTableViewCell.nib(), forCellReuseIdentifier: HomeTableViewCell.identifier)
        tableView.delegate = self
        tableView.dataSource = self
        loadData()
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return devices?.count ?? 0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: HomeTableViewCell.identifier, for: indexPath) as! HomeTableViewCell
        
        cell.setup(title: "\(devices?[indexPath.row].sourceText)", iconLink: "\(devices?[indexPath.row].logo)")
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120
    }
    
    func loadData(){
        let base = Config().base
        let endPoint = Config.EndPoint()
        let patientId = User().id
            
                let url = base + endPoint.showDevices
                let header: HTTPHeaders = [
                    "Content-Type": "application/json",
                ]
                let params: Parameters = [
                    "patient_id" : patientId
                ]
                
                let code = Code(id: id)
                AF.request(url, method: .get, parameters: params, encoding: URLEncoding.default, headers:header).validate()
                    .response{ (response) in
                        switch response.result {
                        case .success(let value):
                            let json = JSON(value)
                           
                          self.devices = Mapper<Devices>().mapArray(JSONString: json.rawString()!)
            self.tableView.reloadData()
                           
                         case .failure(let error):
                            print(error)
  }
 }    
}
}

I tried printing the device variable after the Mapper

self.devices = Mapper<PatientDevices>().mapArray(JSONString: json.rawString()!)
print(self.devices)

and shows me

Optional([ProjectName.Devices])

I tried to print json response data and request successful however I couldn't add it to tableview. What should I do to solve this problem? I don't know what I am doing wrong.


Solution

  • First of all – as always – since Swift 4 libraries like ObjectMapper and SwiftyJSON have become obsolete in favor of built-in, efficient Codable protocol. Avoid tutorials which suggest those outdated libraries.

    Regarding your mistake you are ignoring the root object, the object with keys success, user_id and devices.

    This is a solution using Decodable

    • Drop SwiftyJSON and ObjectMapper

    • Replace the structs with

        struct Root : Decodable {
            let success : Bool
            let userId : String
            let devices : [Device]
        }
      
        struct Device : Decodable {
            let id : String
            let source : String
            let sourceText : String
            let logoUrl : URL
            let isConnected : String // this value is String, not Bool
        }
      
    • Replace loadData with

        func loadData(){
            let base = Config().base
            let endPoint = Config.EndPoint()
            let patientId = User().id
      
            let url = base + endPoint.showDevices
            let header: HTTPHeaders = [
                "Content-Type": "application/json",
            ]
            let params: Parameters = [
                "patient_id" : patientId
            ]
      
            let code = Code(id: id)
            AF.request(url, parameters: params, headers:header).validate()
                .responseData { (response) in
                    switch response.result {
                        case .success(let data):
                            do {
                                let decoder = JSONDecoder()
                                decoder.keyDecodingStrategy = .convertFromSnakeCase
                                let result = try decoder.decode(Root.self, from: data)
                                self.devices = result.devices
                                self.tableView.reloadData()
                            } catch { print(error) }
      
                        case .failure(let error):
                            print(error)
                    }
            }
        }
      

      The Alamofire response returns raw data and the keyDecodingStrategy converts the snake_cased keys to camelCased struct members and logoUrl is decoded as URL

    • Finally rename the data source array to

        var devices = [Device]()
      

      and remove all question marks after devices