My catch isn't working on url request - swift

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.

Related

Why my DateTask code block does not work?

I create a request to the server, and in the end I expect to receive data, which I then transform into a model using a function, for this I created a session
func fetchNewsData(forCoutry country: String, category: String, complition: #escaping (NewsDataModel) -> ()) {
let urlString = "some url string"
guard let url = URL(string: urlString) else { return }
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
print ("ERROR: \(error)")
guard let data = data else { return }
guard let newsData = self.parseJSON(withData: data) else { return }
complition(newsData)
}
task.resume()
}
but the following code just doesn't work
print ("ERROR: \(error)")
guard let data = data else { return }
guard let newsData = self.parseJSON(withData: data) else { return }
complition(newsData)
I used breakpoints to find out until what point everything is going well, and I realized that this particular block of code is not working.
when I set a breakpoint between the let session and the let task, the code stopped there, but when I set my code to an print(error), this breakpoint did not work
I used the function fetchNewsData in viewDidLoad and I want to work to fill the array with elements that I expect to receive from the data that will come on this request, but my array does not receive any elements, and it remains empty, because of this my application does not work
why part of the code doesn't work, and how can I get the data I need from it?
The problem turned out to be a poor understanding of closures
I was not calling my method correctly to get the data. Having figured it out, I realized that the problem is precisely in a different approach when calling this method

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

Why is UIImage? nil here?

I'm trying to bridge React and Swift code by passing a string for an image path, which I've verified appears correctly on the Native side, and having a bit of an issue. The image path comes from React as NSString, and my goal is to pass that as a String to a Native function that will ultimately send data back to React.
Here's a snippet of some code that handles part of this
classifyImage(value as String)
and some of the body of the classifiyImage is as follows:
#objc func classifyImage(_ image: String) {
let imageData = Data(base64Encoded: image, options: .ignoreUnknownCharacters)!
let uiImage = UIImage(data: imageData)
guard let orientation = CGImagePropertyOrientation(
rawValue: UInt32((uiImage?.imageOrientation.rawValue)!)) else {
return
}
...code
}
The exact error is at the line with the rawVale, reading
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Here's more info if it may help...
Image data can come from the camera as such image NSMutableString "file:///var/mobile/Containers/Data/Application/54691469-2196-444E-9B45-C0D6F2CABEBC/Library/Caches/Camera/EEC3631C-3E96-44DA-B258-411363A2F10C.jpg" 0x00000002815a8420
or from the phone's gallery image String "ph://8F109DC0-CE95-4D0A-9D11-1B2E9CE6B8D3/L0/001"
Image from a file
First, we need to turn the string into a URL, then the URL into data like so:
let url = URL(string: image)
do {
let data = try Data(contentsOf: url)
} catch {
print(e)
}
Then we can use it to create the image.
do {
let data = try Data(contentsOf: url)
let image = UIImage(data: data)
guard let orientation = CGImagePropertyOrientation(
rawValue: UInt32(image.imageOrientation.rawValue)) else {
print("that didn't work")
return
}
} catch {
print(e)
}

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

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

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.