I try to update label.text with server response. But label is updated only after second call of method. What is wrong?
dispatch_async(dispatch_get_main_queue()) is deprecated. I changed it to DispatchQueue.main.async, but it does not work.
Example for swift 2:
Swift changing text of a label after downloading content of a web page
Not working code:
#IBAction func buttonAction(_ sender: Any) {
let url = URL(string: "https://api.nasa.gov/planetary/apod?date=2005-2-22&api_key=DEMO_KEY")!
let task = URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
if let data = data,
let stringData = String(data: data, encoding: .utf8) {
print("stringData: \(stringData)") // prints every time
DispatchQueue.main.async {
// change label text after second calling.
// ??? How to change label text at first calling ???
self?.mainLabel.text = stringData
}
}
}
task.resume()
}
Try this
var labelString : String = "" {
didSet {
self.mainLabel.text = labelString
}
}
Write below code inside buttonAction
DispatchQueue.main.async {
// change label text after second calling.
// ??? How to change label text at first calling ???
self?.labelString = stringData
}
Related
I've been looking for the answer everywhere and could't find any.. Is there any way I can access the value of searchBar.text in another file? I have the delegate set in my SearchVC but I also have a custom tableView cell in another file.
I need the value of the SearchBar of my SearchVC to use in FirstDefinitionVC for decoding the word from the searchBar and use it for finding the audio URL.
All works fine while I call the function inside the searchBarSearchButtonClicked method but I can find no way to pass that String into FirstDefintionVC.
The relevant searchVC code :
var word = ""
`
extension SearchVC: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
// { (data: [WordData], [Definitions])
word = searchBar.text!
wordManager.performRequest(word: word) { data in
self.wordData = data
self.searchButtonPressed = true
// print(data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
fetchAudio(word: word) { data in //this one works fine
DispatchQueue.main.async {
self.wordData = data
}
}
}
func fetchAudio(word: String, comp: #escaping ([WordData])-> Void) {
let wordURL = "https://api.dictionaryapi.dev/api/v2/entries/en/"
let urlString = "\(wordURL)\(word)"
if let url = URL(string: urlString) {
let dataTask = URLSession.shared.dataTask(with: url, completionHandler: {
(data,response,error) in
guard let data = data, error == nil else {
print("Error occured while accessing data with URL")
return
}
do {
let decoded = try JSONDecoder().decode([WordData].self, from: data)
comp(decoded)
if let sound = decoded[0].phonetics[0].audio,
let sound2 = decoded[0].phonetics[1].audio {
print("sound = \(sound)")
let nonEmpty = (sound != "") ? sound : sound2 //write switch cases or another ternary with more urls to choose from if both are empty
self.audioUrl = URL(string: nonEmpty)
// url = URL(string: sound2)
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
self.player = AVPlayer(url: self.audioUrl!)
guard let player = self.player else { return }
player.play()
} catch let error {
print(error.localizedDescription)
}
}
//comp(decoded, entries.self)
} catch {
print("Error occured while decoding JSON into Swift structure \(error)")
}
})
dataTask.resume()
}
}
I need to call the searchBar.text value in another file inside this IBAction of class FirstDefinitionVC:
`
#IBAction func pronunciationButton(_ sender: UIButton) {
searchVC.fetchAudio(word: searchVC.word) { data in
self.wordData = data
}
}
This was one of my approaches to this, I tried to create a global model Word with an initializer also and it didn't work. Is there any way around it?
I'm getting this error on line var delegate = WeatherManagerDelegate()
import Foundation
protocol WeatherManagerDelegate {
func didUpdateWeather(weather:WeatherModel)
}
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?&appid=d73ab8784f3b294976fc6189b3e6eba2&units=metric"
var delegate = WeatherManagerDelegate()
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String)
{
//Create URL
if let url = URL(string: urlString){
//Create a URL Session.
let session = URLSession(configuration: .default)
//Give session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{ //on selecting url 2nd option the seletors you get select from data onwards to error and press enter and it will be set to closure format.
print(error!)
return
}
if let safeData = data {
/* let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)*/
if let weather = parseJSON(weatherData: safeData) {
self.delegate.didUpdateWeather(weather:weather)
}
}
}
//Start task
task.resume()
}
}
func parseJSON(weatherData: Data) ->WeatherModel? {
let decoder = JSONDecoder()
do{
let decodeData = try decoder.decode(WeatherData.self, from: weatherData)
let name = decodeData.name
let temp = decodeData.main.temp
print(decodeData.main.temp_max)
print(decodeData.main.temp_min)
print(decodeData.sys.country)
print(decodeData.weather[0].description)
let id = decodeData.weather[0].id
let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
print(weather.conditionName)
print(weather.temperatureString)
}
catch{
print(error)
return nil
}
}
}
and when I'm trying to make it an optional
var delegate = WeatherManagerDelegate?()
I'm getting this error
No exact matches in call to initializer
Replace
var delegate = WeatherManagerDelegate()
with
weak var delegate: WeatherManagerDelegate?
and update the calls to read self.delegate?.didUpdateWeather()
WeatherManager should not be responsible for creating its own delegate, that is something that should come from wherever it is begin used.
The weak attribute is almost always necessary when using delegates to avoid retain cycles.
Since weak can only be applied to class objects, you also need to indicate that in the protocol definition:
protocol WeatherManagerDelegate: AnyObject { ... }
I have a function that connects to an API to retrieve data. The API takes two parameters accessCode (provided by user in a text box) and then UDID (UDID of their device). I can parse the data from within the function, but only locally. I need to store the values that are returned but am unsure on how to return them properly. Essentially I need this to return the json object as a dictionary (I think...) so it can be parsed outside of the async task. I've read through the swift documentation and that's where I found out how to do the requests, but I can't find a way to store the returned values in memory for access outside of the function.
func getResponse(accessCode:String, UDID:String, _ completion: #escaping (NSDictionary) -> ()) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult as? NSDictionary
print(results)
completion(results!)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
First of all don't use NSDictionary in Swift, use native [String:Any] and declare the type as optional to return nil if an error occurs.
And never use .mutableContainers in Swift, the option is useless.
func getResponse(accessCode:String, UDID:String, completion: #escaping ([String:Any]?) -> Void)) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error else {
print(error)
completion(nil)
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
print(jsonResult)
completion(jsonResult)
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
task.resume()
}
Your mistake is that you don't consider the closure, you have to execute the entire code inside the completion handler
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { [weak self] result in
if let accessCodeFound = result?["Found"] as? Bool {
print("Value of Found during function:")
//If access code is valid, go to License view
print(accessCodeFound)
if accessCodeFound {
//Load License View
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self?.show(licenseController, sender: self)
}
}
}
}
}
Your completion closure should handle the obtained data. You would call the function like this:
getResponse(accessCode: "code", UDID: "udid", completion: { result in
// Do whatever you need to do with the dictionary result
}
Also, I'd recommend you to change your NSDictionary with a swift Dictionary.
This is what the API returns as a response
{
AccessCode = 00000000;
Client = "0000 - My Company Name";
EmailAddress = "brandon#brandonthomas.me";
FirstName = Brandon;
Found = 1;
LastName = Thomas;
Status = A;
UDIDregistered = 1;
}
And this is what calls the function. I am calling at after clicking a button after an access code is being entered in a text field.
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
var accessCodeFound: Bool? = nil
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { result in
accessCodeFound = result["Found"] as! Bool
print("Value of Found during function:")
print(accessCodeFound)
//accessCodeFound = true
}
//If access code is valid, go to License view
print("Value of Found after function:")
print(accessCodeFound)
//accessCodeFound = nil ???
//it seems the value is getting reset after the function completes
if accessCodeFound == true{
//Load License View
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self.show(licenseController, sender: Any?.self)
}
}
I'm working on retrieving some data via scraping in Swift and I'm having trouble passing the data between classes in my project.
Right now the functions successfully grab the data and can print it to the console, but I can't get the data back to the ViewController class.
Here's ViewController
#IBAction func tester(sender: UIButton) {
var xx = "DON'T PRINT THIS"
xx = grabTheDeetz()
print(xx)
}
and here's grabTheDeetz(). I'm looking to return data back to ViewController.
print("starting")
var x = ""
rip(onSuccess: {
data in
print(data)
})
return x
//////////////////////////////
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
guard let data = data else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .shiftJIS) else {
print("couldn't cast data into String")
return
}
//print(htmlString)
success(htmlString)
}
task.resume()
I know the data is being successfully retrieved, I'm just trying to get it back to the right place where it can be handled properly.
Functions after it in ViewController run before the data can be returned. (In this case, on button press "DON'T PRINT THIS" will always be printed, the the print(htmlString) will print the correct data.)
You can create a callback function like this and get back response from this function to any other controller.
private func process(using request:URLRequest, closure:#escaping (Error?, Any?) -> ()) {
let urlSessionTask = URLSession.shared.dataTask(with:request, completionHandler: { (data, response, error) in
var responseData:Any?
var errorResponse:Error?
closure(errorResponse, responseData)
})
urlSessionTask.resume()
}
So I am downloading a JSON file using a URLRequest().
I parse through it in order to get a specific string and I want to set the text of a label I have in my ViewController to that specific string.
I use a CompletionHandler in order to retrieve the function that gets the JSON file from another Swift file.
Here is the code of calling the function and setting the label:
class SecondViewController: UIViewController {
tr = TransportServices()
tr.getLyftData(origin: originstring, destination: destinationstring){ json in
//Parsing JSON in order to get specific data
self.lyftlabel.text = stringexample
}
}
and here is the code of getting the JSON
func getLyftData(origin: String, destination: String, completionHandler: #escaping ([String: Any]) -> ()){
let urlrequest = URLRequest(url: URL(string: urlstring)!)
let config = URLSessionConfiguration.default
let sessions = URLSession(configuration: config)
let task = sessions.dataTask(with: urlrequest) {(data, response, error) in
guard error == nil else {
print(error!)
return
}
guard let responseData = data else {
print("error, did not receive data")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any]{
completionHandler(json)
}
}
catch {
print("Error with URL Request")
}
}
task.resume()
}
This does the job, but in a very slow manner. I know that there is a runtime issue because UILabel.text must be set from main thread only, but I don't know any other way to fix it. Please help.
If you want to set label text in main thread use this:
DispatchQueue.main.async {
self.lyftlabel.text = stringexample
}