Assign Data using Grand Central Dispatch Swift 3 - ios

I created a Networking file for downloading data, and I want to assign the data to another view controller so I can populate a map with annotations. The data download successfully, but I can't get it to assign to the view controller. I can only get it working when I include the networking code in the view controller and use DispatchQueue.main.async. I want to keep the networking file and view controller separate. Any insights would be greatly appreciated. Apologies in advance for the many lines of code.
The networking file is as follows:
import UIKit
class Networking {
static let shared = Networking()
var objects = [Any]()
func getData (_ completionHandler:#escaping (Location?) -> ()) {
//Create the url with NSURL reuqest
let url = URL(string: "http://localhost:3000/locations")
let request = NSMutableURLRequest(url: url! as URL)
//Set HTTP method as GET
request.httpMethod = "GET"
//HTTP Headers
request.addValue("application/json", forHTTPHeaderField: "Accept")
//Create dataTask using the session object to send data to the server
URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data,
let dataStore = String(data: data, encoding: String.Encoding.utf8) else {
print("Could not find network")
completionHandler(nil)
return
}
guard error == nil else {
print("Error calling GET")
completionHandler(nil)
return
}
let HTTPResponse = response as! HTTPURLResponse
let statusCode = HTTPResponse.statusCode
if (statusCode == 200) {
print("Files downloaded successfully. \(dataStore)" )
} else {
completionHandler(nil)
return
}
//Create json object from data
do {
let json = try! JSONSerialization.jsonObject(with: data , options: []) as? [[String: Any]]
let location: [Location] = []
if let array = json {
for i in 0 ..< array.count {
if let data_object = array[i] as? [String: Any] {
if let _id = data_object["_id"] as? String,
let name = data_object["name"] as? String,
let imageID = data_object["imageID"] as? String,
let category = data_object["category"] as? String,
let details = data_object["details"] as? String,
let latitude = data_object["latitude"] as? Double,
let longitude = data_object["longitude"] as? Double {
var dictionary = [_id, name, imageID, category, details, latitude, longitude] as [Any]
dictionary.append(location)
}
}
}
}
}
}.resume()
}
}
The model is as follows:
class Location {
var _id : String
var name : String
var imageID : String
var category : String
var details : String
var latitude : Double
var longitude : Double
init?(_id: String, name: String, imageID: String, category: String, details: String, latitude: Double, longitude: Double) {
self._id = _id
self.name = name
self.imageID = imageID
self.category = category
self.details = details
self.latitude = latitude
self.longitude = longitude
}
}
The view controller is as follows:
class MapViewController: UIViewController, MGLMapViewDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
Networking.shared.getData { (locations) in
}
populateMap()
}
func populateMap (){
let point = MGLPointAnnotation()
for location in locations {
let coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude )
point.coordinate = coordinate
point.title = location.name
point.subtitle = location.category
self.mapView.addAnnotation(point)
}
}

You are executing completion blocks only in failure cases. Execute the completion block once you have managed to parse the data and pass the array as parameter to closure/block.
import UIKit
class Networking {
static let shared = Networking()
var objects = [Any]()
func getData (_ completionHandler:#escaping ([Location]?) -> ()) {
//Create the url with NSURL reuqest
let url = URL(string: "http://localhost:3000/locations")
let request = NSMutableURLRequest(url: url! as URL)
//Set HTTP method as GET
request.httpMethod = "GET"
//HTTP Headers
request.addValue("application/json", forHTTPHeaderField: "Accept")
//Create dataTask using the session object to send data to the server
URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data,
let dataStore = String(data: data, encoding: String.Encoding.utf8) else {
print("Could not find network")
completionHandler(nil)
return
}
guard error == nil else {
print("Error calling GET")
completionHandler(nil)
return
}
let HTTPResponse = response as! HTTPURLResponse
let statusCode = HTTPResponse.statusCode
if (statusCode == 200) {
print("Files downloaded successfully. \(dataStore)" )
} else {
completionHandler(nil)
return
}
//Create json object from data
do {
let json = try! JSONSerialization.jsonObject(with: data , options: []) as? [[String: Any]]
let location: [Location] = []
if let array = json {
for i in 0 ..< array.count {
if let data_object = array[i] as? [String: Any] {
if let _id = data_object["_id"] as? String,
let name = data_object["name"] as? String,
let imageID = data_object["imageID"] as? String,
let category = data_object["category"] as? String,
let details = data_object["details"] as? String,
let latitude = data_object["latitude"] as? Double,
let longitude = data_object["longitude"] as? Double {
var dictionary = [_id, name, imageID, category, details, latitude, longitude] as [Any]
dictionary.append(location) //am not sure of what this means test your code
completionHandler(location)
}
}
}
}
}
}.resume()
}
}
Few more mistakes in your code :
Your completion block expects Location as a parameter. but in your code you are creating an array of Locations.
let location: [Location] = []
So I have modified the completion block parameters to return array of locations
In your for loop you are creating
var dictionary = [_id, name, imageID, category, details, latitude, longitude] as [Any]
and appending it to dictionary.append(location) I have no idea what this code is. I believe what u actually trying to do is create a location object from the data and then add it to location array
location.append(your_new_location_object)
Hope it helps

Related

Access Value of a Same key in a dictionary of outside function

I want to access the same key of a dictionary that called name and I cant change the value of key because it is on server : here is my code :
func infoUser(complition:#escaping ([String:Any]) -> Void) {
let url = URL(string: "\(offerUrl)/api/user")! //change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "GET" //set http method as POST
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.setValue( "bearare \(profileKeychain["token"]!)", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
print("data is : \(data)")
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print("this is json format: \(json)")
// handle json ...
guard let YourName = json["name"] as? String else { return }
guard let YourAddress = json["address"] as? String else { return }
guard let YourPhone = json["telephone"] as? String else { return }
guard let YourEmail = json["email"] as? String else { return }
guard let city = json["city"] as? [String: Any] else { return }
guard let title = city["title"] as? String else { return }
guard let country = json["country"] as? [String: Any] else { return }
guard let names = country["name"] as? String else { return }
let dict = ["name":YourName,"address":YourAddress,"telephone":YourPhone,"email":YourEmail,"city":city,"title":title,"country":country,"name" : names] as [String : Any]
complition(dict)
}
} catch let error {
print("error is this : \(error.localizedDescription)")
}
})
task.resume()
}
I want to access the value of names in country which its key in name like the name of user
and also this is a completion handler that I use it I called it from viewdidLoad() function :
override func viewDidLoad() {
super.viewDidLoad()
if profileKeychain["token"] != "" {
infoUser { dict in
DispatchQueue.main.async {
self.yourNamelbl.text = dict["name"] as? String
self.yourPhonelbl.text = dict["telephone"] as? String
self.yourCitylbl.text = dict["title"] as? String
self.yourMaillbl.text = dict["email"] as? String
self.yourAddresslbl.text = dict["address"] as? String
// this line
self.countryNamelbl.text = dict["name" ] as? String }}}
and in simulator the name of country and the name of user is same in labels but I dont want to happen this, what's your idea?
thanks for attention
You are overwriting the value for "name" key.
let dict = [
"name": YourName, // First write happens here
"address": YourAddress,
"telephone": YourPhone,
"email": YourEmail,
"city": city,
"title": title,
"country": country,
"name" : names // Second write, The problem is here
] as [String: Any]
UPDATE
You already have name nested inside country dictionary, so you don't need to store it one more time at the top level.
You can remove the second write from above code "name": names part and use it like following in your viewDidLoad().
let country = dict["country"] as? [String: Any]
self.countryNamelbl.text = country?["name"] as? String

Why URLSession.DataTask.shared is not working properly?

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!

JSON Post method is not working swift

I am Parsing json data from google maps api in swift, am trying to show current location using google maps api, getting current location latitude and longitude using didUpdateLocations. inside the serviceLocationupdate()
posting lat and long values using post method but am not getting the response from the json. how can I get the response(data) from the json.
this is the code
var lat = ""
var long = ""
var latlng:String = ""
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location:CLLocationCoordinate2D = manager.location!.coordinate
self.lat = String(location.latitude)
self.long = String(location.longitude)
latlng = self.lat+"," + self.long
}
func serviceLocationupdate()
{
var request = URLRequest(url: URL(string: "http://maps.googleapis.com/maps/api/geocode/json")!)
let session = URLSession.shared
request.httpMethod = "POST"
----->> let bodyData = "latlng=\(latlng)&sensor=\("true")"
print("bodydata",bodyData)
request.httpBody = bodyData.data(using: String.Encoding.utf8);
let task = session.dataTask(with:request,completionHandler:{(d,response,error)in
do{
if let data = d{
do{
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if let results = jsonResult["results"] as? [[String : AnyObject]] {
for result in results{
if let addressComponents = result["address_components"] as? [[String : AnyObject]] {
print(addressComponents)
}
}
}
} catch
{
}
}
}
})
task.resume()
}
if i can use this api its working fine getting proper data
var request = URLRequest(url: URL(string:"http://maps.googleapis.com/maps/api/geocode/json?latlng=13.026811,77.593773&sensor=true")!)
I want display the current location using latitude and longitude
Looks like the API works with the url you mentioned (http://maps.googleapis.com/maps/api/geocode/json?latlng=13.026811,77.593773&sensor=true), then you can just use GET instead of POST. Try the following:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location:CLLocationCoordinate2D = manager.location!.coordinate
self.lat = String(location.latitude)
self.long = String(location.longitude)
latlng = self.lat+","+self.long
serviceLocationupdate()
}
func serviceLocationupdate()
{
var request = URLRequest(url: URL(string: "http://maps.googleapis.com/maps/api/geocode/json?latlng=\(latlng)&sensor=true")!)
let task = session.dataTask(with:request,completionHandler:{(d,response,error)in
if let data = d{
do{
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if let results = jsonResult["results"] as? [[String : AnyObject]] {
for result in results{
if let addressComponents = result["address_components"] as? [[String : AnyObject]] {
print(addressComponents)
}
}
}
} catch
{
}
}
})
task.resume()
}
This works in a playground:
//: Playground - noun: a place where people can play
import UIKit
import XCTest
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
func serviceLocationupdate()
{
let session = URLSession.shared
if let url = URL(string: "http://maps.googleapis.com/maps/api/geocode/json"),
var params = URLComponents(url: url, resolvingAgainstBaseURL: false) {
params.queryItems = [URLQueryItem]()
params.queryItems?.append(URLQueryItem(name: "latlng", value: "33.9250675,-84.339827"))
params.queryItems?.append(URLQueryItem(name: "sensor", value: "true"))
guard let finalURL = params.url else {
print("Failed to create URL")
return
}
var request = URLRequest(url: finalURL)
request.httpMethod = "POST"
let task = session.dataTask(with:request,completionHandler:{(data,response,error)in
if let error = error {
print("Error is \(error)")
return
}
if let resp = response as? HTTPURLResponse {
print("Response status code \(resp.statusCode)")
guard let data = data else {
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary,
let results = jsonResult["results"] as? [[String : AnyObject]] {
for result in results {
if let addressComponents = result["address_components"] as? [[String : AnyObject]] {
print(addressComponents)
}
}
} else {
print("Could not coerce into a dictionary")
}
} catch _ {
print("An error occurred")
}
} else {
print("Invalid response")
}
})
task.resume()
}
}
serviceLocationupdate()

How to parse a api for swift 3?

Have been researching on the parsing for quite a bit. With plethora of information avilable for JSON nothing seems to explain how to do in a sensible way to extract information with swift 3.
This is what got so far
func getBookDetails() {
let scriptUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:9781451648546" .
let myurl = URL(string:scriptUrl)
let request = NSMutableURLRequest(url: myurl!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: myurl! ) { (data, response, error) in
if error != nil{
print("THIS ERROR",error!)
return
} else{
if let mydata = data{
do{
let myJson = try (JSONSerialization.jsonObject(with: mydata, options: JSONSerialization.ReadingOptions.mutableContainers)) as AnyObject
// print("this is the MY JSON",myJson) ---> prints out the json
if let dictonary = myJson["items"] as AnyObject? {
print("the DICTONARY",dictonary) // ----> OUTPUT
if let dictonaryAA = dictonary["accessInfo"] as AnyObject? {
print("the accessInfo",dictonaryAA)
}
}
} catch{
print("this is the in CATCH")
}
} //data
}
}
task.resume()
}
}
OUTPUT :
the DICTONARY (
{
accessInfo = {
accessViewStatus = SAMPLE;
country = US;
=============
RELEVANT DATA as in https://www.googleapis.com/books/v1/volumes?
q=isbn:9781451648546"
==========================
title = "Steve Jobs";
};
}
)
Just need to parse through the json data to get the name, author and title of the book with reference to isbn.
Know there should be a better way to do things that is easily understandable to someone new into the language
You can parse the api in two ways
Using URLSession:
let rawDataStr: NSString = "data={\"mobile\":\"9420....6\",\"password\":\"56147180..1\",\"page_no\":\"1\"}"
self.parsePostAPIWithParam(apiName: "get_posts", paramStr: rawDataStr){ ResDictionary in
// let statusVal = ResDictionary["status"] as? String
self.postsDict = (ResDictionary["posts"] as! NSArray!) as! [Any]
print("\n posts count:",self.postsDict.count)
}
func parsePostAPIWithParam(apiName:NSString, paramStr:NSString,callback: #escaping ((NSDictionary) -> ())) {
var convertedJsonDictResponse:NSDictionary!
let dataStr: NSString = paramStr
let postData = NSMutableData(data: dataStr.data(using: String.Encoding.utf8.rawValue)!)
let request = NSMutableURLRequest(url: NSURL(string: "http://13.12..205.248/get_posts/")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = nil
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error as Any)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse as Any)
do{
if let convertedJsonIntoDict = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
convertedJsonDictResponse = convertedJsonIntoDict.object(forKey: apiName) as? NSDictionary
// callback for response
callback(convertedJsonDictResponse)
}
} catch let error as NSError {
print(error)
}
}
Using Alamofire
func AlamofirePOSTRequest() {
let urlString = "http://13.12..205.../get_posts/"
let para = ["data": "{\"mobile\":\"9420....6\",\"password\":\"56147180..1\",\"page_no\":\"1\"}"]
Alamofire.request(urlString, method: .post, parameters: para , headers: nil).responseJSON {
response in
switch response.result {
case .success:
print("response: ",response)
let swiftyJsonVar = JSON(response.result.value!)
if let resData = swiftyJsonVar["posts"].arrayObject {
self.postsDict = resData as! [[String:AnyObject]]
}
print("\n \n alomafire swiftyJsonVar: ",swiftyJsonVar)
break
case .failure(let error):
print(error)
}
}
}
})
dataTask.resume()
}
First of all, all JSON types are value types in Swift 3 so the most unspecified type is Any, not AnyObject.
Second of all, there are only two collection types in the JSON type set, dictionary ([String:Any]) and array ([Any], but in most cases [[String:Any]]). It's never just Any nor AnyObject.
Third of all, the given JSON does not contain a key name.
For convenience let's use a type alias for a JSON dictionary:
typealias JSONDictionary = [String:Any]
The root object is a dictionary, in the dictionary there is an array of dictionaries for key items. And pass no options, .mutableContainers is nonsense in Swift.
guard let myJson = try JSONSerialization.jsonObject(with: mydata) as? JSONDictionary,
let items = myJson["items"] as? [JSONDictionary] else { return }
Iterate through the array and extract the values for title and authors which is an array by the way. Both values are in another dictionary for key volumeInfo.
for item in items {
if let volumeInfo = item["volumeInfo"] as? JSONDictionary {
let title = volumeInfo["title"] as? String
let authors = volumeInfo["authors"] as? [String]
print(title ?? "no title", authors ?? "no authors")
The ISBN information is in an array for key industryIdentifiers
if let industryIdentifiers = volumeInfo["industryIdentifiers"] as? [JSONDictionary] {
for identifier in industryIdentifiers {
let type = identifier["type"] as! String
let isbn = identifier["identifier"] as! String
print(type, isbn)
}
}
}
}
You are doing wrong in this line
if let dictonaryAA = dictonary["accessInfo"] as AnyObject?
because dictonary here is an array not dictionary. It is array of dictionaries. So as to get first object from that array first use dictonary[0], then use accessInfo key from this.
I am attaching the code for your do block
do{
let myJson = try (JSONSerialization.jsonObject(with: mydata, options: JSONSerialization.ReadingOptions.mutableContainers)) as AnyObject
// print("this is the MY JSON",myJson) ---> prints out the json
if let array = myJson["items"] as AnyObject? {
print("the array",array) // ----> OUTPUT
let dict = array.object(at: 0) as AnyObject//Master Json
let accessInf = dict.object(forKey: "accessInfo") //Your access info json
print("the accessInfo",accessInf)
}
}
Hope this helps you.

Passing data from JSON to table view cell in Swift 3

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

Resources