I'm trying to parse csv file from my ios project(swift 2.3) and found this website. In the tutorial code, it has the following section of code :
if let content = String(contentsOfURL: contentsOfURL,
encoding: encoding, error: error) {
...........
}
And I'm not sure what it does. Does it create a String object?
Does it create a String object?
Yes, it creates a string from the contents of the URL given by contentsOfURL and using the character encoding given by encoding. It's analogous to the following Objective-C code:
NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
encoding:encoding
error:&error];
The if let part is a form of conditional statement. let is used to assign a value to an immutable variable. Using it in a conditional as in your example only allows the body of the conditional statement to execute if that assignment succeeds. So, if some error occurs while the data at the given URL is being fetched or if the string cannot be created for some reason, the condition fails and the body isn't executed. The whole snippet might be written like this in Objective-C:
NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
encoding:encoding
error:&error];
if (content != nil) {
// do something with content
}
That code creates a string, but it does it by fetching the contents of a URL. Usually that URL points to a resource on the Internet. In that case it's a very bad way to fetch a string, since it is a synchronous network call that can hang or fail. It's a very bad idea to do synchronous networking calls on the main thread.
You could wrap that code in a GCD call to a background queue, but instead I'd suggest using NSURLSession and submitting a data task. Your search terms would be NSURLSession (or just URLSession in Swift 3) and the function func dataTask(with url: URL). (It might be easier search on it's Objective-C name, dataTaskWithURL since Google searches don't work very well with special characters.)
Take a look at a GitHub project I created called Async_demo. It has a singleton class called DownloadManager that downloads a blob of data from a specified URL. It's written to return the data as a Data object, but it would be a simple matter to convert that result from Data to a String.
The key bit of code is this:
typealias DataClosure = (Data?, Error?) -> Void
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
Related
I've created a Service file in order to handle all of my networking within the Weather Application that I am fine tuning. Within this service file, I use protocols, in order to return the retrieved data from GET requests to the appropriate View Controller.
During my code refactoring, and for the sake of learning, rather than using URLSessions, I decided I wanted to learn how to use Alamofire.
One of my GET requests retrieves an image of either a (sun, cloud, rain cloud, etc.), depending on the weather of a certain city (this is an example of the URL I am submitting my GET request to: http://openweathermap.org/img/wn/03n#2x.png.
Before I imported Alomofire, I would GET the bytes of this image, and render the bytes within UIImage like so:
self.weatherIcon.image = UIImage(data: result)
This worked just fine. But now, when using Alamofire for my request, the challenge I am having is that I'm unable to convert AFDataResponse to type Data, in order to then be rendered to UIImage.
Below you may see my GET Request.
AF.request(myUrl).responseData{response in
debugPrint(reponse)
self.delegate3?.iconServiceDelegateDidFinishWithData(result: response)
}
The response is of type AFDataResponse.
Therefore, when trying to write:
self.weatherIcon.image = UIImage(data: result)
I get an error saying,
Cannot convert value of type 'AFDataResponse (aka 'DataResponse<Data, AFError') to expected argument type 'Data'.
Any help would be much appreciated from the community.
Thanks.
Simple answer:
Create a variable of type Data and assign response.data to this variable.
AF.request(myUrl).responseData{ response in
debugPrint(response)
var imgData : Data //create variable of type data
imgData = Data(response.data!) // access the data through response.data
self.delegate3?.iconServiceDelegateDidFinishWithData(result: imgData)
}
There are many ways to do this. You can access the data directly, as you originally suggested (though I would treat it as an Optional rather than force unwrapping, as it'll crash otherwise).
.responseData { response in
let image = response.data.map(UIImage.init(data:)) // Creates UIImage?
}
You can transform the Result value to maintain any Error you receive.
.responseData { response in
let image = response.result.map(UIImage.init(data:)) // Creates Result<UIImage, AFError>
}
Or you can map the entire DataResponse.
.responseData { response in
let imageResponse = response.map(UIImage.init(data:)) // Creates DataResponse<UIImage, AFError>
}
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.
I have a question about NSURLSession
I have downloaded JSON data using NSURLSession
I want to access the JSON enter code hereData variable from outside this block of code so I manipulate it
this is my code
// variable declared in my class
var jsonData = JSON("")
// my function
func loadCategories(){
var url = NSURL(string: "http://localhost:8888/api/v1/getAllCategories")
var request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "GET"
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data: NSData!, response: NSURLResponse!, errors: NSError!) in
self.jsonData = JSON(data: data)
}).resume()
}
When I try to get jsonData outside the block of NSURLSession I get empty variable
Any help ?
Check if you are accessing the jsonData on the same thread. Maybe you have a race condition here.
The problem is that you don't understand how async functions with completion blocks work.
Take a look at my answer to this thread. I explain what's going on in detail:
Why does Microsoft Azure (or Swift in general) fail to update a variable to return after a table query?
(Don't let the title of the thread mislead you. It has nothing to do with Microsoft Azure.)
The method you are using, dataTaskWithRequest, invokes your completion closure on the URL session's delegate queue. Unless you've provided a background queue as the delegate queue you don't need to bother with dispatch_async.
I have found the solution
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT dispatch_async(dispatch_get_global_queue(priority, 0)) { self.jsonData = JSON(data: data) dispatch_async(dispatch_get_main_queue()) { self.collectionView?reloadData() } }
I wanted to know if it's possible to load the text in a label from a URL.
I was going to try and use NSURL to pull in a .txt file stored online.
Would anyone know how to implement this with swift?
Thanks
If you don't want to use sessions, you can also use the simpler NSURLConnection Class, something like this:
let url = NSURL(string: "https://wordpress.org/plugins/about/readme.txt")
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
dispatch_async(dispatch_get_main_queue()) {
// Do stuff on the UI thread
self.textField.text = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
return
}
}
Yes it's possible. Yes, I know how to do it. However, you haven't exerted any effort whatsoever to solve your problem yourself.
Search on NSURLConnection. Unless you need https or login, you could use the class method sendAsynchronousRequest:queue:completionHandler:, which is very easy to use. You should be able to find examples of using it on the net.
You could also use a library like Alamofire. That makes it even easier.
Can anyone please tell me what does below code means. I am trying to learn swift and i didn't understand
below lines.
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
Thank you very much for your time.
This asynchronously initiates the network request and when it's done, it performs a println of the NSString representation of the data it received, and it does this the main queue (NSOperationQueue.mainQueue()).
In terms of deciphering it, consider the definition of the sendAsynchronousRequest function:
class func sendAsynchronousRequest(_ request: NSURLRequest,
queue queue: NSOperationQueue!,
completionHandler handler: (NSURLResponse!,
NSData!,
NSError!) -> Void)
That third parameter is a closure. Your syntax is taking advantage of the "trailing closure" syntax, that allows you to supply that trailing closure parameter as a block after the function. And thus, the response, data, error in syntax is mapping those three variables to the NSURLResponse, NSData and NSError parameters in that that completionHandler parameter.
See Closures discussion in The Swift Programming Language.
It sends request asynchronously. When the operation completed it calls closure ({} part). response parameter is NSURLResponse! instance and represents server's response. The data parameter is NSData! instance and represents the data the server returns. The error parameter is NSError! instance and represents the error might happen in this process.