Performing an automatic segue - ios

I'm downloading remote JSON data and want my loading screen to stay up until the download is complete. Once my parse method finishes running, a segue should be called to move to the next view automatically.
I've verified that my data is properly downloading and parsing. My performSegue function is even being called when I throw up a breakpoint. But the application is still not moving to the next view.
Here's where I'm calling my parse method and then immediately calling the desired segue:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
downloadSources(atURL: "https://newsapi.org/v1/sources?language=en")
performSegue(withIdentifier: "loadingFinished", sender: self)
}
For reference, if you need it, here is my parse method in its entirety:
func downloadSources(atURL urlString: String) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
if let validURL = URL(string: urlString) {
var request = URLRequest(url: validURL)
request.setValue("49fcb8e0fa604e7aa461ee4f22124177", forHTTPHeaderField: "X-Api-Key")
request.httpMethod = "GET"
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil {
assertionFailure()
return
}
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data
else {
assertionFailure()
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
guard let sources = json["sources"] as? [[String: Any]]
else {
assertionFailure()
return
}
for source in sources {
guard let id = source["id"] as? String,
let name = source["name"] as? String,
let description = source["description"] as? String
else {
assertionFailure()
return
}
self.sources.append(Source(id: id, name: name, description: description))
}
}
}
catch {
print(error.localizedDescription)
assertionFailure()
}
}
task.resume()
}
}
Thanks in advance.

Sounds like a closure callback is what you want.
typealias CompletionHandler = ((_ success:Bool) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
downloadSources(atURL: "www.example.com", completion: {
if success {
performSegue(withIdentifier: "loadingFinished", sender: self)
return
}
// otherwise deal with failure
})
}
func downloadSources(atURL urlString: String, completion: CompletionHandler) {
if error != nil {
completion?(false)
return
}
// finish downlaod
completion?(true)
}

Related

How to display YouTube search API response in UI text view?

I am creating a project where I have to display the YouTube search API response in a UI Text View when a button is clicked. Now I am getting the response in console.
1) How to display it as a JSON response.
2) How to display it in UI text view.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var resultTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func goButton(_ sender: Any) {
guard let url = URL(string: "https://www.youtube.com/watch?v=6Zf79Ns8_oY")else {
return
}
let session = URLSession.shared
let task = session.dataTask(with: url) {(data, response, error) in
if let response = response {
print(response)
}
if let jsondata = data {
print(jsondata)
}
}
task.resume()
}
}
I think you are looking for that:
Parse Response using JSONSerialization and set required value to textview text.
guard let url = URL(string: "https://www.youtube.com/watch?v=6Zf79Ns8_oY")else {
return
}
let session = URLSession.shared
let task = session.dataTask(with: url) {(data, response, error) in
guard let data = data, error == nil else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any]
let text = json["KEY"] as? String
DispatchQueue.main.async{
self.YOURTEXTVIEW.text = text
}
} catch let error as NSError {
print(error)
}
}
task.resume()
maybe it is helpful.

Swift - View loads before http request is finished in viewDidLoad()

I am trying to load a values from a database and put them into a UITableView in the viewDidLoad function in one of my Swift files. When debugging, at the time of the view rendering, the list of values is empty, but after the view loads, the list gets populated by the view loads. I don't have much experience with threads in Swift, so I am not exactly sure why this is happening, any ideas? I have tried to run DispatchQueue.main.async, but that did not work My code is below:
override func viewDidLoad() {
super.viewDidLoad()
// Load any saved meals, otherwise load sample data.
loadDbMeals()
}
private func loadDbMeals() {
var dbMeals = [Meal]()
let requestURL = NSURL(string: self.URL_GET)
let request = NSMutableURLRequest(url: requestURL! as URL)
request.httpMethod = "GET"
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//parsing the response
do {
//converting response to NSDictionary
let myJSON = try JSONSerialization.jsonObject(with: data!, options: [.mutableContainers]) as? NSDictionary
//parsing the json
if let parseJSON = myJSON {
if let nestedDictionary = parseJSON["message"] as? NSArray {
for meal in nestedDictionary {
if let nestedMeal = meal as? NSDictionary {
let mealName = nestedMeal["name"]
let rating = nestedMeal["rating"]
dbMeals.append(Meal(name: mealName as! String, photo: UIImage(named: "defaultPhoto"), rating: rating as! Int, ingredientList: [])!)
}
}
}
}
} catch {
print(error)
}
}
meals += dbMeals
//executing the task
task.resume()
}
So, the current order of breakpoints, is the call to loadDbMeals() in the viewDidLoad() function, then it tries to add the dbMeals variables to the global meals variable, and then the http request gets executed, after the empty list has already been added. I appreciate any help!
Reload your table after loading data
if let parseJSON = myJSON {
if let nestedDictionary = parseJSON["message"] as? NSArray {
for meal in nestedDictionary {
if let nestedMeal = meal as? NSDictionary {
let mealName = nestedMeal["name"]
let rating = nestedMeal["rating"]
dbMeals.append(Meal(name: mealName as! String, photo: UIImage(named: "defaultPhoto"), rating: rating as! Int, ingredientList: [])!)
}
}
DispatchQueue.main.async{
self.tableView.reloadData()
}
}
}
the request happens asynchronously. so the view is loaded while the request may still be in progress.
move the meals += dbMeals line into the request's completion handler (after the for loop), add a self. to the meals var since you are referencing it from within a closure and reload the tableview from the main thread afterwards:
DispatchQueue.main.async{
self.tableView.reloadData()
}
Because dataTask is not a synchronised call, we need to use lock to wait until all fetch is finished.
Code will look something like this:
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
let lock = DispatchSemaphore(value: 0)
// Load any saved meals, otherwise load sample data.
self.loadDbMeals(completion: {
lock.signal()
})
lock.wait()
// finished fetching data
}
}
private func loadDbMeals(completion: (() -> Void)?) {
var dbMeals = [Meal]()
let requestURL = NSURL(string: self.URL_GET)
let request = NSMutableURLRequest(url: requestURL! as URL)
request.httpMethod = "GET"
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//parsing the response
do {
//converting response to NSDictionary
let myJSON = try JSONSerialization.jsonObject(with: data!, options: [.mutableContainers]) as? NSDictionary
//parsing the json
if let parseJSON = myJSON {
if let nestedDictionary = parseJSON["message"] as? NSArray {
for meal in nestedDictionary {
if let nestedMeal = meal as? NSDictionary {
let mealName = nestedMeal["name"]
let rating = nestedMeal["rating"]
dbMeals.append(Meal(name: mealName as! String, photo: UIImage(named: "defaultPhoto"), rating: rating as! Int, ingredientList: [])!)
}
}
}
}
} catch {
print(error)
}
// call completion
completion()
}
meals += dbMeals
//executing the task
task.resume()
}
So execute loadDbMeals with completion block which will be called when fetching is finished and lock will wait until completion block is called.

Json and variable scope

My error should be quite obvious but I can't find it;
I've a global variable initialized a the beginning of my class:
class InscriptionStageViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var lesSemaines = [String]()
I try to populate this array with a distant json file using that function
func getSemainesStages(){
let url = URL(string: "http://www.boisdelacambre.be/ios/json/semaines.json")
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
let listeSemaines = myJson["semaine"] as! [[String:AnyObject]]
//print(listeSemaines)
for i in 0...listeSemaines.count-1 {
var tabSem = listeSemaines[i]
let intituleSemaine:String = tabSem["intitule"] as! String
//let dateSemaine:String = tabSem["date"] as! String
DispatchQueue.main.sync
{
self.lesSemaines.append(intituleSemaine)
}
}
} catch
{
print("erreur Json")
}
}
}
task.resume()
}
When I call my function in the viewDidLoad and then I print my global array, it's empty (the URL is correct, the json data is read correctly and when I read the data appended in the array in the loop, it print the (so) needed value...)
Thanks in advance
The download takes time. Introduce another methode:
func updateUi() {
print(lesSemaines)
//pickerView.reloadAllComponents()
}
And call it after the download finished:
func getSemainesStages(){
// ... your code
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
// ... your code
for tabSem in listeSemaines{
guard let intituleSemaine = tabSem["intitule"] as? String else {
print("erreur Json")
continue
}
self.lesSemaines.append(intituleSemaine)
}
// update UI *after* for loop
DispatchQueue.main.async {
updateUi()
}
// ... your code
}
}
I have updated your code to Swift 3. Please replace it with below code.
func getSemainesStages(){
let url = URL(string: "http://www.boisdelacambre.be/ios/json/semaines.json")
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String: Any]
let listeSemaines = myJson["semaine"] as! [[String: Any]]
for i in 0...listeSemaines.count-1 {
var tabSem = listeSemaines[i]
let intituleSemaine:String = tabSem["intitule"] as! String
self.lesSemaines.append(intituleSemaine)
}
print(self.lesSemaines)
} catch {
print("erreur Json")
}
}
}
task.resume()
}

How to use JSON Results created in a function [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 6 years ago.
I'm parsing JSON data from a remote service. i wrote a function wich do the parsing process. This function has a return value. The result is created in this function and saved in a global property. But when i call the function in viewDidLoad i get an empty result:
Here is my code
class ViewController: UIViewController {
var rates = [String:AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
print(getRates("USD")) // <- Gives me an empty Dictionary
}
func getRates(base: String) -> [String:AnyObject]{
let url = NSURL(string: "http://api.fixer.io/latest?base=\(base)")!
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithURL(url) { (data, response, error) in
do{
self.rates = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as! [String:AnyObject]
//print(self.rates) //<-- Gives me the right output, but i want to use it outside.
}
catch{
print("Something went wrong")
}
}
task.resume()
return self.rates //<- returns an empty Dictionary
}
I can only get the right result inside the function, but I can't use it outside. What is wrong here?
EDIT:
Tank you! All answers are working, but is there a way to store the result in a global property so that i can use the result anywhere? Assuming i have a tableView. Then i need to have the result in a global property
You cannot return response value at once - you have to wait until response arrives from network. So you have to add a callback function (a block or lambda) to execute once response arrived.
class ViewController: UIViewController {
var rates = [String:AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
getRates("USD"){(result) in
print(result)
}
}
func getRates(base: String, callback:(result:[String:AnyObject])->()){
let url = NSURL(string: "http://api.fixer.io/latest?base=\(base)")!
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithURL(url) { (data, response, error) in
do{
self.rates = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as! [String:AnyObject]
callback(self.rates)
//print(self.rates) //<-- Gives me the right output, but i want to use it outside.
}
catch{
print("Something went wrong")
}
}
task.resume()
}
Because you are using NSURLSession and the task is asynchronous you will need to use a completion handler. Here is an example:
//.. UIViewController Code
var rates = [String: AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
getRates("USD") { [weak self] result in
self?.rates = result
}
}
func getRates(base: String, completion: [String: AnyObject] -> Void) {
let url = NSURL(string: "http://api.fixer.io/latest?base=\(base)")!
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithURL(url) { (data, response, error) in
do {
let rates = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as! [String:AnyObject]
completion(rates)
}
catch {
print("Something went wrong")
}
}
task.resume()
}
Try this on your code:
class ViewController: UIViewController {
var rates = [String:AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
getRates() { (result) in
print(result)
}
}
func getRates(completion: (result: Array)) -> Void{
let url = NSURL(string: "http://api.fixer.io/latest?base=\(base)")!
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithURL(url) { (data, response, error) in
do{
self.rates = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as! [String:AnyObject]
completion(self.rates)
}
catch{
print("Something went wrong")
}
}
task.resume()
return self.rates //<- returns an empty Dictionary
}
}

Include a return handler in async call in Swift

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

Resources