I need to fetch some quizzes for my application from the server. Unfortunately, it seems that URLSession.DataTask.shared is not working. How do I fix the problem?
This is for Swift 4.
import Foundation
import UIKit
class QuizService {
let baseUrl = "https://iosquiz.herokuapp.com/api/quizzes"
func fetchQuizzes(completion: #escaping (([Quiz]?) -> Void)) -> Void {
if let url = URL(string: baseUrl) {
let request = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let resultsList = json as? [String: Any], let results = resultsList["quizzes"] as? [[String: Any]] {
let quizzes = results.map({ json -> Quiz? in
print(json)
if
let title = json["title"] as? String,
let id = json["id"] as? Int,
let level = json["level"] as? Int,
let description = json["description"] as? String,
let category = json["category"] as? String,
let questions = json["questions"] as? [String: Any],
let imageUrl = json["image"] as? String {
let quiz=Quiz(id:id,title:title,descript:description,category:category,level:level,imageUrl:imageUrl,questions:questions)
return quiz
} else {
return nil
}
}).filter { $0 != nil } .map { $0! }
completion(quizzes)
} else {
completion(nil)
}
} catch {
completion(nil)
}
} else {
completion(nil)
}
}
dataTask.resume()
} else {
completion(nil)
}
}
}
My error is that field of quizzes are null, so my code is not working in my view controller.
I took a look at the response here https://iosquiz.herokuapp.com/api/quizzes.
The "questions" should be array of dictionaries instead of dictionary.
so it should works if you replace this line
let questions = json["questions"] as? [String: Any]
with this line
let questions = json["questions"] as? [[String: Any]]
BTW, I prefer to extract the logic of parsing json into another method to keep fetchQuizzes method simple.
Hope this helps!
Related
I'm trying to pass data from a JSON response to a table view cell. I'm having problems with capturing the response values that I'm extracting in URLSession.shared.dataTask.
func callYouTubeAPIToGetAllVideos() {
let url = URL(string: "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=XYZ&maxResults=50&order=date&key=ABC")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
} else {
if let usableData = data {
let json = try? JSONSerialization.jsonObject(with: usableData, options: [])
if let dictionary = json as? [String: Any?] {
if let array = dictionary["items"] as? [Any] {
for object in array {
if let objectAsDictionary = object as? [String: Any?] {
if let objectWithKindAndVideoId = objectAsDictionary["id"] as? [String: String] {
if let videoId = objectWithKindAndVideoId["videoId"] {
//pass data to table cell
}
}
if let snippet = objectAsDictionary["snippet"] as? [String: Any] {
if let description = snippet["description"] {
//pass data to table cell
}
}
}
}
}
}
}
}
}
task.resume()
}
I tried appending the values to an instance variable but it didn't work.
Sorry about the messy code, this is my 1st time working with JSON in Swift.
First of all never declare a received JSON dictionary as [String:Any?]. A received dictionary value can't be nil.
Declare a custom struct Video.
struct Video {
let videoId : String
let description : String
}
Declare a data source array.
var videos = [Video]()
Parse the JSON into the array and reload the table view on the main thread.
func callYouTubeAPIToGetAllVideos() {
let url = URL(string: "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=XYZ&maxResults=50&order=date&key=ABC")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
} else {
do {
if let dictionary = try JSONSerialization.jsonObject(with: data!) as? [String: Any],
let array = dictionary["items"] as? [[String: Any]] {
for object in array {
if let objectWithKindAndVideoId = object["id"] as? [String: String],
let snippet = object["snippet"] as? [String: Any] {
let videoId = objectWithKindAndVideoId["videoId"] ?? ""
let description = snippet["description"] as? String ?? ""
let video = Video(videoId: videoId, description: description)
self.videos.append(video)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} catch {
print(error)
}
}
}
task.resume()
}
In cellForRow assign the values to the text properties
let video = videos[indexPath.row]
cell.textLabel!.text = video.videoId
cell.detailTextLabel?.text = video.description
I try to get information about the weather hourly from the Dark Sky API, but the code stops working at the if let data = hourly["data"] as? [String : AnyObject] line (checked with printing stuff after every line). I want to know what is wrong with my code. I think it could be something with the "data" let, but I don't know for sure.
let Task2 = URLSession.shared.dataTask(with: urlRequestDark) { (data, response, error) in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let hourly = json["hourly"] as? [String : AnyObject] {
if let data = hourly["data"] as? [String : AnyObject]{
if let hourNum = data["14"] as? [String : AnyObject] {
if let chanceRain = hourNum["precipProbability"] as? Float{
self.chanceHour1 = String(chanceRain)
}
DispatchQueue.main.sync {
self.ChanceRainLabel.text = self.chanceHour1
}
}
}
}
} catch let jsonError {
print(jsonError.localizedDescription)
}
}
}
Task2.resume() test
The strange part is, this does work:
let urlRequestDark = URLRequest(url: URL (string: "https://api.darksky.net/forecast/(API Key)/(coordinates)")!)
let Task = URLSession.shared.dataTask(with: urlRequestDark) { (data, response, error) in
if error == nil {
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let currently = json["currently"] as? [String : AnyObject] {
if let chance2 = currently["precipProbability"] as? Float{
print(String(chance2))
self.chance = String(Int(chance2 * 100)) + "%"
self.PreType = currently["precipType"] as? String
}
if let _ = json["error"]{
}
DispatchQueue.main.sync{
self.TypeLabel.text = self.PreType
self.ChanceLabel.text = self.chance
}
}
}catch let jsonError{
print(jsonError.localizedDescription)
}
}
}
Task.resume()
You've made couple mistakes.
First, "data" is an array of dictionaries, so it should be cast to [[String : AnyObject]].
Second, you're trying to subscript array by String, not Int.
Third, using self in escaping closures potentially creates retain cycles.
Let me propose you some fixed and adjusted code.
let task2 = URLSession.shared.dataTask(with: urlRequestDark) { [weak self] (data, response, error) in
guard error == nil else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String : AnyObject],
let hourly = json["hourly"] as? [String : AnyObject],
let data = hourly["data"] as? [[String : AnyObject]],
data.count > 14,
let chanceRain = data[14]["precipProbability"] as? Float {
self?.chanceHour1 = String(chanceRain)
DispatchQueue.main.sync {
self?.ChanceRainLabel.text = self?.chanceHour1
}
}
} catch let jsonError {
print(jsonError.localizedDescription)
}
}
task2.resume()
Try like this
import UIKit
class WebService: NSObject {
var session = URLSession()
public class var sharedInstance: WebService {
struct Singleton {
static let instance = WebService()
}
return Singleton.instance
}
override init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30.0
configuration.timeoutIntervalForResource = 60.0
session = URLSession(configuration: configuration)
}
public func weatherData(coordinate:String,APIkey:String,completion:#escaping (_ responsedata:NSDictionary?,_ error:NSError?) -> Void) {
var Baseurl = "https://api.darksky.net/forecast/\(APIkey)/\(coordinate)"
Baseurl = Baseurl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let weatherRequestUrl = URL(string: Baseurl)
let request = NSMutableURLRequest(url: weatherRequestUrl!)
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard error == nil && data != nil else {
return
}
if let httpStatus = response as? HTTPURLResponse{
if httpStatus.statusCode != 200 {
print("Something is wrong")
}
}
do {
let WindlocationData = try JSONSerialization.jsonObject(with: data! as Data, options:.allowFragments) as! NSDictionary
print(WindlocationData)
completion(WindlocationData,nil)
}
catch let error as NSError {
completion(nil,error)
}
}
task.resume()
}
}
And call API like this!
func callAPI(latlong:String,APIkeyParm:String) {
WebService.sharedInstance.weatherData(coordinate: latlong,APIkey: APIkeyParm) { (responsData, error) in
if error == nil{
print("Response data is-\(responsData)")
}
}
}
Call the method like this
let latlongStr = "\(latitude),\(longitude)"
self.callAPI(latlong: latlongStr,APIkeyParm: "APIKeyString")
One importent thing you need to pass latlong like this format 23.022504999999999,72.571362100000002
I have this long function here which makes a bunch of API calls, parses through data and returns two arrays representing a bunch of sight-seeing locations (one array holds the latitudes, one holds the latitudes). The issue I am having is determining when the two arrays are finished being populated. Ideally, I would like to be able to place
print("ArrayCount = \(self.latArray.count)")
somewhere in my code and receive a single print statement in the console reading ArrayCount = 123. However everywhere I place the print statement I get either an array count of 0 or a loop of values being printed out as they are added (1..2..3.. ... ..123). Thanks in advance!
func someFunction()
{
let url:URL = URL(string: "...")
let task = URLSession.shared.dataTask(with: URLRequest(url: url))
{
data, response, error in
if error != nil
{
print("ERROR IN API REQUEST: \(error!.localizedDescription)")
}
else
{
do
{
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
{
if let layerOne = parsedData["one"] as? [String: Any]
{
if let layerTwo = layerOne["two"] as? [[String: Any]]
{
for layerThree in layerTwo
{
if let variableName = layerThree["value"] as? String
{
let innerUrl:URL = URL(string: "...")!
let innerTask = URLSession.shared.dataTask(with: URLRequest(url: innerUrl))
{
data, response, error in
if error != nil
{
print("ERROR IN API REQUEST: \(error!.localizedDescription)")
}
else
{
do
{
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
{
if let layerA = parsedData["A"] as? [String: Any]
{
if let lat = layerA["Latitude"] as? String, let long = layerA["Longitude"] as? String
{
self.latArray.append(lat)
self.longArray.append(lon)
}
}
}
}
catch
{
print("ERROR IN JSON SERIALIZATION")
}
}
}
innerTask.resume()
}
}
}
}
}
}
catch
{
print("ERROR IN JSON SERIALIZATION")
}
}
}
task.resume()
}
I'm not sure exactly what you're trying to do, but once I thinned out your incredibly verbose code and tried to understand it, I think the conclusion is that you'll only want to record the array counts after the last iteration of your inner loop. For example:
Instead of doing this: for layerThree in layerTwo
do this: for (index, layerThree) in layerTwo.enumerated()
Then, after you append the lat/long values, add this check:
if index == layerTwo.count - 1 //if this is our last inner loop
{
//print the array counts
print("Lat count: \(latArray.count)")
print("Long count: \(longArray.count)")
}
This should work in your case. However, I disagree entirely with the execution of this simply because of the lack of portability and reusability of your code. Additionally, there are quite a few good language constructs which are being ignored. The relentless nested if let blocks could be reduced significantly with a few good guard statements. Furthermore, considering you aren't handling your errors in any catch blocks anyway, might as well just remove them and opt for a try? instead. One much swiftier way of handling things would be to just include a completion handler in the function itself so that the array count printing logic could be handled elsewhere. I'll include some example code of how I would clean things up:
func someFunction(completion: (([String], [String]) -> Void)?)
{
let url = URL(string: "...")!
var latArray: [String] = []
var longArray: [String] = []
let task = URLSession.shared.dataTask(with: URLRequest(url: url),
completionHandler: { (data, response, error) -> Void in
guard error == nil else
{
print("ERROR IN API REQUEST: \(error?.localizedDescription)")
return
}
guard let parsedData = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments),
let parsedDict = parsedData as? [String: Any],
let layerOne = parsedDict["one"] as? [String: Any],
let layerTwo = layerOne["two"] as? [[String: Any]] else
{
print("JSON OBJECT DOES NOT MATCH EXPECTED PATTERN")
return
}
for (index, layerThree) in layerTwo.enumerated()
{
guard let _ = layerThree["value"] as? String else
{
print("Skip this iteration (I guess?)")
continue
}
let innerUrl = URL(string: "...")!
let innerTask = URLSession.shared.dataTask(with: URLRequest(url: innerUrl),
completionHandler: { (data, response, error) -> Void in
guard error == nil else
{
print("ERROR IN API REQUEST: \(error?.localizedDescription)")
return
}
guard let parsedData = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments),
let parsedDict = parsedData as? [String: Any],
let layerA = parsedDict["A"] as? [String: Any],
let lat = layerA["Latitude"] as? String,
let long = layerA["Longitude"] as? String else
{
print("Something went wrong")
return
}
latArray.append(lat)
longArray.append(long)
if index == layerTwo.count - 1 //if this is our last inner loop
{
completion?(latArray, longArray)
}
}
)
innerTask.resume()
}
}
)
task.resume()
}
someFunction(completion: { (latArray, longArray) -> Void in
//print the array counts
print("Lat count: \(latArray.count)")
print("Long count: \(longArray.count)")
})
This could still be improved much further, though based on your question I don't see that I personally should be putting more time into constructing this for you. It would be best if you come up with a few methods of your own to build an application with more readable and reusable code.
I'm having a problem with the following code. I'm downloading a list of actors in JSON and I want to populate Struct Actor with the received data. Everything works great until I try to flatMap on the received data and try to initialize the struct Actor. When I try to compile the code i get the error: Cannot assign value of type '()' to type [Actor]. The error corresponds to a line in viewDidLoad actorsList = downloadActors() Would anybody have any recommendation who to solve this?
import UIKit
func downloadActors() {
var request = URLRequest(url: URL(string: "url...")!)
request.httpMethod = "POST"
let postString = "actorGroup=\("Superhero")"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
guard let data = data, error == nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("error : statusCode should be 200 but is \(httpStatus.statusCode)")
print("response = \(response)")
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: AnyObject]
guard let actorsJSON = json?["response"] as? [[String : AnyObject]] else {
return
}
} catch {
print("catch error")
}
}
}
}
task.resume()
}
func loadActors() -> [Actor] {
if let actors = actorsJSON as? [[String : AnyObject]] {
return actors.flatMap(Actor.init)
}
}
let actorsArray = loadActors()
class MasterViewController: UITableViewController {
var actorsList = [Actor]()
var detailViewController: DetailViewController? = nil
var objects = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
actorsList = downloadActors()
print(actorsList)
Struct Actors is as follows:
struct Job {
let actorGroup: String
let actorName: String
}
extension Actor: JSONDecodable {
init?(JSON: [String : AnyObject]) {
guard let actorGroup = JSON["actorGroup"] as? String, let actorName = JSON["actorName"] as? String else {
return nil
}
self. actorGroup = actorGroup
self. actorName = actorName
}
}
let listActors = actorsJSON as? [[String : AnyObject]] {
Should be:
if let listActors = actorsJSON as? [[String : AnyObject]] {
Edit: For more info I'd like to add Vadian's comment:
Very confusing code. What does the function in the middle of the do block? Why do you type-check actorsJSON twice? The computed property is let listActors... which should be probably an optional binding (if let ... ). Further .mutableContainers is completely nonsense in Swift. And finally a JSON dictionary is [String:Any] in Swift 3.
I am new to swift and in programming, and I am trying to parse a JSON with the help of Alamofire and SwiftyJSON, if the JSON file is simple, I have no problems and working good, but when I have something like Dictionary -> Dictionary -> Array -> Dictionary, problems begin, so I have the following code:
func performCYesterdayWeatherFetch(forSelectedCity: String)
{
let properString = forSelectedCity.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
Alamofire.request("http://api.apixu.com/v1/history.json?key=MY_KEY&q=\(properString!)&dt=2016-10-20").responseJSON { (response) -> Void in
guard response.result.isSuccess else
{
print("Error while fetching remote rooms: \(response.result.error)")
return
}
guard let json = response.result.value as? JSON,
let forecastJson = json["forecast"].dictionary else
{
print("YESTERDAY PROBLEM")
return
}
for item in (forecastJson["forecastday"]?.arrayValue)!
{
let day = item["day"].dictionaryObject
guard let yesterdayTempCels = day?["avgtemp_c"] as! Double?,
let yesterdayTempFahr = day?["avgtemp_f"] as! Double? else
{
return
}
MY_KEY - is really my key, the problem is not in that i didn't input the key.
It always get in else here:
guard let json = response.result.value as? JSON,
let forecastJson = json["forecast"].dictionary else
{
print("YESTERDAY PROBLEM")
return
}
They result JSON looks like that:
The thin I need is avgtemp_c and avgtemp_f
What am I doing wrong?
Here you have a solution where you don't even need SwiftyJSON to get those values.
let properString = forSelectedCity.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
Alamofire.request("http://api.apixu.com/v1/history.json?key=MY_KEY&q=\(properString!)&dt=2016-10-20").responseJSON { (response) -> Void
guard let json = response.result.value as? [String: Any],
let forecastDictionary = json["forecast"] as? [String: Any],
let forecastDayArray = forecastDictionary["forecastday"] as? [[String: Any]] else {
print("YESTERDAY PROBLEM")
return
}
for item in forecastDayArray {
guard let day = item["day"] as? [String: Any],
let yesterdayTempCels = day["avgtemp_c"] as? Double,
let yesterdayTempFahr = day["avgtemp_f"] as? Double else {
return
}
// Here you should have the values that you need
}
}