How can i download images from multiple API urls but skip the unreachable - swift

I am downloading images from an API (multiple urls into a CollectionView). Everything works fine until a link is not reachable and the app crashes. How can i download from the links that are reachable but skip the ones that are not?
guard let url = URL(string: self.photos[indexPath.item]) else { return cell }
cell.imageView.image = nil
DispatchQueue.global().async {
guard let image = UIImage(data: try! Data(contentsOf: url)) else { return }
let dataCompress = image.compress(to: 1000)
if let image = UIImage(data: dataCompress) {
DispatchQueue.main.async {
cell.imageView.image = image
self.photos.append(image)
}
}
}
return cell
}
"Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=256 "The file “665x10002.jpg” couldn’t be opened." UserInfo={NSURL=https://"*************}
Current code crashes when a link happens to be unreachable. Any help is appreciated.

As already mentioned, you should not be using try!.
You should never be using Data(contentsOf:) to fetch data from a remote server. This is explicitly mentioned in the documentation for that initializer.
Instead, you should use
URLSession.shared.dataTask(with: url) { data, response, error in
// process the data or handle the error
}

guard let image = UIImage(data: try? Data(contentsOf: url)) else { return }
The crash is occurring because you're using ! to force unwrap your try statement. Force unwrapping is dangerous (for this reason) and should only be used when you're certain that the information will be there, otherwise the app will crash. Replace ! with ?, as above, to gracefully return when the contents of the URL is not available.

Related

data of image from url is nil after unwrapping

I'm using Swift 4 and trying to get images from my server. Using ipconfig in terminal, I know my localhost's ip is 10.43.229.215.
the following code is to retrieve the image data and turn it into UIImage:
func GetImage(url:String) {
let image = String(localIP + url).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let url = URL(string: image)
print(image)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print("Client error!")
return
}
guard let data = data else {
print("Error: did not receive data")
return
}
DispatchQueue.main.async {
self.LensImageView.image = UIImage(data: data)
}
}).resume()
}
What I don't understand is that, the image string did show the image I want if I copy/paste the string to my browser
(http://10.43.229.215:3000/lensPic/%E5%A4%AA%E5%A6%83%E7%B3%96%E6%9D%8F.png)
However, error appears at the line self.LensImageView.image = UIImage(data: data) saying Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.
I'm really confused about:
How can data be nil if there is already a guard let method?
Why can the data be nil is I can show the image through my browser?
Any help is highly appreciated!
The problem lies in making a false assumption about what is nil. It has nothing to do with the image or the data. What’s nil is self.LensImageView, the outlet property.

do - try - catch code doesnt try or catch. just do's

thanks for any help upfront.
url session works perfect with connection, it prints the error as nil. but without it it prints the .localizedDescription just fine and shows me the right error, but then continues to do the do{ try } and crashes with this error in the try line:
Thread 6: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
now I am not even sure if this has anything to do with the errorhandling. thanks for any help with understanding whats going on or just solving the problem!
func getData(completion: (() -> ())?) {
let urlString = URL(string: "https://api.coinmarketcap.com/v1/ticker/")
URLSession.shared.dataTask(with: urlString!, completionHandler: { (data, response , error) in
print("before entering do-try-catch", error?.localizedDescription)
do {
//create Dictionary
print("downloading content")
self.coinData = try JSONSerialization.jsonObject(with: data!) as! [[String:Any]]
//set connection status
self.connection = true
//update tableView
DispatchQueue.main.async {
completion?()
}
} catch {
print("catch", error.localizedDescription)
//set connection status
self.connection = false
//update tableView
DispatchQueue.main.async {
completion?()
}
}
}).resume()
}
Thread 6: Fatal error: Unexpectedly found nil while unwrapping an Optional value is a common problem for beginners.
You try to work with data that is not there.
So for example in your code you force to execute try JSONSerialization.jsonObject(with: data!)
When data is nil the code will crash.
The same at the beginning URLSession.shared.dataTask(with: urlString!, completionHandler: { (data, response, error) {}
When urlString is not a valid URL the code will be crash. (In this case the url seems to be valid).
For more information have a look here:
https://stackoverflow.com/a/24034551/4420355
Try the following snipped it should work
if let data = data {
self.coinData = try JSONSerialization.jsonObject(with: data) as? [[String:Any]]
//... work with coinData
}
Reason why it is crashing is because data is Optional and it should be nil or has some value. On line
self.coinData = try JSONSerialization.jsonObject(with: data!) as! [[String:Any]]
Compiler thinks:
Let's take a look and unwrap this Optianal variable. But it's nil, there is "nothing"! So what should I unwrap? Let's crash and throw Fatal Error message.
How to easily avoid this with Optional binding:
if let data = data {
....do something with data
} else {
...print error message
}
For more information take look at this brilliant answer.
https://stackoverflow.com/a/32170457/3046686

Guarding against non-200 responses when loading images in Swift 4

When loading in external images with Swift 4, I'm having trouble when a URL responds with a non 200 response in the code block below. For instance, when serving an image that is sending a 401, the app crashes on line 2 of this example with the following error:
Thread 29: Fatal error: Unexpectedly found nil while unwrapping an Optional value
How can I catch this problem and serve up a different URL?
let imageUrl:URL = URL(string: imageUrlString)!
let imageData:NSData = NSData(contentsOf: imageUrl)!
let imageView = UIImageView(frame: CGRect(x:11, y:90, width:80, height:80))
// When from background thread, UI needs to be updated on main_queue
DispatchQueue.main.async {
let image = UIImage(data: imageData as Data)
imageView.image = image
imageView.layer.zPosition = 1
imageView.contentMode = UIViewContentMode.scaleAspectFit
self.view.addSubview(imageView)
}
From the NSData(contentsOf: documentation (unfortunately this note is missing in Data):
So please take this note seriously and use URLSession
guard let imageUrl = URL(string: imageUrlString) else { // ... handle the `nil` case }
let task = URLSession.shared.dataTask(with: imageUrl) { data, response, error in
if let error = error { print(error); return }
guard let response = response as? HTTPURLResponse else { print("Response is not HTTPResponse"); return }
if response.statusCode != 200 { print("response error:", response.statusCode); return }
DispatchQueue.main.async {
let image = UIImage(data: data!) // `data` can be unwrapped safely if `error` is nil
imageView.image = image
imageView.layer.zPosition = 1
imageView.contentMode = .scaleAspectFit
self.view.addSubview(imageView)
}
}
task.resume()
You can handle error and response separately. If error is nil then data is non-nil and vice versa.
NSData(contentsOf: url) is a failable initialiser which means it returns an optional NSData instance and one instance in which it fails is for a non 200 response.
Your code then tried to force unwrap it using the '!' character which is why you get an error.
First you need to decide what to do if it does fail, i.e. are you going to not show an image, show a default image, etc.
Then change your code to take account of that. So something like this:
let imageUrl:URL = URL(string: imageUrlString)!
if let imageData = NSData(contentsOf: imageUrl) {
let imageView = UIImageView(frame: CGRect(x:11, y:90, width:80, height:80))
// When from background thread, UI needs to be updated on main_queue
DispatchQueue.main.async {
let image = UIImage(data: imageData as Data)
imageView.image = image
imageView.layer.zPosition = 1
imageView.contentMode = UIViewContentMode.scaleAspectFit
self.view.addSubview(imageView)
}
} else {
// Do whatever you want if getting the image fails, e.g. non 200 response.
}
EDIT
As has been pointed out NSData(contentsOf:) should not be used for network related queries however even for local queries it can still fail so I will leave the answer up as that is the cause of the original error.

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.

Parse Video Upload - Fatal error: unexpectedly found nil while unwrapping an Optional Value

I am trying to upload a video to Parse
If I just try to upload the video like so:
let videoData = NSData(contentsOfURL: url)
let videoFile = PFFile(name: "video.mov", data: videoData)
videoUploadObject["Video"] = videoFile
I will receive the error. I tried to remove this by running something like the following.
Main.sharedMain.userVideoOutputURL
returns:
file:///private/var/mobile/Containers/Data/Application/3B78A154-4340-432B-817A-2857EBA8064A/tmp/video.mov
Here is my full code:
let url = Main.sharedMain.userVideoOutputURL!
if let videoData = NSData(contentsOfURL: url) {
let videoFile = PFFile(name: "video.mov", data: videoData)
videoUploadObject["Video"] = videoFile
print("Video File \(videoFile)")
} else {
print("Else")
}
Else is always printed? What can I do to fix this? Thank you!
The URL looks fine (iOS doesn't use "/User/blah/blah/blah" for applicaion storage).
My first thought would be to check the data is writing correctly. NSData has a "writeToFile: options:" method that returns a bool. This will return false if writing has failed, and also throw an error which should give you some extra information.
let url = <YOUR_URL>
do {
// put your options in here
try data.writeToURL(url, options: NSDataWritingOptions.DataWritingAtomic)
} catch _ {
print(error.localizedDescription)
}
If writing has succeeded, then you'll want to try loading the data using "contentsWithUrl: options:". This also throws an error if it fails, and so using the "localisedDescription" property of the thrown error object you should be able to get to the bottom of why it isn't loading.
let url = <YOUR_URL>
var data: NSData? = nil
do {
// put your options in here
try data = NSData(contentsOfURL: url, options: NSDataReadingOptions.DataReadingUncached)
} catch _ {
print(error.localizedDescription)
}
Hope that helps :)