Alamofire download from JSON API - json

I'm trying to set text on a label from api, but it seems that the function doesn't even get called. Please refer to the snippet below. Is there anything wrong with it?
EDIT: typealias DownloadComplete = () -> ()
var date: String = ""
override func viewDidLoad() {
super.viewDidLoad()
timeLbl.text = date
// Do any additional setup after loading the view.
}
func downloadTimeData(completed: #escaping DownloadComplete) {
//Downloading forecast weather data for TableView
Alamofire.request(APIURL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let currentDate = dict["fulldate"] as? String {
self.date = currentDate
print(self.date)
print("xxx")
}
}
completed()
}
}

I figured it out with simpler and easier way, through the alamofire documetation.
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(APIURL).responseJSON { response in
print(response.result) // result of response serialization
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
let currentDate = dict["fulldate"] as? String
self.timeLbl.text = currentDate
}
}
}

In the code you posted you are not calling downloadTimeData(completed:) anywhere.
You can do that in viewDidAppear(_:) for example:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
downloadTimeData {
// The request has completed
timeLbl.text = date
}
}
Note that you may need to change the call slightly, depending on how DownloadComplete is defined.

You are setting timeLbl.text immediately on page load in viewDidLoad, but you haven't told the app to do anything else.
You have to move downloadTimeData to viewDidLoad, and in the completion, set 'timeLbl.text = date'
You have to set some sort of text place holder or loader while your call is being made, because you can't guarantee that it is instant.
Are we setting one label? Or a whole tableview of labels?
I changed some syntax to be "swiftier"
var date = ""
override func viewDidLoad() {
super.viewDidLoad()
//call downloadTimeData here
downloadTimeData() {
//once we are in completion, this means internet call finished, so set label now
self.timeLbl.text = date
}
}
func downloadTimeData(completed: #escaping DownloadComplete) {
//Downloading forecast weather data for TableView
Alamofire.request(APIURL).responseJSON { response in
guard let dict = response.result.value as? [String: AnyObject], let currentDate = dict["full date"] as? String else {
//handle error here if response fails to give you good data
completed()
return
}
self.date = currentDate
print(self.date)
print("xxx")
completed()
}
}

Related

Unable to add parameter to API in swift

I am getting parameter value from other viewcontroller, and i am getting parameter valueperfectly but which i am unable to add to API
here if i hardcode eventStatus then its working
and eventStatus value from otherviewcontroller also coming perfectly which i am unable to add to API
if i hard code like this its working
var eventType = "Draft"
let string = Constants.GLOBAL_URL + "/get/allevents/?eventstatus=\(self.eventType)"
Code: here i am getting correct eventStatus value but while breakpoint its control goes to else, why?
class EventsViewController: UIViewController {
var eventsListArray = [AnyObject]()
// var eventType = "Draft"
var eventType: String!
var eventList : EventsModel? = nil
#IBOutlet weak var eventsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getAllEventsList()
}
func getAllEventsList() {
DispatchQueue.main.async {
let headers = ["deviceid": deviceId,"userType": "personal","key": personalId]
DispatchQueue.main.async {
//let string = Constants.GLOBAL_URL + "/get/allevents/?eventstatus=\(self.eventType)"
let string = Constants.GLOBAL_URL + "/get/allevents"
var urlComponents = URLComponents(string: string)
let eventStatus = self.eventType
print("event status value in API call \(eventStatus)")
let requestEventType = URLQueryItem(name: "eventstatus", value: eventStatus)
urlComponents?.queryItems = [requestEventType]
let urlStr = urlComponents?.url
var request = URLRequest(url: urlStr!, cachePolicy: .useProtocolCachePolicy,timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers as! [String : String]
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
DispatchQueue.main.async {
if error == nil {
let httpResponse = response as? HTTPURLResponse
if httpResponse!.statusCode == 200 {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data!) as! [String :Any]
print("publish event \(jsonObject)")
self.eventList = EventsModel.init(fromDictionary: jsonObject)
DispatchQueue.main.async {
if self.eventList?.events.count != 0 {
DispatchQueue.main.async {
self.eventsTableView.reloadData()
}
}
else {
DispatchQueue.main.async {
Constants.showAlertView(alertViewTitle: "", Message: "No Events \(self.eventType)", on: self)
self.eventList?.events.removeAll()
self.eventsTableView.reloadData()
}
}
}
} catch { print(error.localizedDescription) }
} else {
Constants.showAlertView(alertViewTitle: "", Message: "Something went wrong, Please try again", on: self)
}
}
}
})
dataTask.resume()
}
}
}
}
where i am wrong, why eventStatus value not adding to API.. please suggest me
it looks like you have a POST request and you need to use request data instead URL parameters.
HTTP Request in Swift with POST method here you can see:
let parameters: [String: Any] = [
"eventstatus": yourValue
]
request.httpBody = parameters.percentEncoded()
You also need to create parameters with eventStatus. And put it in
request.httpBody = parameters.percentEncoded()
If this endpoint on your server waits on this data in request body than you could not add it like a URL parameter.
Also, don't forget these 2 extensions from the example
extension Dictionary {
func percentEncoded() -> Data? {
return map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return allowed
}()
}
The solution must be obvious from the discussion in the comments. Although you seem a little puzzled so, I'm adding this to help you out. The value of eventType is never getting allocated to a value, it is remaining as nil unless you set it's value at a point in your code before the API call. So here is a way for you to figure this out:
let string = "https://www.google.com" + "/get/allevents"
var urlComponents = URLComponents(string: string)
let eventStatus = self.eventType ?? "Published" // here value is defaulted to "Published"
let requestEventType = URLQueryItem(name: "eventstatus", value: eventStatus)
urlComponents?.queryItems = [requestEventType]
let urlStr = urlComponents?.url
print(urlStr?.absoluteString)
Here, we're setting a default value for eventType for the scenario where eventType is nil.

How can I keep the value of a variable outside a function?

I'm trying to display the value I get from a JSON in an AR text, within the DecodeJSON function it all works, I even achieve to put that value in a normal label but when I try to set that value to the AR text it is empty... what can I do or what I'm doing wrong? It would be very useful if you can help me out with this.
#IBOutlet var sceneView: ARSCNView!
#IBOutlet weak var labelTest: UILabel!
let URL_VWC = "http://w1.doomdns.com:11000/restaguapotable/api/celula/10/sitio/4";
var name :String!
struct JSONTest: Codable {
let Nombre: String
let Tiempo: String
}
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene()
// Set the scene to the view
sceneView.scene = scene
DecodeJson();
let text = SCNText(string: name, extrusionDepth: 1.0)
text.firstMaterial?.diffuse.contents = UIColor.black
let textNode = SCNNode(geometry: text)
textNode.position = SCNVector3(0,0, -0.5)
textNode.scale = SCNVector3(0.02,0.02,0.02)
sceneView.scene.rootNode.addChildNode(textNode)
}
func DecodeJson(){
guard let url = URL(string: URL_VWC) else { return }
// 2
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
// 3
//Decode data
let JSONData = try JSONDecoder().decode(JSONTest.self, from: data)
// 4
//Get back to the main queue
DispatchQueue.main.async {
self.name = JSONData.Nombre
self.labelTest.text = self.name
}
} catch let jsonError {
print(jsonError)
}
// 5
}.resume()
}
This means that DecodeJson() returns self.name = JSONData.Nombre after let text = SCNText(string: name, extrusionDepth: 1.0) is called.
You should have a separate function where you setup your scene after the DecodeJson() has returned the JSON

-- swift: async + JSON + completion + DispatchGroup

The code in my viewcontroller-class is executed before the JSON-download-process is ready even though there is a completion handler in the func for downloading JSON an a DispatchGroup(). I store the JSON-data in an array called "fetchedModules" and this is filled with 11 items in this case. Why does this happen?
result in console:
---> in Class PostCell - func numberOfSections: 0
JSON call finished
// ViewController
override func viewDidLoad() {
super.viewDidLoad()
let group = DispatchGroup()
group.enter()
self.fetchJSON()
// here calling downloadJSONasync
group.leave()
group.notify(queue: .main) {
print("JSON call finished")
}
...
// networkService with completion
func downloadJSONasync(searchItem: String, completion: #escaping ([NSDictionary]) -> Void) {
//request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
//let session = URLSession(configuration: configuration, delegate: nil)
let session = URLSession(configuration: configuration)
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
if (error != nil) {
print("error!")
}
else{
do {
let fetchedData = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [NSDictionary]
completion(fetchedData)
}
catch {
print("error")
}
}
})
task.resume()
}
// call in viewController
override func numberOfSections(in tableView: UITableView) -> Int {
print("---> in Class PostCell - func numberOfSections: \(String(describing: fetchedModules.count))")
return fetchedModules.count
// code of fetchJSON
func fetchJSON()
{
let baseurl = AppConstants.Domains.baseurl // https://m.myapp2go.de
let compositURL = baseurl + "getmodules_noItems.php?id=\(AppConstants.appString.startString)"
let encodedUrl : String! = compositURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) // remove the spaces in the url string for safty reason
let JSONurl = URL(string: encodedUrl)! // convert the string into url
var JSONrequest = URLRequest(url: JSONurl) // make request
JSONrequest.httpMethod = "GET"
//JSONrequest.cachePolicy = .reloadIgnoringCacheData
let networkService = NetworkService(request: JSONrequest)
networkService.downloadJSONasync(searchItem: AppConstants.appString.startString, completion: { (fetchedData) in
fetchedModules.removeAll()
DispatchQueue.main.async {
for eachFetchedModul in fetchedData {
let eachModul = eachFetchedModul
if
let custid = eachModul["custid"] as? String,
let modulcat = eachModul["modulcat"] as? String,
let modulname = eachModul["modulname"] as? String,
let comment = eachModul["comment"] as? String
{
fetchedModules.append(CModules(custid: custid, modulcat: modulcat, modulname: modulname, comment: comment))
print(custid)
print(modulcat)
print(modulname)
print(comment)
print("---------------------")
}
}// for end
// ... somehow set data source array for your table view
self.tableView.reloadData()
}// dispatch
}
)} // func end
Because fetchJSON returns immediately, before the JSON is downloaded. The effect is that the DispatchGroup is entereed and left right away, without waiting for the JSON:
group.enter()
self.fetchJSON() // returns immediately
group.leave() // the JSON has yet to be downloaded
To wait until the JSON has arrived, add a completion handler to fetchJSON:
override func viewDidLoad() {
group.enter()
self.fetchJSON {
group.notify(queue: .main) {
print("JSON call finished")
}
group.leave()
}
}
// Change the data type of the completion handler accordingly
func fetchJSON(completionHandler: #escaping (Data?) -> Void) {
// ...
networkService.downloadJSONasync(searchItem: AppConstants.appString.startString) { fetchedData in
defer { completionHandler(fetchedData) }
// ...
}
)
Using defer ensures that the completion handler will always be called, no matter how the outer closure returns. I'm not clear why you use a DispatchGroup here as there is no waiting, but I kept it in place to answer your question.
Your table view doesn't have any data from the beginning because any data hasn't been fetched yet. So it's ok, that table view has no cells. You just need to reloadData of your table view since now you appended elements to table view's data source array and now you should show this data.
Please, don't use DispatchGroup for this, just use your completion parameter of your method and inside of the completion closure after data are received set data source array for table view and then ... reload data of table view
downloadJSONasync(searchItem: "someString") { dictionary in
DispatchQueue.main.async { // don't forget that your code doesn't run on the main thread
// ... somehow set data source array for your table view
self.tableView.reloadData()
}
}
Note that you should avoid using NSDictonary and you should rather use Dictionary. Also from Swift 4+ you can use Codable instead of JSONSerialization.

Swift how to reuse my JSON HTTP Request header

I am making an application which makes a lot of requests from an API. So I don't want to copy and past the code over and over. I was wondering how I can reuse my code in a some more efficient way? Maybe with extensions?
This is my code know:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "****"
let password = "****"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
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]] {
for item in items {
if let description = item["Description"] as? String {
self.projectNaam.append(description)
}
if let id = item["Id"] as? String {
self.projectId.append(id)
}
if let companyId = item["CompanyId"] as? String {
self.companyId.append(companyId)
}
}
}
self.apiRequestCompani()
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
Yes, you can use Extensions to create a BaseViewController and extend that where you want to use your code over and over again. Then you should abstract all dynamic data over input parameters to that method.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func getApiRequest (Parameters) {
//API Request
}
And then in your view controller you just extend BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Call method in baseviewcontroller
getApiRequest(parameters)
//Call method in self
self.getApiRequest(parameters)
}
override func getApiRequest(Parameters) {
//IF you need to override default configuration
}
So I don't want to copy and past the code over and over.
Absolutely right, no one aiming to get duplicated code; That's the issue of massive view controller. This issue appears since the view controller layer in your application handles most of the responsibilities, such as: getting data from the network, how data should be represented, deliver the formatted data to the view layer, etc...
There are many approaches for solving such an issue (using an appropriate architectural pattern for your application), for simplicity, I would recommend to apply the MVC-N (or MVCNetworking) approach into your app, it is almost the same usual MVC, with a separated files (managers), represent a new layer for handling -for instance- the integration with the external APIs.
Applying the MVN-N should not be that complex, nevertheless it needs to be described well (which might be too abroad to be descried in the answer), I would suggest to check the above mentioned apple example, also watching this video should be useful.

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()
}
}