So I've written a function in swift which gives me a numeric value from JSON api. My question is how can I take the value from function so I can use it in more practical means.
override func viewDidLoad() {
super.viewDidLoad()
getJSON()
}
func getJSON(){
let url = NSURL(string: baseURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
let usdPrice = swiftyJSON["bpi"]["USD"]["rate"].doubleValue
print(usdPrice)
}else{
print("There was an error!")
}
let usdPrice gets me the value so how can I take that from the function getJSON() and do something with it, for example attribute it to some label in Main.storyboard
Unfortunately the other answers are incorrect. Just returning a value will not work because you are getting the value from the completion closure of dataTaskWithRequest.
Having the statement return usdPrice should be a compiler error because the completion closure does not have a return value.
You'll need to add your own completion closure to getJSON that gets the double as a parameter.
override func viewDidLoad() {
super.viewDidLoad()
getJSON { (usdPrice) -> Void in
// do something with usdPrice
print(usdPrice)
}
}
func getJSON(completion: (Double) -> Void) {
let url = NSURL(string: baseURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
let usdPrice = swiftyJSON["bpi"]["USD"]["rate"].doubleValue
completion(usdPrice)
} else {
print("There was an error!")
}
}
Your function needs to return the value as a result of its execution. For more details check out this: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID160
You have to have a return value for the function. The code below should work.
func getJSON() -> Double {
let url = NSURL(string: baseURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var usdReturnValue : Double = 0.0
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
let usdPrice = swiftyJSON["bpi"]["USD"]["rate"].doubleValue
print(usdPrice)
usdReturnValue = usdPrice
}else{
print("There was an error!")
}
}
return usdReturnValue
}
Related
Hi i am a beginner studying swift and would like to know what to use when making an api request. What is the modern and professional way?
is it using an escaping closure like so:
func getTrendingMovies(completion: #escaping (Result< [Movie], Error >) -> Void) {
guard let url = URL(string: "\(Constants.baseUrl)/trending/all/day?api_key=\.(Constants.API_KEY)") else {return}
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _,
error in
guard let data = data, error == nil else {
return
}
do {
let results = try JSONDecoder().decode(TrendingMoviesResponse.self, from:
data)
completion(.success(results.results))
} catch {
completion(.failure(error))
}
}
task.resume()
}
or should i make an api request without escaping closure while using a sort of delegate like so:
func performRequest(with urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
delegate?.didFailWithError(error: error!)
return
}
if let safeData = data{
// created parseJson func
if let weather = parseJSON(safeData){
delegate?.didUpdateWeather(self,weather: weather)
}
}
}
task.resume()
} else {
print("url is nil")
}
}
I agree with matt, the modern and professional way is async/await
func getTrendingMovies() async throws -> [Movie] {
let url = URL(string: "\(Constants.baseUrl)/trending/all/day?api_key=\(Constants.API_KEY)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(TrendingMoviesResponse.self, from: data).results
}
I am using URLSessionTask to get the source code of url. When the internet is connected, it works well.
However, when the Internet is disconnected, I try building. And in simulator it is blank and the cpu is 0%. What affects is that My Tab Bar Controller is also missing and blank (It is my initial view controller). It seems that this task is under connecting?
I want the data received from dataTask, so I use semaphore to make it synchronous. Otherwise, as dataTask is an asynchronous action, what I
get is an empty string.
How can I fix this problem?
Thanks!
let urlString:String="http://www.career.fudan.edu.cn/jsp/career_talk_list.jsp?count=50&list=true"
let url = URL(string:urlString)
let request = URLRequest(url: url!)
let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)
let dataTask = session.dataTask(with: request,
completionHandler: {(data, response, error) -> Void in
if error != nil{
errorString = "Error!"
}else{
htmlStr = String(data: data!, encoding: String.Encoding.utf8)!
//print(htmlStr)
}
semaphore.signal()
}) as URLSessionTask
//start task
dataTask.resume()
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
Update: As #Moritz mentioned, I finally use completion handler (callback).
func getforData(completion: #escaping (String) -> ()) {
if let url = URL(string: "http://XXXXX") {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data, let getString = String(data: data, encoding: String.Encoding.utf8), error == nil {
completion(getString)
} else {
print("error=\(error!.localizedDescription)")
}
}
task.resume()
}
}
And in viewdidload
override func viewDidLoad() {
super.viewDidLoad()
getforData { getString in
// and here we get the "returned" value from the asynchronous task
print(getString) //works well
//tableview should work in main thread
DispatchQueue.main.async {
self.newsTableView.dataSource = self
self.newsTableView.delegate = self
self.newsTableView.reloadData()
}
}
I converted my project to Swift 3 and got an error: Cannot call value of non-function type UITextField
in the following line:
let url:URL = URL(string: _url!)!
full code
func StartDownloadingPL(_ urlFromTextField: String?) -> Void {
let _url = urlFromTextField
// some url checks...
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let url:URL = URL(string: _url!)!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.dataTask(with: request, completionHandler: {
(
data, response, error) in
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
guard let _:Data = data, let _:URLResponse = response, error == nil else {
print("download error")
return
}
let dataString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
self.ParsePlaylist(dataString)
})
task.resume()
}
URL I am getting from UITextField:
#IBAction func DownloadAndParsePL1(_ sender: AnyObject) {
StartDownloadingPL(Playlist1URL.text)
}
also
#IBOutlet weak var Playlist1URL: UITextField!
I have found a problem, after converting NSURL to URL it has conflict with outlet called URL.
Well I am new to Swift and I don't know much of completion handler. I want to get a request from an API and parse the JSON response so I can get the token. But what's happening with my code is that whenever I call the getAuthentication function my UI freezes and waiting for the data to get. Here is the code for getAuthentication
func getAuthentication(username: String, password: String){
let semaphore = dispatch_semaphore_create(0);
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
dispatch_semaphore_signal(semaphore);
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
then, I am calling this method in my LoginViewController. Someone says that I am using a Synchronous request thats why my UI freezes, but I have really no idea on how to change it to Async and wait for the data to be downloaded. Can someone help me with this? Any help will much be appreciated.
Firstly, remove dispatch_semaphore related code from your function.
func getAuthentication(username: String, password: String){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
}
task.resume()
}
In the above code, the function dataTaskWithRequest itself is an asynchronus function. So, you don't need to call the function getAuthentication in a background thread.
For adding the completion handler,
func getAuthentication(username: String, password: String, completion:((sucess: Bool) -> Void)){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
var successVal: Bool = true
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
successVal = false
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(successVal)
})
}
task.resume()
}
It can be called as follows:
self.getAuthentication("user", password: "password", completion: {(success) -> Void in
})
You may pass an escaping closure argument to getAuthentication method.
func getAuthentication(username: String, password: String, completion: (JSON) -> ()){
...
// create a request in the same way
...
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
completion(swiftyJSON)
} else {
print("There was an error")
}
}
task.resume()
}
And call getAuthentication in LoginViewController like this:
getAuthentication(username, password) { (json) -> in
//Do whatever you want with the json result
dispatch_async(dispatch_get_main_queue()) {
// Do UI updates
}
}
Another way to go is calling getAuthentication in a background thread in your LoginViewController to avoid blocking the main thread (i.e. UI thread).
//In LoginViewController
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
getAuthentication(username, password)
dispatch_async(dispatch_get_main_queue()) {
// UI updates
}
}
I'm experimenting with async calls but I'm a little lost. The print(json) in the viewDidLoad outputs an empty dictionary, but the one within the function prints correctly. This is unsurprising; it gets to that print before the async is completed. I can't figure out how to fix it; I tried putting the return within the completion handler, but I got an error that Unexpected non-void return value in void function. I tried changing the completion handler to expect a return value, but either that's not the right approach or I was doing it wrong.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let json = getJson("https://maps.googleapis.com/maps/api/geocode/json?address=WashingtonDC&sensor=false")
print(json)
}
func getJson(url: String) -> AnyObject {
var json:AnyObject = [:]
let urlPath = NSURL(string: url)
let urlRequest = NSURLRequest(URL: urlPath!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
if error != nil {
print("Error")
} else {
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(json)
} catch {
print("json error")
}
}
})
task.resume()
return json
}
}
You will need to have a completion handler based interface to your async API.
func getJson(url: String, completion : (success: Bool, json: AnyObject? ) ->Void ) -> Void {
var json:AnyObject = [:]
let urlPath = NSURL(string: url)
let urlRequest = NSURLRequest(URL: urlPath!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
if error != nil {
print("Error")
} else {
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(json)
//Call the completion handler here:
completion(success : true, json :json )
} catch {
print("json error")
completion(success : false, json :nil )
}
}
})
task.resume()
}
}
Now you call call this API as follows-
override func viewDidLoad() {
super.viewDidLoad()
getJson("https://maps.googleapis.com/maps/api/geocode/json?address=WashingtonDC&sensor=false") { (success, json) -> Void in
if success {
if let json = json {
print(json)
}
}
}
}