Result of initializer unused - json

I was just wondering why I get this error. I have struct as 'Model' and initialize it from the extract_data function, which is called in viewDidLoad(), but when I set a breakpoint in the struct I can see that everything was initialised properly and whats strange to is if i take my label and set it to my Struct.base String, it doesn't work somehow...I'm a beginner so an explanation would really be appreciated:)
Here is my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
get_data("https://api.fixer.io/latest?base=CHF")
testLabel.text = TestStruct().base
//ANOTHER ERROR HERE (MISSING ARGUMENT FOR PARAMETER "base" IN CALL)
}
#IBOutlet var testLabel: UILabel!
var test:[TestStruct] = [TestStruct]()
func get_data(_ link:String){
let url:URL = URL(string: link)!
let sessioin = URLSession.shared
let request = URLRequest(url: url)
let testtask = sessioin.dataTask(with: request, completionHandler: { (data, response, error) in
self.extract_data(data)
})
testtask.resume()
}
func extract_data(_ data:Data?){
let json:Any?
if(data==nil){
return
}
do {
json = try JSONSerialization.jsonObject(with: data!, options: [])
//The whole JSON
let data_dictonary = json as? [String: Any]
//Just the "base"
let data_base = data_dictonary?["base"] as! String
print(data_base)
//Just the "date"
let data_date = data_dictonary?["date"] as! String
print(data_date)
//All the exchange rates
let data_rates = data_dictonary?["rates"] as! [String: Double]
print(data_rates)
TestStruct.init(base: data_base, date: data_date, rates: data_rates)
//ERROR IS HERE
}catch {
return
}
}
Here is my Model:
struct TestStruct {
var base: String
var date: String
var rates: [String: Double]
init(base: String, date: String, rates: [String:Double]) {
self.base = base
self.date = date
self.rates = rates
}
}

Your syntax for calling the initializer is incorrect. Change:
TestStruct.init(base: data_base, date: data_date, rates: data_rates)
to:
let someVar = TestStruct(base: data_base, date: data_date, rates: data_rates)
And the error with:
testLabel.text = TestStruct().base
is that you are trying to access the base property on a newly created TestStruct instance but you have no init on your TestStruct with no parameters.
You need to create TestStruct by passing all of the required arguments but that seems pointless since all you want is its base and you need the base to create the instance to begin with.

Related

decoding a json to generic array or class in swift

How do you decode json to a generic model in swift?
In java for decoding json I use GSON and in general it does not matter I use <T<E>> or ArrayList<E>.In swift Array is a struct and can't be inheritance and it has not implemented Decodable.
I'm looking for a generic elegant class to use in all my web service.
My scenario:
I have json response
{
"status": true,
"message": "",
"code": 200,
"response": [{
"id": 43
}]
}
and a generic reponse model like this from web services:
class GeneralResponse< T : Decodable >:NSObject,Decodable{
var status = false
var message = ""
var code = -1
var response : T?
private enum CodingKeys: String, CodingKey {
case status
case message
case code
case response
}
required public init(from decoder: Decoder) throws{
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(Bool.self, forKey: .status)
message = try container.decode(String.self, forKey: .message)
code = try container.decode(Int.self, forKey: .code)
response = try container.decode(T.self, forKey: .response)
}
}
class ItemDemoModel:Decodable {
var id = -1
private enum ItemDemModelCodingKeys : String, CodingKey {
case id
}
required init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: ItemDemModelCodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
}
}
response variable can be ItemDemoModel or an array of ItemDemoModel.
For example:
It can be GeneralResponse<Array<ItemDemoModel>>>
or GeneralResponse<ItemDemoModel>>
thanks.
If you declare a Decodable properties with same name as the key in json then you don't really need an enum to define Coding keys and an initializer to manually map every property with the key.
Also, there is no need to inherit from NSObject in Swift until you have a specific use case for that. Looking at the declaration, it seems unnecessary so your GeneralResponse can be redeclared as simple as this,
class GeneralResponse<T: Decodable>: Decodable {
var code: Int
var status: Bool
var message: String?
var response : T?
}
Similarly, ItemDemoModel can be declared as this,
class ItemDemoModel: Decodable {
var id: Int
}
Now you can setup your service as below to get the GeneralResponse<T> for any request,
struct RequestObject {
var method: String
var path: String
var params: [String: Any]
}
class WebService {
private let decoder: JSONDecoder
public init(_ decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}
public func decoded<T: Decodable>(_ objectType: T.Type,
with request: RequestObject,
completion: #escaping (GeneralResponse<T>?, Error?) -> Void) {
// Here you should get data from the network call.
// For compilation, we can create an empty object.
let data = Data()
// Now parsing
do {
let response = try self.decoder.decode(GeneralResponse<T>.self, from: data)
completion(response, nil)
} catch {
completion(nil, error)
}
}
}
Usage
let request = RequestObject(method: "GET", path: "https://url.com", params: [:])
WebService().decoded([ItemDemoModel].self, with: request) { (response, error) in
if let items = response?.response {
print(items)
}
}
P.S; You must be used to declare arrays and dictionaries as below,
let array: Array<SomeType>
let dictionary: Dictionary<String: SomeType>
let arrayOfDictionary: Array<Dictionary<String: SomeType>>
But with Swift's type inference, you can declare an array and a dictionary as simple as below,
let array: [SomeType]
let dictionary: [String: SomeType]
let arrayOfDictionary: [[String: SomeType]]
Here you have a function you may want to use in order to decode your JSON:
func decode<T: Decodable>(_ data: Data, completion: #escaping ((T) -> Void)) {
do {
let model = try JSONDecoder().decode(T.self, from: data)
completion(model)
} catch {
log(error.localizedDescription, level: .error)
}
}
So you can just call your function like:
decode(data, completion: { (user: User) in
// Do something with your parsed user struct or whatever you wanna parse
})
I hope this helps :D
Array<T> conforms to Decodable if T conforms to Decodable, so GeneralResponse<[ItemDemoModel]> won't produce any errors.
As shown here:
You can simply do this:
let decoder = JSONDecoder()
let obj = try decoder.decode(type, from: json.data(using: .utf8)!)

getting JSON Data with Swift 4 and Xcode 9

I've been trying to work with the below code to get my JSON data. It returns "Error after loading". I am using this JSON data in another application and it works. I'm trying to implement the new simplified method using Swift 4. The code does work to the point of the print statement "downloaded".
class MortgageRatesVC: UIViewController {
final let url = URL (string:"http://mortgous.com/JSON/currentRatesJSON.php")
override func viewDidLoad() {
super.viewDidLoad()
downloadJason()
}
func downloadJason () {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Oops Call for Help")
return
}
print("downloaded")
do
{
let decoder = JSONDecoder()
let rates = try decoder.decode([LenederRates].self, from: data)
print(rates)
} catch {
print("Error after loading")
}
}.resume()
}
}
Class
class LenederRates: Codable {
let key : String
let financial_institution : String
let variable_rate : String
let six_months : String
let one_year : String
let two_year : String
let three_year : String
let four_year : String
let five_year : String
// let date : Date
init(key: String, financial_institution: String, variable_rate: String, six_months: String, one_year: String, two_year: String, three_year: String, four_year: String, five_year: String) {
self.key = key
self.financial_institution = financial_institution
self.variable_rate = variable_rate
self.six_months = six_months
self.one_year = one_year
self.two_year = two_year
self.three_year = three_year
self.four_year = four_year
self.five_year = five_year
}
}
The problem is the missing property date in your Codable class. You need to set the decoder dateDecodingStrategy to .formatted and pass a fixed format dateFormatter. Btw I suggest changing your class for a struct, change your property names using the Swift naming convention camelCase and provide the custom CodingKeys:
struct LenederRates: Codable {
let key: String
let financialInstitution : String
let variableRate: String
let sixMonths: String
let oneYear: String
let twoYear: String
let threeYear: String
let fourYear: String
let fiveYear: String
let date: Date
private enum CodingKeys: String, CodingKey {
case key, financialInstitution = "financial_institution", variableRate = "variable_rate", sixMonths = "six_months", oneYear = "one_year", twoYear = "two_year", threeYear = "three_year", fourYear = "four_year", fiveYear = "five_year", date
}
}
let mortgousURL = URL(string:"http://mortgous.com/JSON/currentRatesJSON.php")!
URLSession.shared.dataTask(with: mortgousURL) { data, urlResponse, error in
guard let data = data else { return }
do {
let dateFormat = DateFormatter()
dateFormat.locale = Locale(identifier: "en_US_POSIX")
dateFormat.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormat)
let rates = try decoder.decode([LenederRates].self, from: data)
print(rates)
} catch {
print(error)
}
}.resume()

How to add a (Swift4) init to the Decodable protocol

I am trying to create a Codable extension that is capable of initialising a Decodable (Swift 4) object with only a json string. So what Should work is:
struct MyObject: Decodable {
var title: String?
}
let myObject = MyObject(json: "{\"title\":\"The title\"}")
I think this means that I should create an init that calls the self.init with a Decoder. Here is the code that I came up with:
public init?(json: String) throws {
guard let decoder: Decoder = GetDecoder.decode(json: json)?.decoder else { return }
try self.init(from: decoder) // Who what should we do to get it so that we can call this?
}
That code is capable of getting the decoder, but I get a compiler error on calling the init. The error that I get is:
'self' used before self.init call
Does this mean that there is no way to add an init to the Decodable protocol?
For the complete source code see the codable extension on github
update:
After debugging the solution below from #appzYourLive I found out that I had a conflict with the init(json: initialisers on Decodable and Array. I just published a new version of the extension to GitHub. I also added the solution as a new answer to this question.
A possible workaround
DecodableFromString
A possible solution is defining another protocol
protocol DecodableFromString: Decodable { }
with its own initializer
extension DecodableFromString {
init?(from json: String) throws {
guard let data = try json.data(using: .utf8) else { return nil }
guard let value = try? JSONDecoder().decode(Self.self, from: data) else { return nil }
self = value
}
}
Conforming to DecodableFromString
Now you need to conform your type to DecodableFromString
struct Person:Codable, DecodableFromString {
let firstName: String
let lastName: String
}
Result
And finally given a JSON
let json = """
{
"firstName": "Luke",
"lastName": "Skywalker"
}
"""
you can build your value
if let luke = try? Person(from: json) {
print(luke)
}
Person(firstName: "Luke", lastName: "Skywalker")
This is what can be done:
extension Decodable {
init?(jsonString: String) throws {
guard let jsonData = jsonString.data(using: .utf8) else { return nil }
self = try JSONDecoder().decode(Self.self, from: jsonData)
}
}
See: https://bugs.swift.org/browse/SR-5356
[UPD] Problem fixed in XCode 9 beta 3 (see the above link for details).
My original problem was caused by these 2 reasons:
A conflict between an init with an identical signature that I added as an
extension to Array which is also Codable when its inner objects are
Codable.
A swift compiler bug that causes a problem when you use a failable
initialiser. See https://bugs.swift.org/browse/SR-5356
So since there is an issue with using a failable initialiser I ended up with:
public extension Decodable {
init(json: String) throws {
guard let data = json.data(using: .utf8) else { throw CodingError.RuntimeError("cannot create data from string") }
try self.init(data: data, keyPath: keyPath)
}
init(data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
enum CodingError : Error {
case RuntimeError(String)
}
I also made a variation where you kan use a keyPath to jump to a certain section:
public extension Decodable {
init(json: String, keyPath: String? = nil) throws {
guard let data = json.data(using: .utf8) else { throw CodingError.RuntimeError("cannot create data from string") }
try self.init(data: data, keyPath: keyPath)
}
init(data: Data, keyPath: String? = nil) throws {
if let keyPath = keyPath {
let topLevel = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)
guard let nestedJson = (topLevel as AnyObject).value(forKeyPath: keyPath) else { throw CodingError.RuntimeError("Cannot decode data to object") }
let nestedData = try JSONSerialization.data(withJSONObject: nestedJson)
self = try JSONDecoder().decode(Self.self, from: nestedData)
return
}
self = try JSONDecoder().decode(Self.self, from: data)
}
}
You can find the complete code of my Decodable and Encodable extensions below. It's also in my GitHub subspec. With this extension you can use code like:
struct YourCodableObject : Codable {
var naam: String?
var id: Int?
}
let json = yourEncodableObjectInstance.toJsonString()
let data = yourEncodableObjectInstance.toJsonData()
let newObject = try? YourCodableObject(json: json)
let newObject2 = try? YourCodableObject(data: data)
let objectArray = try? [YourCodableObject](json: json)
let objectArray2 = try? [YourCodableObject](data: data)
let newJson = objectArray.toJsonString()
let innerObject = try? TestCodable(json: "{\"user\":{\"id\":1,\"naam\":\"Edwin\"}}", keyPath: "user")
try initialObject.saveToDocuments("myFile.dat")
let readObject = try? TestCodable(fileNameInDocuments: "myFile.dat")
try objectArray.saveToDocuments("myFile2.dat")
let objectArray3 = try? [TestCodable](fileNameInDocuments: "myFile2.dat")
And here are the 2 extensions:
//
// Codable.swift
// Stuff
//
// Created by Edwin Vermeer on 28/06/2017.
// Copyright © 2017 EVICT BV. All rights reserved.
//
enum CodingError : Error {
case RuntimeError(String)
}
public extension Encodable {
/**
Convert this object to json data
- parameter outputFormatting: The formatting of the output JSON data (compact or pritty printed)
- parameter dateEncodinStrategy: how do you want to format the date
- parameter dataEncodingStrategy: what kind of encoding. base64 is the default
- returns: The json data
*/
public func toJsonData(outputFormatting: JSONEncoder.OutputFormatting = .compact, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .deferredToDate, dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .base64Encode) -> Data? {
let encoder = JSONEncoder()
encoder.outputFormatting = outputFormatting
encoder.dateEncodingStrategy = dateEncodingStrategy
encoder.dataEncodingStrategy = dataEncodingStrategy
return try? encoder.encode(self)
}
/**
Convert this object to a json string
- parameter outputFormatting: The formatting of the output JSON data (compact or pritty printed)
- parameter dateEncodinStrategy: how do you want to format the date
- parameter dataEncodingStrategy: what kind of encoding. base64 is the default
- returns: The json string
*/
public func toJsonString(outputFormatting: JSONEncoder.OutputFormatting = .compact, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .deferredToDate, dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .base64Encode) -> String? {
let data = self.toJsonData(outputFormatting: outputFormatting, dateEncodingStrategy: dateEncodingStrategy, dataEncodingStrategy: dataEncodingStrategy)
return data == nil ? nil : String(data: data!, encoding: .utf8)
}
/**
Save this object to a file in the temp directory
- parameter fileName: The filename
- returns: Nothing
*/
public func saveTo(_ fileURL: URL) throws {
guard let data = self.toJsonData() else { throw CodingError.RuntimeError("cannot create data from object")}
try data.write(to: fileURL, options: .atomic)
}
/**
Save this object to a file in the temp directory
- parameter fileName: The filename
- returns: Nothing
*/
public func saveToTemp(_ fileName: String) throws {
let fileURL = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(fileName)
try self.saveTo(fileURL)
}
#if os(tvOS)
// Save to documents folder is not supported on tvOS
#else
/**
Save this object to a file in the documents directory
- parameter fileName: The filename
- returns: true if successfull
*/
public func saveToDocuments(_ fileName: String) throws {
let fileURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(fileName)
try self.saveTo(fileURL)
}
#endif
}
public extension Decodable {
/**
Create an instance of this type from a json string
- parameter json: The json string
- parameter keyPath: for if you want something else than the root object
*/
init(json: String, keyPath: String? = nil) throws {
guard let data = json.data(using: .utf8) else { throw CodingError.RuntimeError("cannot create data from string") }
try self.init(data: data, keyPath: keyPath)
}
/**
Create an instance of this type from a json string
- parameter data: The json data
- parameter keyPath: for if you want something else than the root object
*/
init(data: Data, keyPath: String? = nil) throws {
if let keyPath = keyPath {
let topLevel = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)
guard let nestedJson = (topLevel as AnyObject).value(forKeyPath: keyPath) else { throw CodingError.RuntimeError("Cannot decode data to object") }
let nestedData = try JSONSerialization.data(withJSONObject: nestedJson)
let value = try JSONDecoder().decode(Self.self, from: nestedData)
self = value
return
}
self = try JSONDecoder().decode(Self.self, from: data)
}
/**
Initialize this object from an archived file from an URL
- parameter fileNameInTemp: The filename
*/
public init(fileURL: URL) throws {
let data = try Data(contentsOf: fileURL)
try self.init(data: data)
}
/**
Initialize this object from an archived file from the temp directory
- parameter fileNameInTemp: The filename
*/
public init(fileNameInTemp: String) throws {
let fileURL = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(fileNameInTemp)
try self.init(fileURL: fileURL)
}
/**
Initialize this object from an archived file from the documents directory
- parameter fileNameInDocuments: The filename
*/
public init(fileNameInDocuments: String) throws {
let fileURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(fileNameInDocuments)
try self.init(fileURL: fileURL)
}
}

optional type String? not unwrapped

I have a PHP page that is on my webserver that interacts with a mysql database called grabmapinfo.php
The output of the page is [{"companyname":"Brunos Burgers","companyphone":"7745632382","companytown":"858 Western Ave, Lynn, MA 01905"}]
Now I have this Swift code, which I want to get the info from the database, geocode the address to latitude and longitude, plot the annotation on the map, change the annotation image and title, and make a circle with a radius of 5 with the pin being in the center.
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.buyerMapView1.showsUserLocation = true
let url = NSURL(string: "https://alanr917.000webhostapp.com/grabmapinfo.php")
var request = URLRequest(url:url! as URL)
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) -> Void in
if error != nil {
// Display an alert message
print(error)
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [[String:AnyObject]] {
for item in json {
// Get company info from DB
let companyname = item["companyname"] as? String
let companyphone = item["companyphone"] as? String
let companytown = item["companytown"] as? String
print("Company : \(companyname)")
print("Phone : \(companyphone)")
print("Address : \(companytown)")
let address = companytown
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address, completionHandler: {
(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = companyname
pa.imageName = #imageLiteral(resourceName: "growerAnnotation")
self.buyerMapView1.addAnnotation(pa)
let center = annotation.coordinate
let circle = MKCircle(center: center, radius: 5) // change the 5 later to however many miles the grower purchased
self.buyerMapView1.add(circle)
}
})
}
}
} catch {
print(error)
}
})
}
But i get an error that says the optional type String? is not unwrapped and it errors out and wont build.
Does anyone see where I'm going wrong? Thanks!
companyTown is declared as an optional string and the geocodeAddressString method takes a string. You need to unwrap the option before calling it.
if let addressUnwrapped = address {
geocoder.geocodeAddressString(addressUnwrapped, completionHandler: {
(placemarks: [AnyObject]!, error: NSError!) -> Void in
...
})
}
Please check the comments through the code for more detailed explanation on the problems that I found in your code:
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var buyerMapView1: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
buyerMapView1.showsUserLocation = true
// first unwrap your url
guard let url = URL(string: "https://alanr917.000webhostapp.com/grabmapinfo.php") else { return }
print("url:",url)
// no need to create a request. just a url is fine and you don't need to specify the parameters type. Let the compiler infer it.
URLSession.shared.dataTask(with: url) { data, response, error in
// unwrap your data and make sure there is no error
guard let data = data, error == nil else {
print(error ?? "nil")
return
}
// you should update the UI from the main queue
DispatchQueue.main.async {
print("data:", data)
do {
if let array = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] {
for dict in array {
// make sure you unwrap your dictionary strings
let companyname = dict["companyname"] as? String ?? ""
let companyphone = dict["companyphone"] as? String ?? ""
let companytown = dict["companytown"] as? String ?? ""
print("Company:", companyname)
print("Phone:", companyphone)
print("Address:", companytown)
let address = companytown
let geocoder = CLGeocoder()
// again let the compiler infer the types vvv vvv
geocoder.geocodeAddressString(address) { placemarks, error in
if let placemark = placemarks?.first,
let coordinate = placemark.location?.coordinate {
let pa = MKPointAnnotation()
pa.coordinate = coordinate
pa.title = companyname
self.buyerMapView1.addAnnotation(pa)
let center = pa.coordinate // where does this coordinate come from??
let circle = MKCircle(center: center, radius: 5)
self.buyerMapView1.add(circle)
}
}
}
}
} catch {
print(error)
}
}
// you forgot to call resume to start your data task
}.resume()
}
}

Loading Serialised JSON Into Table?

Im having a really hard time wrapping my head around this process, I have made an API call and received back JSON, I then use a model to serialise the JSON so it can be used easily in my View Controller Table, but im having issues with how to call the API in the View Controller to have the result fit into my serialisation model. I hope I explained it correctly?
Here is the code of my API Request:
open class ApiService: NSObject {
open func getData(completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
let requestUrl = "https://wger.de/api/v2/exercise/?format=json"
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success( let data):
completionHandler(data as? NSDictionary, nil)
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
}
}
and here is the code of my serialisation
final public class Exercise: ResponseObjectSerializable {
var id: Int!
var description: String!
var name: String!
var muscles: String!
var equipment: String!
public init?(response: HTTPURLResponse, representation: Any) {
guard
let representation = representation as? [String: Any],
let id = representation["id"] as? Int,
let description = representation["description"] as? String,
let name = representation["name"] as? String,
let muscles = representation["muscles"] as? String,
let equipment = representation["equipment"] as? String
else { return nil }
self.id = id
self.description = description
self.name = name
self.muscles = muscles
self.equipment = equipment
}
}
But I cant work out how to fit this into my view controller function call which is currently this
let apiService = ApiService()
let searchController = UISearchController(searchResultsController: nil)
var arrRes: [String] = []
var filtered: [String] = []
var searchActive: Bool = false
var id: Int?
var description: String?
var name: String?
var muscles: String?
var equipment: String?
override func viewDidLoad() {
super.viewDidLoad()
exercisesTableView.delegate = self
exercisesTableView.dataSource = self
exerciseSearchBar.delegate = self
getApiData()
}
func getApiData() {
let _ = apiService.getData() {
(data, error) in
if let data = data {
if let arr = data["results"] as? [String] {
self.arrRes = arr
self.exercisesTableView.reloadData()
}
} else if let error = error {
print(error)
}
}
}
First of all the HTTP response does not affect the custom class at all so I left it out.
Second of all the values for keys muscles and equipment are arrays rather than strings.
Third of all since the JSON data seems to be immutable declare the properties in the class as constant (let)
With a few slightly changes this is the custom class
final public class Exercise {
let id : Int
let description: String
let name: String
let muscles : [Int]
let equipment : [Int]
public init?(dictionary: [String: Any]) {
guard
let id = dictionary["id"] as? Int,
let description = dictionary["description"] as? String,
let name = dictionary["name"] as? String,
let muscles = dictionary["muscles"] as? [Int],
let equipment = dictionary["equipment"] as? [Int]
else { return nil }
self.id = id
self.description = description
self.name = name
self.muscles = muscles
self.equipment = equipment
}
}
Then you have to declare the data source array
var exercises = [Exercise]()
And in the method getApiData() populate the array
...
if let results = data["results"] as? [[String:Any]] {
for result in results {
if let exercise = Exercise(dictionary: result) {
self.exercises.append(exercise)
}
}
self.exercisesTableView.reloadData() // might be dispatched on the main thread.
}
Note: Any is used in Swift 3, in Swift 2 replace all occurrences of Any with AnyObject