I'm calling to refresh label after refreshQuote() function finishes. When look at the log to see what happened with label.text I can see it has been replaced however the view still looks the same.
Im calling refreshQuote() from viewDidload.
When calling setQuoteLabel directly from viewDidLoad it all works fine
Please help, loosing hope now.
Thanks in advance
func refreshQuote()
{
var url : String = "https://api.parse.com/1/classes/Quotes"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.addValue("------", forHTTPHeaderField: "X-Parse-Application-Id")
request.addValue("------strong text", forHTTPHeaderField: "X-Parse-REST-API-Key")
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
NSLog(error.debugDescription);
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil)
{
var allResults: NSArray = jsonResult["results"] as NSArray
var pickedResult: NSDictionary = allResults[0] as NSDictionary
let q:String = pickedResult["message"] as String!
self.setQuoteLabel(q);
} else
{
// couldn't load JSON, look at error
}
})
}
func setQuoteLabel (quote:String)
{
NSLog(quote)
quoteLabel.text = quote;
quoteLabel.setNeedsDisplay();
NSLog(quoteLabel.text!)
}
All UI components must be updated from the main thread, whereas in your case you are updating most likely from a different thread, because the code is in an asynchronous block.
To force a block of code to be executed in the main thread, you can use dispatch_async as follows:
dispatch_async (dispatch_get_main_queue ()) {
self.setQuoteLabel(q)
}
Related
I am currently trying to download, parse and print JSON from an URL.
So far I got to this point:
1) A class (JSONImport.swift), which handles my import:
var data = NSMutableData();
let url = NSURL(string:"http://headers.jsontest.com");
var session = NSURLSession.sharedSession();
var jsonError:NSError?;
var response : NSURLResponse?;
func startConnection(){
let task:NSURLSessionDataTask = session.dataTaskWithURL(url!, completionHandler:apiHandler)
task.resume();
self.apiHandler(data,response: response,error: jsonError);
}
func apiHandler(data:NSData?, response:NSURLResponse?, error:NSError?)
{
do{
let jsonData : NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary;
print(jsonData);
}
catch{
print("API error: \(error)");
}
}
My problem is, that the data in
do{
let jsonData : NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary;
print(jsonData);
}
remains empty.
When I debug,the connection starts successfully, with the given url as a parameter. But my jsonData variable doesn't get printed. Instead the catch block throws the error, stating that there is no data in my variable:
API error: Error Domain=NSCocoaErrorDomain Code=3840 "No value."
Can someone please help me with this?
What am I missing?
Thank you all very much in advance!
[Edited after switching from NSURL Connection to NSURLSession]
Here's an example on how to use NSURLSession with a very convenient "completion handler".
This function contains the network call and has the "completion handler" (a callback for when the data will be available):
func getDataFrom(urlString: String, completion: (data: NSData)->()) {
if let url = NSURL(string: urlString) {
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { (data, response, error) in
// print(response)
if let data = data {
completion(data: data)
} else {
print(error?.localizedDescription)
}
}
task.resume()
} else {
// URL is invalid
}
}
You can use it like this, inside a new function, with a "trailing closure":
func apiManager() {
getDataFrom("http://headers.jsontest.com") { (data) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: [])
if let jsonDict = json as? NSDictionary {
print(jsonDict)
} else {
// JSON data wasn't a dictionary
}
}
catch let error as NSError {
print("API error: \(error.debugDescription)")
}
}
}
I am getting this error This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes.This will cause an exception in a future release. I don't know what is causing this error. Can anybody help me.
func getUserDataFromTwitterWithUser(user : PFUser)
{
//NRLoader.showLoader()
let strTwURL = "https://api.twitter.com/1.1/users/show.json? screen_name="+PFTwitterUtils.twitter()!.screenName! + "&access_token="+PFTwitterUtils.twitter()!.authToken!
let twURL = NSURL (string: strTwURL)
let request = NSMutableURLRequest(URL: twURL!, cachePolicy: NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 2.0) as NSMutableURLRequest
PFTwitterUtils.twitter()?.signRequest(request)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error == nil {
var jsonOptional = Dictionary<String, AnyObject>()
do {
jsonOptional = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! Dictionary<String, AnyObject>
// use jsonData
} catch {
// report error
}
var userName = ""
if let screenName = jsonOptional["screen_name"] as? String{
userName = screenName
}
else if let name = jsonOptional["name"] as? String{
userName = name
}
var profilePicUrl = ""
if let picUrl = jsonOptional["profile_image_url"] as? String{
profilePicUrl = picUrl
}
AppUser.currentUser()?.username = userName
AppUser.currentUser()?.profileAwsURL = profilePicUrl
//NRLoader.hideLoader()
//if ParseUtils.isLoggedInUserIsAnonymous() {
let signUpVC:SignMeUpViewController = self.storyboard!.instantiateViewControllerWithIdentifier("SignMeUpViewController") as! SignMeUpViewController
signUpVC.isFromLogin = true
self.navigationController!.pushViewController(signUpVC, animated: true)
//} else {
// self.pushToSubmitDreamViewController()
//}
}
else {
//NRLoader.hideLoader()
NRToast.showToastWithMessage(error!.description)
}
}).resume()
}
The dataTaskWithRequest call runs in the background and then calls your completion handler from the same thread. Anything that updates the UI should run on the main thread, so all of your current handler code should be within a dispatch_async back onto the main queue:
dispatch_async(dispatch_get_main_queue()) {
// Do stuff to UI
}
Swift 3:
DispatchQueue.main.async() {
// Do stuff to UI
}
Therefore, ideally all the code you currently have within if error == nil should be off in another function, say called handleRequest, so your current code becomes:
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
self.handleRequest(...)I
})
}
Swift 3
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error == nil {
DispatchQueue.main.async {
self.handleRequest(...)I
}
}
Should try Symbolic Breakpoint to detect the issue:-
Then put your UI Update code in main thread
DispatchQueue.main.async {}
You'd better change UI only in the main thread
swift3,
let liveInfoUrl = URL(string: "http://192.168.1.66/api/cloud/app/liveInfo/7777")
let task = URLSession.shared.dataTask(with: liveInfoUrl! as URL) {data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
print(String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) ?? "aaaa")
//do some ui work
}
}
if the above suggestions still give you no joy then the sure-est way is to redesign your functions so that getting what you need with
URLSession.shared.dataTask
then hands over so a variable declared outside that function, then a separate UIControl ( button, swipe etc ) displays it to a label or textview or whatever.
After all that is what the error message is telling you. they're separate concerns
Im having an issue, I am trying to create a method which accepts a PFObject as a parameter. The PFObject in this case is the facebook picture URL. The method takes the URL and basically converts it into an image. I can get it to work if i just use this block of code without trying to make it into a method, however I would like to create a method out of this so that I dont have to keep repeating myself. When i try to return the users image i keep getting the error cannot convert the expressions type UIImage to type void swift
Here is the code
func downloadFBUserImage(object: PFObject?) -> UIImage? {
var userProfilePhotoURLString = object?.valueForKey("pictureURL") as String?
if userProfilePhotoURLString != nil {
var pictureURL: NSURL = NSURL(string: userProfilePhotoURLString!)!
var urlRequest: NSURLRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: { (NSURLResponse response, NSData data, NSError error) -> Void in
if error == nil && data != nil {
var userProfilePic: UIImage? = UIImage(data: data)
return userProfilePic
}
})
return nil
}
The error is reporting that the completionHandler of the sendAsynchronousRequest is defined to pass you the response, data, and error objects, but that it expects that completionHandler, itself, to not return any values. But you're trying to return a value from within that completionHandler closure.
Bottom line, you cannot simply return the UIImage from your function, because you are performing asynchronous method (i.e. the data is returned later even though you return from the function immediately). So, employ asynchronous pattern:
func downloadFBUserImage(object: PFObject?, completionHandler: (UIImage?, NSError?) -> Void) {
if let userProfilePhotoURLString = object?.valueForKey("pictureURL") as? String {
let pictureURL = NSURL(string: userProfilePhotoURLString)!
let urlRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in
if data != nil {
var userProfilePic = UIImage(data: data)
completionHandler(userProfilePic, nil)
} else {
completionHandler(nil, error)
}
}
}
}
And you'd call it using the same completion handler pattern that sendAsynchronousRequest does:
downloadFBUserImage(object) { image, error in
if image == nil {
println(error)
} else {
// use the image here
}
}
// but don't try to use asynchronously retrieved image here
You cannot return to the completion block like that. The completion block does not have a return parameter. This is why you are getting an error.
For updating the image after download, you can pass in a block along with your downloadFBUserImage function like below.
I used dispatch_async because UI updates have to be done on the main thread.
func downloadFBUserImage(object: PFObject?, completion completionBlock:(UIImage) -> ()) -> (){
var userProfilePhotoURLString = object?.valueForKey("pictureURL") as String?
if userProfilePhotoURLString != nil {
var pictureURL: NSURL = NSURL(string: userProfilePhotoURLString!)!
var urlRequest: NSURLRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: { (NSURLResponse response, NSData data, NSError error) -> Void in
if error == nil && data != nil {
if let userProfilePic = UIImage(data: data) {
completionBlock(userProfilePic)
}
}
})
}
}
It can be called like this
func do() {
downloadFBUserImage(pfObject, completion: { (image) -> () in
//updateImage
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// UI updates
}
})
}
I had the following method in a separate class:
class API: NSObject {
var data = NSData()
var delegate: APIProtocol?
func getItems(callback: (Array<Image>) -> ()) {
let urlPath: NSString = "http://localhost:3000/files"
let url = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
let config = NSURLSessionConfiguration.defaultSessionConfiguration() as NSURLSessionConfiguration
let session = NSURLSession(configuration: config) as NSURLSession
var dataTask = NSURLSessionDataTask()
dataTask = session.dataTaskWithRequest(request) { (data, response, error) in
if (error == nil) {
println("API at URL \(url)")
let responseArray = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSArray
var images = Image[]()
for item: AnyObject in responseArray {
var location = Image(dict: item as NSDictionary)
images.append(location)
}
var img = images[0] as Image
callback(images)
//self.delegate?.didReceiveResponse(responseArray)
}
}
dataTask.resume()
}
}
I couldn't get my tableView to reload when calling self.tableView.reloadData() inside the callback() until I added the dispatch_async() around it.
My questions are:
1) Why wouldn't it work without it and is it the proper thing for me to do now that it's refreshing the tableView correctly?
2) Is there another way to get it working without having to add the dispatch on the main queue?
api.getItems() { (theArray) in
dispatch_async(dispatch_get_main_queue(), {
self.images = theArray
self.tableView.reloadData()
if (viaPullToRefresh) {
self.refreshControl.endRefreshing()
}
})
}
When creating a NSURLSession you can specify the delegate/completion queue. If you don't specify a queue
the session creates a serial operation queue for performing all
delegate method calls and completion handler calls.
So this means that your callbacks are called on a private serial queue. Now, all UI must be updated on the main queue, this is why tableView.reloadData() wasn't working.
To remove the dispatch call to the main_queue create the session like this
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: NSOperationQueue.mainQueue())
Today I'm trying to get some data from a webpage.
It keeps crashing saying "Exc Bad Access", any suggestion ?
class CodeViewController: NSObject {
#IBOutlet var output_box : NSTextField
#IBOutlet var url_textField : NSTextField
var output : NSString = "Unable to load data"
func downloadHTML (path: String) -> String {
var url = NSURL(string: "\(path)")
var request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
self.output = NSString(data: data, encoding: NSUTF8StringEncoding)
//EXC_BAD_ACCESS when trying to set self.output new value
}
return output
}
#IBAction func retrive(sender: AnyObject!) {
println(downloadHTML(url_textField.stringValue))
}
}
I suspect that "url_textField.stringValue" is nil at the point that you pass it in. Trying it with a fixed string works. But note also that the function will return before the async request completes, and so your code will always print "Unable to load". If you add a line that assigns the output to your output_box in the completion handler, you will see the the text box update once the request completes...
The code I tried, which works, is...
class CodeViewController: NSObject {
var output : NSString = "Unable to load data"
func downloadHTML (path: String) -> String {
var url = NSURL(string: "\(path)")
var request = NSURLRequest(URL: url)
let completionBlock: (NSURLResponse!, NSData!, NSError!) -> Void = {response, data, error in
self.output = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Asynch completed \(self.output)")
}
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: completionBlock)
return output
}
}
var c: CodeViewController? = nil
c = CodeViewController()
c!.downloadHTML("http://www.google.com")
println("Immediate \(c!.output)")
and its output is:
Immediate Unable to load data
Asynch completed <!doctype html><html ...