Hi i am trying to use alamofire to download json weather data. Here is my code, the working version:
class WeatherModel {
private var _date: String?
private var _location: String?
private var _weatherType: String?
private var _temperature: Double?
func getWeatherInfoFromAPI(completed: #escaping ()-> ()) {
let url = URL(string: WEATHER_URL)!
Alamofire.request(url).responseJSON(completionHandler: { response in
// Test updating data
self._temperature = 25
self._weatherType = "Clear"
self._location = "Vietnam"
completed()
})
}
}
-> This way, i am able to update the property of the class.
Failing to update class property version of getWeatherInfoFromAPI func:
func getWeatherInfoFromAPI(completed: #escaping ()-> ()) {
let url = URL(string: WEATHER_URL)!
Alamofire.request(url).responseJSON{ response in
// Test updating data
self._temperature = 25
self._weatherType = "Clear"
self._location = "Vietnam"
}
completed()
}
So, i dont know what is the difference between them. Please help me to clarify between 2 ways here.
Alamofire.request(url).responseJSON(completionHandler: { response in })
and
Alamofire.request(url).responseJSON{ response in }
What is the reason that my code does not work? Since i see the Alamofire docs also use like the second way! I am thinking about thread difference between them
Also, how do i know what thread the code is running in responseJSON?
Thanks, i appreciate your time and help!
Those two ways are functionally identical, the second one just uses Swift's trailing closure syntax.
What do you do in completed()? Because in first example, you are calling it upon completion of network call, and in second case you are calling it immediately after you start the network call - the call is not completed yet. You should call if in Alamofire callback, like in first example. In second example, if you're inspecting those properties inside completed, then it's no wonder they're not updated yet.
Related
var endLat: Double = 0.0
var endLong: Double = 0.0
func forwardGeocodingStarting(address: String) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
return
}
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
dispatch_async(dispatch_get_main_queue()) {
startLat = (coordinate?.latitude)!
startLong = (coordinate?.longitude)!
print("\(startLat) is lat and \(startLong) is long")
}
})
}
Hello, here is a geocoding function that I created, that simply takes an address and returns the address's coordinates. My problem is, that at the end of the code section, when I do print(endLat) it prints out 0, however when I do it in the dispatch_async in the function, it comes out to the proper latitude.
I realize this is an asynchronous function, which is why I tried to use the dispatch_async to update the variable instantly.
My question is, how can I have these variables update instantly? Thanks in advance.
You've got a little confused with what you mean by "Asynchronous function not updating variable". forwardGeocodingStarting(_:) is NOT an Async function. geocodeAddressString is an async function designed by the API to allow you to use the following values you get after the operation is done within that function only. Hence you keep getting 0 as the it doesn't wait till the operation is done completely to display the value. dispatch_async(dispatch_get_main_queue) is used only to update UI on the Main thread. You've to use a completion handler in your function like so:
typealias theDouble = (Double, Double) -> ()
func forwardGeocodingStarting(address: String, completion: theDouble){
//your code
let aVar: Double!
let bVar: Double!
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
aVar = (coordinate?.latitude)!
bVar = (coordinate?.longitude)!
completion(aVar, bVar)
}
Now when you call your function, update the variables startLat and startLong like so:
forwardGeocodingStarting(<your string address>) { firstVar, secondVar in
startLat = firstVar
startLong = secondVar
print("\(startLat) is lat and \(startLong) is long") //You will get correct values
}
You cannot have these variables update instantaneously, as you are calling an asynchronous function. Thats an obvious contradiction :-)
That you dispatch back to the main queue is fine, but that doesn't change the asynchronous behaviour. In fact your are even issuing yet another dispatch_async ... (meaning the body of that code will get run at some later point in time)
You could wait for the results to be available, e.g. using a dispatch group, but quite likely that is not what you want here.
I assume you want to use the values in some view controller? In this case you probably fill in some instance variables as the results arrive and update the UI state once both values are available.
I have seen similar posts on this, but nothing that is helping me out. I can not for the life of me determine why I can't extract data from my task. I am trying to update an object (myUser) with data obtained from a SOAP XML response from a Web Service I built. I am new to Swift and IOS, but I've done this with C#. I can update the label on the viewcontroller with the results from the web service, but not a class variable. Thanks in advance for your help!
class FirstViewController: UIViewController {
#IBOutlet weak var label: UILabel! //CAN UPDATE THIS FROM TASK
var myUser = User() //CAN NOT UPDATE THIS FROM TASK
func soapRequest(){
...
let task = session.dataTaskWithRequest(request) {(data, response, error)-> Void in
do{ xmlResponse = try AEXMLDocument(xmlData: data!)}
catch{print("\(error)")}
//Example of the data I am trying to send to the ViewController object
let firstName = xmlResponse.root["element1"].stringValue
//I've tried this
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.myUser.firstName = firstName //This does not work
self.label.text = firstName //This DOES work
})
}
task.resume()
}
}
Dan's comment was the correct solution to my issue. The code was executing correctly, but since the request is asynchronous, it wasn't finishing in time and I assumed it was broken.
Your network request is asynchronous, it won't have finished yet when
you print the value in viewDidLoad so the value won't have been set
yet. – dan 1 hour ago
Can anyone suggest a solution for downloading multiple files say 100s, in background. The important thing being that the download url has a life span of 15 minutes, so it's required that we fetch a download url and then start downloads. We can't prefetch all urls and add it to download task, as that may lead to download failures of expired url after few successful downloads.
Thanks in advance.
you can do the following:
var data : NSData?{
didSet{
//Parse the data to any thing you want
}
}
var urlFetchedAsString : String? {
didSet{
if(urlFetchedAsString == nil)
return
let url : NSURL = NSURL(string: urlFetchedAsString!)!
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
() -> Void in
data = NSData(contentsOfURL: url)
})
}
}
func fetchURL() ->String{
//Fetched Your url and return
}
override func viewDidLoad(){
super.viewDidLoad()
urlFetchedAsString = fetchURL()
}
Explanation
The OS will execute the didSet block in the variable urlFetchedAsString each time it is set
The didSet block will fetch the data from the url and save them as NSData
The dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) means that fetching data from url will be done on the different thread to prevent blocking the UI thread
After done saving you, the variable data will be set and the didSet for this variable will be executed. In this block you can implement your parsing algorithm.
Fetching the url itself is up to you because you didn't clarify where they are or how will u get them
Note
Here i assumed that you don't need to copy of all the urls because as you said they will expire in 15 mins
In one of my apps I need to geocode address string. At first I considered using CLGeocoder. However, after I tried it I stumbled upon a problem which I described in this question.
The solution was to use Google's Geocoding APIs instead. I have now switched to them and managed to get them working by having the following functions:
func startConnection(){
self.data = NSMutableData()
let urlString = "https://maps.googleapis.com/maps/api/geocode/json?address=\(searchBar.text!)&key=MYKEY"
let linkUrl:NSURL = NSURL(string:urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!)!
let request: NSURLRequest = NSURLRequest(URL: linkUrl)
let connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!){
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? [String: AnyObject] {
print(json)
}
}
catch {
print("error1")
}
}
This works great and resolves the problem which I had with CLGeocoder. However, in addition to extracting the coordinates of place, I need to also use Google's Timezone APIs to extract the timezone for each place.
Doing this with the NSURLConnection or NSURLSession seems to me a bit difficult as I would need to keep track of which session/connection returns. So, I would like to have some solution which uses completion handlers.
I have tried using Alamofire framework (using the correct branch for Swift 2.0). However, it seems like request() function is the wrong one to use in this case. I have tried:
let parameters = ["address":searchBar.text!,"key":"MYKEY"]
Alamofire.request(.GET, "https://maps.googleapis.com/maps/api/geocode/json", parameters: parameters).responseJSON(options:.AllowFragments) { _, _, JSON in
print(JSON)
}
And all I am getting printed is "SUCCESS". I hope that I am doing something wrong and it can be fixed because I would really like to be able to use closures instead of delegate calls.
My questions are:
Is it possible to use Alamofire with Google Geocoding APIs?
If so, can you please tell me what am I doing wrong?
If it is not possible, can you please suggest me how to design a system with NSURSessions or NSURLConnections which would allow me to use completion handlers for each call instead of delegates?
P.S. I am aware that I can use synchronous requests but I would really like to avoid using that option
Update
It was suggested that adding .MutableContainers as an option should make responseJSON work. I tried the code below:
let apiKey = "MYKEY"
var parameters = ["key":apiKey,"components":"locality:\(searchBar.text!)"]
Alamofire.request(.GET, "https://maps.googleapis.com/maps/api/geocode/json", parameters: parameters).responseJSON(options:.MutableContainers) { one, two, JSON in
print(JSON)
}
And all I get printed is "SUCCESS".
Ok, I have finally figured this out (with the help from #cnoon). The value which is returned is of type Result. I couldn't find documentation for it, but the source code is available here.
In order to retrieve JSON below implementation can be used:
Alamofire.request(.GET, "https://mapss.googleapis.com/maps/api/geocode/json", parameters: parameters).responseJSON(options:.MutableContainers) { _, _, JSON in
switch JSON {
case .Failure(_, let error):
self.error = error
break
case .Success(let value):
print(value)
break
}
}
The value printed is the correct representation of response from Geocoding APIs.
Hi I am really new to coding in Swift, and am trying to follow the codes in this book: http://www.apress.com/9781484202098. Learn iOS 8 App Development 2nd Edition by James Bucanek
In particular, I am working through Chapter 3 - building a URL shortening app, but despite having copied the code exactly, I am getting an error on the code in Page 76:
if let toShorten = webView.request.URL.absoluteString {
which states 'NSURLRequest?' does not have a member named 'URL'.
I have tried googling an answer, but unfortunately have not come across anything. Any response I can find seems to suggest that my code ought to be working (e.g. How to get url which I hit on UIWebView?). This seems to have the closest answer SWIFT: Why I can't get the current URL loaded in UIWebView? but the solution does not appear to work for me. If I add a ? after the request, it will then at least build it, but I then have a nil variable returned.
I am using Xcode v6.1.1. Here is the piece of code that is coming up with the error in ViewController.swift:
let GoDaddyAccountKey = "0123456789abcdef0123456789abcdef" //this is replaced by my actual account key in my own code
var shortenURLConnection: NSURLConnection?
var shortURLData: NSMutableData?
#IBAction func shortenURL( AnyObject ) {
if let toShorten = webView.request?.URL.absoluteString { // ? now added
let encodedURL = toShorten.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let urlString = "http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"
shortURLData = NSMutableData()
if let firstrequest = NSURL(string: urlString) //added if here and removed !
let request = NSURLRequest(URL:firstrequest)
shortenURLConnection = NSURLConnection(request:request, delegate:self)
shortenButton.enabled = false
}
}
}
If you have any suggestions on how I can fix this, I would really appreciate it!
Update:
Following suggestions from Ashley below, I have amended my code so that it is no longer bringing up the error (see comments above). However, it is now no longer running. This appears to be because the urlString is being created as http://api.x.co/Squeeze.svc/text/d558979bb9b84eddb76d8c8dd9740ce3?url=Optional("http://www.apple.com/"). The problem is therefore the Optional() that is included and thus makes it an invalid URL. Does anyone have a suggestion on how to remove this please?
request is an optional property on UIWebView:
var request: NSURLRequest? { get }
also stringByAddingPercentEscapesUsingEncoding returns an optional:
func stringByAddingPercentEscapesUsingEncoding(_ encoding: UInt) -> String?
What you need is to make user of optional binding in a few places:
if let toShorten = webView.request?.URL.absoluteString {
if let encodedURL = toShorten.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlString = "http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"
shortURLData = NSMutableData()
if let firstrequest = NSURL(string: urlString) { // If a method can return a nil, don't force unwrap it
let request = NSURLRequest(URL:first request)
shortenURLConnection = NSURLConnection(request:request, delegate:self)
shortenButton.enabled = false
}
}
}
See Apple's docs on optional chaining for details
See Apple's docs for NSURL class