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
Related
I follow a lesson from one course
And I need to get json, but i want get another json than in a lesson.
So this is my json:
https://api.scryfall.com/cards/search?q=half
And code:
struct Card {
var cardId: String
var name: String
var imageUrl: String
var text: String
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String,
let cardId = dict["cardId"] as? String,
let imageUrl = dict["imageUrl"] as? String,
let text = dict["text"] as? String else { return nil }
self.cardId = cardId
self.name = name
self.imageUrl = imageUrl
self.text = text
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping(GetCardResponse) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
do {
print ("ok1")
let response = try GetCardResponse(json: json)
print ("ok2")
completion(response)
} catch {
print(error)
}
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
print(json)
} catch {
print(error)
}
}.resume()
}
}
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let array = json as? [[String: AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in array {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
Problem in struct GetCardResponse and [[String: AnyObject]] because I dont know how to parse this type of json. I tried to change them in the likeness of json. But I dont really understand how it works and in which part of code i need to put json["data"] or something like this... Help pls. I just want get json fields tcgplayer_id, name, art_crop
As of your code, you can parse the required details as:
struct Card {
var cardId: String = ""
var name: String = ""
var imageUrl: String = ""
var text: String = ""
init(dict: [String: Any]) {
if let obj = dict["name"] {
self.name = "\(obj)"
}
if let obj = dict["tcgplayer_id"] {
self.cardId = "\(obj)"
}
if let obj = dict["image_uris"] as? [String:Any], let url = obj["art_crop"] {
self.imageUrl = "\(url)"
}
if let obj = dict["oracle_text"] {
self.text = "\(obj)"
}
}
static func models(array: [[String:Any]]) -> [Card] {
return array.map { Card(dict: $0) }
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping([Card]?) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
print ("ok1")
if let jData = json as? [String:Any], let data = jData["data"] as? [[String:Any]] {
let response = Card.models(array: data)
completion(response)
}
completion(nil)
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
} catch {
print(error)
}
}.resume()
}
}
CardNetworkService.getCards(url: "https://api.scryfall.com/cards/search?q=half") { (res) in
print(res ?? [])
}
Just paste this code in playground and it'll work.
Happy Coding :)
You are wrong get entry of data field.
First you need get data field in json. And parse to deeper.
Try use the code.
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let jsonObject = json as? [String: Any], let data = jsonObject["data"] as? [[String:AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in data {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
UPDATE:
init function in Card has something wrong. In your json cardId is not found
Card class maybe like this because cardId, imageUrl, text maybe not found. It is optional
struct Card {
var cardId: String?
var name: String
var imageUrl: String?
var text: String?
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String else { return nil }
self.cardId = dict["cardId"] as? String
self.name = name
self.imageUrl = dict["imageUrl"] as? String
self.text = dict["text"] as? String
}
}
Try using Codable to parse the JSON data like so,
Create the models like,
struct Root: Decodable {
let cards: [Card]
enum CodingKeys: String, CodingKey {
case cards = "data"
}
}
struct Card: Decodable {
let tcgplayerId: Int
let name: String
let artCrop: String
}
Now parse your JSON data using,
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
}
You can access the properties in cards of response like so,
response.cards.first?.tcgplayerId
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()
Hi I am making an app which works with an API. I have a working code which receives data from the API. But I thought it would be better to make my code a bit cleaner. I want to set the data from the api in an dictionary but I can't get it working. Any help would be appreciated, thanx!
Here is the api result:
I want to set the AutorId and BranchId etc etc in a dictionary.
And this is de code which I have now.
This is the Project class:
class Project: NSObject {
var AuthorId: String?
var BranchId: String?
var CompanyId: String?
var ContactId: String?
var Date: String?
var Deadline: String?
var Description: String?
var Id: String?
var State: String?
init(dictionary: [String: Any]) {
self.AuthorId = dictionary["AuthorId"] as? String
self.BranchId = dictionary["BranchId"] as? String
self.CompanyId = dictionary["CompanyId"] as? String
self.ContactId = dictionary["ContactId"] as? String
self.Date = dictionary["Date"] as? String
self.Deadline = dictionary["Deadline"] as? String
self.Description = dictionary["Description"] as? String
self.Id = dictionary["Id"] as? String
self.State = dictionary["State"] as? String
}
}
and here I am trying to set it in an dictionary:
func apiRequest() {
apiRequestHeader()
var running = false
let urlProjects = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session?.dataTask(with: urlProjects! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
if let items = dictionary["items"] as? [[String:Any]] {
let project = Project(dictionary: items)
print(project)
self.projects.append(project)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task?.resume()
while running {
print("waiting...")
sleep(1)
}
}
I have an API that loads into my app:
https://wger.de/api/v2/exercise/?language=2
I have no errors on the API call and no errors in the table view where I run the get data function. Its worked previously perfectly until now. I havent changed any code.
I assumed the servers would be down for the call, but they arent as you can see.
I am running a print 'request was successful' which is displayed in the debug when running, so its getting the data, however the print(self.exercises) returns an empty array hence no table data...any ideas? Here is the API call
open class ApiService: NSObject {
open func getData(completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
//loads api filtering by english only
let requestUrl = "https://wger.de/api/v2/exercise/?format=json&language=2"
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success( let data):
print("Request was sucessful")
completionHandler(data as? NSDictionary, nil)
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
}
Table Function
func getApiData() {
let _ = apiService.getData() {
(data, error) in
if let data = data {
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()
print(self.exercises)
}
}
}
}
I am also using a serialization model if that could interfere?
final public class Exercise {
var id: Int
var descrip: String
var name: String
var language: [Int]
var muscles: [Int]
var musclesSecondary: [Int]
var equipment: [Int]
public init?(dictionary: [String: Any]) {
guard
let id = dictionary["id"] as? Int,
let descrip = dictionary["description"] as? String,
let name = dictionary["name"] as? String,
let language = dictionary["language"] as? [Int],
let muscles = dictionary["muscles"] as? [Int],
let musclesSecondary = dictionary["muscles_secondary"] as? [Int],
let equipment = dictionary["equipment"] as? [Int]
else { return nil }
self.id = id
self.descrip = descrip
self.name = name
self.language = language
self.muscles = muscles
self.musclesSecondary = musclesSecondary
self.equipment = equipment
}
Sorry,
I havent changed any code
is wrong.
You added the properties musclesSecondary and language and exactly there is the error: language is single Int rather than an array.
var language: Int
...
let language = dictionary["language"] as? Int,
So I'm trying to populate multiple tableviews with the json code below:
{ "company":[
{ "company_id":"0",
"name": "Company Name",
"phone_number":"0123978978",
"website":"http:\/\/www.Company.co.uk\/",
"email":"Company#hotmail.co.uk",
"address":"123 Alm Street...",
"employees":[
{
"company_id": "0",
"name":"Steve",
"age":"25",
"description":"desc"},
{
"company_id": "0",
"name":"Paul",
"age":"35",
"description":"desc"}]
}
]
}
below is the model of fetching the json and formatting it into something I can use
class Company: NSObject {
var name: String
var phoneNumber: String
var website: String
var email: String
var address: String
var employees: [Employee]
override init() {
}
init(name: String, phoneNumber: String, website: String, email: String, address: String, employees: [Employee]) {
self.name = name
self.phoneNumber = phoneNumber
self.website = website
self.email = email
self.address = address
self.employees = employees
}
}
class Employee : NSObject {
var name:String
var age:Int
var information:String
override init() {
}
init(name: String, age: Int, information: String) {
self.name = name
self.age = age
self.information = information
}
}
func getJSON(completion: (array: [Company])->()) {
var companyArray = [Company]()
let requestURL: NSURL = NSURL(string: urlString)!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
data, response, error -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("Everything is fine, file downloaded successfully.")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let companies = json["companies"] as? NSArray {
for companyDict in companies {
let company = Company()
let employee = Employee()
var employeeArray = [Employee]()
if let name = companyDict["name"] as? String,
let phoneNumber = companyDict["phone_number"] as? String,
let website = companyDict["website"] as? String,
let email = companyDict["email"] as? String,
let address = companyDict["address"] as? String {
company.name = name
company.phoneNumber = phoneNumber
company.website = website
company.email = email
company.address = address
}
if let employees = companyDict["employees"] as? NSArray {
for employeesDict in employees {
if let name = employeesDict["name"] as? String,
let age = employeesDict["age"] as? Int,
let information = employeesDict["information"] as? String {
var employeeArray = [Employee]()
let employee = Employee()
employee.name = name
employee.information = information
employee.age = age
employeeArray.append(employee)
company.employees = employeeArray
}
}
}
companyArray.append(company)
}
dispatch_async(dispatch_get_main_queue()) {
completion(array: companyArray)
}
}
} catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
}
This is where the problem arrises, I can populate my first tableview fine. Each cell displays the data fine. The problem occurs when I try to populate my second tableview based on selection of a cell in my first tableview.
let selectedCompany:Company?
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedCompany!.employees!.count //ignore force unwrap
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let employee = self.selectedCompany!.employees![indexPath.row] //ignore force unwrap
cell.employeeName.text = employee.name
return cell
}
The line let employee = self.selectedCompany!.employees![indexPath.row] displays a compiler error of type has no subscript members. I think I know why - I need to populate the second table view as an array. At the moment, Employee is not an array. I've tried several different methods to make it an array but I can't seem to do it. What I've tried usually returns nil and the app crashes or i get compiler errors
You have to declare employees in Company as array
var employees = [Employee]()
and also in the initializer
init(name ... , employees: [Employee]) {
and – very very important – you have to create the Employee instance in the repeat loop, otherwise you have employees.count times the same object (due to reference semantics of classes)
...
for employeesDict in employees {
if let name = employeesDict["name"] as? String,
let age = employeesDict["age"] as? Int,
let information = employeesDict["information"] as? String {
let employee = Employee(name:name, age:age, information:information)
employeeArray.append(employee)
}
}
}
company.employees = employeeArray
...
You have to use the initializer anyway. And you have to assign the array after the repeat loop.