My UILabel isn't updating after I change the text value - swift

This is in my viewDidLoad method
http://i.imgur.com/zTD7wHr.png
This is my first post so stackoverflow won't let me post images I guess I'll just have to drop this link.
Basically, I'm trying to assign text from a json located here : http://catfacts-api.appspot.com/api/facts using the code above. Ive gone through the values in the Xcode with a breakpoint at the self.catLabel.text = catFactArray[0] as? String line, and the catLabel.text string has the value I want it to, but the label does not update. Ive already gone over this with a few people and I'm really not sure where the problem is so any help would be appreciated.

If you are doing any network operation in background and want to update the UI, those UI update should be done on main thread.
Try it.
dispatch_async(dispatch_get_main_queue(), {
self.catLabel.text = catFactArray[0] as? String
self.view.setNeedsDisplay()
self.catLabel.setNeedsDisplay()
})

Your UI layout updates (label text changes, frame modifications..) must be to the main thread, in your code you make a network call that it's probably launched in a background thread:
let url = NSURL(string: "http://catfacts-api.appspot.com/api/facts")
let request = NSURLRequest(URL: url!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler : { data, response, error in
if let feed = (try? NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers)) as! NSDictionary! {
if let catFactArray = feed.valueForKeyPath("facts") as! NSArray! {
if !NSThread.isMainThread {
print("Yes , I'm not in the main thread..")
}
dispatch_async(dispatch_get_main_queue(), {
print("Now I'm in the main thread..")
self.catLabel.text = catFactArray[0] as? String
self.view.setNeedsDisplay()
self.catLabel.setNeedsDisplay()
self.catLabel.layoutIfNeeded()
}
}
}
})
task.resume()

Related

Download image from URL and save it to Core Data swift

There is I am trying to download image from URL and save it to variable of model. But it's not setting the image. Inside setImage I saw that it's downloading some data. URL's are checked and working. But when I check it for nil it's showing "empty input", "empty output".
let inputImage = UIImageView()
let outputImage = UIImageView()
DispatchQueue.main.async {
let inputURL = URL(string: "someURL")
let outputURL = URL(string: "someURL")
inputImage.kf.setImage(with: inputURL)
outputImage.kf.setImage(with: outputURL)
}
let coreDataModel = CoreDataModel()
if let inputImageData = inputImage.image?.pngData()
{ cloudAnalysisModel.input_image = inputImageData }
else
{ print("empty input") }
if let outputImageData = outputImage.image?.pngData()
{ coreDataModel.output_image = outputImageData }
else
{ print("empty output") }
I think you have not understood how asynchronous programming works. The block or completionHandler (the one that starts with { and ends with }) gets executed after url is called and response is obtained
What I mean to say here is , that block maybe executed at any time and the code after that is executed right away i.e the code from let coreDataModel onwards
So it is obvious that both the inputImageData and outputImageData are going to be null
Now coming to the solutions:
What you could do is:
Shift all the code inside the callback block instead of keeping it outside and show the user some progressbar or UI
Use DispatchGroup which keeps track of the DispatchQueue More info here

Change a variable within a URLSession callback

I want to check if a file exists and then change the context of a variable.
If I'm working with playgrounds, it will work but coding in a viewport, nothing happens.
I check if the file named "nameoffile.xml" exist, using the statuscode, and then the var color need to change into "blue".
Below you will find my (not working) code.
let url = URL(string: "http://myurl/nameoffile.xml")!
var color = ""
func checkFile()
{
let req = NSMutableURLRequest(url: url)
req.httpMethod = "HEAD"
req.timeoutInterval = 1.0
var response: URLResponse?
var task = URLSession.shared.dataTask(with: url) {(data, response, error) in
if let httpStatus = response as? HTTPURLResponse
{ if httpStatus.statusCode == 200 {
self.kleur = "blue"
}
}
}
From your example it isn't totally clear if the color and url are attributes of the class you are calling checkFile on. I'll assume they are, because it makes the most sense.
So if the colour you want to change is an attribute of UIView.backgroundColor and you want to see the change immediately, you need to change it on a main thread like this:
// Your view controller you are using for the task
class MyViewController: UIViewController {
// The ui element you want to change the colour of
// If you are using a storyboard you would have a #IBOutlet here
let button = UIButton()
func checkFile() {
// I've noticed you aren't using the NSMutableURLRequest anywhere
// Also I've cleaned up your code a bit, hope you don't mind
URLSession.shared.dataTask(with: url) { data, response, error in
guard let httpStatus = response as? HTTPURLResponse else {
return
}
if httpStatus.statusCode == 200 {
DispatchQueue.main.async {
// This line of code will change the color
self.button.backgroundColor = .blue
}
}
}
}
}
Hope this helps. If I haven't guessed your problem, feel free to change your question or leave a comment.

My catch isn't working on url request

I am trying to do my first try catch in Swift.
Essentially the user can give a name to something. So say he/she types in Gmail and hits submit.
Since G is the first letter of the String (doesn't matter if its lower or upper case) the image will load a picture of a G.
If the user also adds in a URL so say gmail.com or www.gmail.com it will pull the favicon for gmail.
So far so good.
HOWEVER. If the person types gmailllllll.com
it loads a picture of a globe (which I think is the default)
What I am trying to do is only put the gmail favicon in the image view if it is actually the image. If it is nil then I want to put the image of the G which I provided.
I understand why my code isn't working. I just do not know how to add to it to do what I want.
DispatchQueue.main.async {
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArray[row].serviceUrl)"
let myURL = URL(string: myURLString)
do {
let myData = try Data(contentsOf: myURL!)
cell.serviceLogoImage.image = UIImage(data: myData)
} catch {
cell.serviceLogoImage.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
}
Also, If the user were to type in www.go google.com
with the space it has an optional crashe!
I tried saying
if myData == nil {...}
But it says it always returns false...
Any help would be appreciated
Optional binding
This solution avoids force unwrapping (ie myURL!) with optional binding.
Note that try? returns an optional. It returns nil rather than throwing an error. It is appropriate when you want to handle all errors in the same way.
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArray[row].serviceUrl)"
if let myURL = URL(string: myURLString), let myData = try? Data(contentsOf: myURL), let image = UIImage(data: myData) {
cell.serviceLogoImage.image = image
}
else {
cell.serviceLogoImage.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
This expression Data(contentsOf: myURL!) is unsafe. I think you expect it to raise an error, but it will just crash your code (by design) if the URL cannot be parsed. Here is a safe example:
if let url = URL(string:mystring) {
do {
let data = try Data(contentsOf:url)
} catch {
print ("network issue")
}
} else {
print("bad string")
}
First of all, never EVER use ! unless you are sure that there is a value and not nil because forced unwrapping will crash your entire app. Second of all, I don't really get what are you trying to do with that Dispatch. I mean, if the user hits submit, you should make a function for example and you would call that when the submit button is tapped.
I would write something like this:
func updateImage() {
var myData: Data?
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArra‌​y[row].serviceUrl)"
let myURL = URL(string: myURLString)
if let url = myURL {
do {
myData = try Data(contentfOf: url)
} catch {
print("error")
}
}
if let data = myData {
cell.serviceLogo.image = UIImage(data: data)
} else {
cell.serviceLogo.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
I really can't figure out what you were trying with that Dispatch, but eventually I'm thinking that first you should use a Dispatch.global(qos: .background).async where you would first verify what the user has entered and eventually download the necessary photos and then, when you're trying to update the UI, you should come back to the main thread with Dispatch.main.async. I guess.

Swift 2 Parse and KingFisher cache images

I'm using KingFisher https://github.com/onevcat/Kingfisher
library so i can cache the images and if there is anyone familiar with it i want some hints.
So i have the following code
let myCache = ImageCache(name: recipesClass.objectId!)
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let optionInfo: KingfisherOptionsInfo = [
.DownloadPriority(0.5),
.CallbackDispatchQueue(queue),
.Transition(ImageTransition.Fade(1)),
.TargetCache(myCache)
]
if let imageFile = recipesClass[RECIPES_COVER] as? PFFile {
let URL = NSURL(string: imageFile.url!)!
cell.coverImage.kf_setImageWithURL(URL, placeholderImage: nil,
optionsInfo: optionInfo,
progressBlock: { receivedSize, totalSize in
print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
},
completionHandler: { image, error, cacheType, imageURL in
print("\(indexPath.row + 1): Finished")
})
} else {
cell.coverImage.image = UIImage(named:"logo")
}
When i first enter the View it loads normally the images with this good anymation. But i also have a refresh button which makes a query to Parse and it checks if there is any new Recipe and then it reloads the data from the collection view and it prints "Finished"
Does this means that it downloads the images again? Or it loads them from Cache??
I'm asking because it appends the images in a different way inside the cells rather than the first time that it loads.
Any idea?
P.S. what i want to do is that in each cell i want to cache the image with the object ID of each recipe so when the cell loads and it has the image cached with this unique object id, to load it from cache and not to download it.
try this code:
var imageView:UIImageView!
let mCa = ImageCache(name: "my_cache")
let imagePath = getImagePath("image url")
imageView.kf_setImageWithURL(NSURL(string: imagePath)!,placeholderImage: UIImage(named: "DefaultImageName"),optionsInfo: [.TargetCache(mCa)])

function return too early

Can anyone explain why the completion returns empty array?
The function:
import Foundation
class IMBD{
func searchMovies(searchText:String, completion: (result: [Movies]) -> Void){
var movies = [Movies]()
let replacedMovieTitle = searchText.stringByReplacingOccurrencesOfString(" ", withString: "+")
let URLString = "http://www.omdbapi.com/?s=\(replacedMovieTitle)&y=&r=json"
let URL = NSURL(string: URLString)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(URL!, completionHandler: {(data, response, error) -> Void in
do{
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary
if let search = jsonData["Search"] as? [[String : AnyObject]]{
for hit in search{
guard let title = hit["Title"] as? String else{
print("returna title")
return
}
guard let year = hit["Year"] as? String else{
print("returna year")
return
}
guard let imbdID = hit["imdbID"] as? String else{
print("returna imbd")
return
}
guard let poster = hit["Poster"] as? String else{
print("returna poster")
return
}
let movie = Movies(title: title, released: year, poster: poster, imbdID: imbdID)
movies.append(movie)
}
}
}catch{
}
}).resume()
completion(result: movies)
}
}
The call:
imbd.searchMovies(searchtext!, completion: { (result) -> Void in
self.movieList = result
})
You have to call your completion handles inside the dataTaskWithURL closure, not after it. This runs asynchronously, so if you call your completion outside of the closure, it would be called before the asynchronous request had a chance to retrieve anything.
Also, remember that this closure doesn't run on the main thread, so you likely want to also dispatch this to the main queue (from within the dataTaskWithURL).
For example:
class IMDB {
func searchMovies(searchText:String, completion: (result: [Movie]?, error: NSError?) -> Void) -> NSURLSessionTask {
var movies = [Movie]()
let allowedCharacters = NSCharacterSet.alphanumericCharacterSet().mutableCopy() as! NSMutableCharacterSet
allowedCharacters.addCharactersInString("-._* ")
let replacedMovieTitle = searchText.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacters)!
.stringByReplacingOccurrencesOfString(" ", withString: "+")
let URLString = "http://www.omdbapi.com/?s=\(replacedMovieTitle)&y=&r=json"
let URL = NSURL(string: URLString)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(URL!) { data, response, error in
guard error == nil && data != nil else {
dispatch_async(dispatch_get_main_queue()) {
completion(result: nil, error: error)
}
return
}
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary
if let search = jsonData["Search"] as? [[String : AnyObject]]{
for hit in search{
guard let title = hit["Title"] as? String else{
print("returna title")
continue
}
guard let year = hit["Year"] as? String else{
print("returna year")
continue
}
guard let imdbID = hit["imdbID"] as? String else{
print("returna imbd")
continue
}
guard let poster = hit["Poster"] as? String else{
print("returna poster")
continue
}
let movie = Movie(title: title, released: year, poster: poster, imdbID: imdbID)
movies.append(movie)
}
}
dispatch_async(dispatch_get_main_queue()) {
completion(result: movies, error: nil)
}
} catch let error as NSError {
dispatch_async(dispatch_get_main_queue()) {
completion(result: nil, error: error)
}
}
}
task.resume()
return task
}
}
A couple of other changes in the above code snippet include:
Add guard in case there was a fundamental network error (e.g. remote server down, no Internet access, etc.)
In the guard statements that are checking for nil values, rather than performing a return (in which case no further results will be gathered), you might want to just continue (i.e. skip to the next record). You generally see guard in conjunction with return, but in this case, continue is probably more appropriate.
Frankly, you might want to take this a step further and consider whether some of these might be optional, rather than discarding the whole record. Notably, poster strikes me as something that might be nil if there was no poster available. Maybe some of the others should be optional, too, (e.g. if a movie hasn't been released yet, might it not have a release date?).
The occurrences of "imbd" have been replaced with "imdb".
The Movies class has been renamed to Movie (since each instance is a single movie, not a collection of them).
I changed the completion block to make [Movie] optional and to return the NSError. Without that, you don't have a way to differentiate between "couldn't find a title of that name" and "whoops, something went wrong".
When we call the completion closure from within the dataTaskWithURL, it can be very useful to have searchMovies dispatch completion calls back to the main queue, like above. This is because UI updates must always happen on the main thread, and frequently when you write routines like this, it is so you can update UI or the model with results.
This is not always necessary to do it like this (you might want to just have this call completion directly from the background thread and let the routine that called searchMovies manually dispatch stuff to the main thread itself), but I often find it useful to have this search method just dispatch the completion back to the main thread and be done with it.
As a matter of practice, I always return the NSURLSessionTask when performing requests. You might not need it now, but at some future date, you might want the ability to cancel an on-going request, and having a reference to the task can be useful. It doesn't hurt to return it, and it can be useful.
You probably should be percent escaping the values you add to the URL. Notably the presence of & or + characters could be problematic. Note, in this case, it looks like this site isn't handling it appropriately, anyway, but it's good to get in the habit of properly percent-escaping values in a query.
Personally, I keep this percent escaping logic in a String extension, but I wanted to keep this simple, so I embedded it right in this method, but hopefully it illustrates the idea.