This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 4 years ago.
This question might be a duplicate, but I think my case is different.
I built an app to fetch news articles from different sources. The problem I have is some sources may not include article image which causing my app to crash, please have a look at my code:
extension UIImageView {
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!) // I get Fatal error on this line
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error as Any)
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
I tried to modify the code to not force unwrap the image if found nil, but It just didn't work, because I get other errors in other places.
If anyone can point the mistake, please, by writing how the code should be in case image found nil to not crash the app. It will be extremely appreciated!
The URL is force unwrapped. You should unwrap optional by using guard let construction:
guard let url = URL(string: url) else { return }
let urlRequest = URLRequest(url: url)
I tried to modify the code to not force unwrap the image if found nil,
Well yes it's duplicated question.
However this problem is solved by OptionalChaining i recommend reading about it Here,
Now for the part on how to solve it quickly, you can try to add this line under your request, which is going to check if its nil or not, and incase it was nil it will simply escape the closure without causing any crashes or problems.
guard let data = data else {return}
There are many types of OptionalChaining, each one has its own usage but they all serve the same propose of handling the nil values without causing a crashes.
DispatchQueue.main.async {
guard let data = data {
self.image = UIImage(data: data)
} else {
print(value is nil.)
}
}
This is the way to safe unwrap the optional value. It will not crash if Data is nil on unwrapping .
Related
This question already has answers here:
Loading/Downloading image from URL on Swift
(39 answers)
Closed 5 years ago.
EDIT 3: Please also read my comment in the "answered" tagged answer. I think I won't use my synchronous method but change to the suggested asynchronous methods that were also given!
Ok I am struggling with some basic concepts of showing images from an URL from the internet on my app.
I use this code to show my image on an UIIamgeView in my ViewController:
func showImage() {
let myUrlImage = URL(string: linkToTheImage)
let image = try? Data(contentsOf: myUrlImage!)
imageView1.image = UIImage(data: image!)
}
Now basically I have the following question:
Is the whole image downloaded in this process?
Or works the UIImageView like a "browser" in this case and doesn't download the whole picture but only "positions" the image from the URL into my UIImageView?
EDIT:
The reason I asked is, I am basically doing a quiz app and all I need in the view is an image from a URL for each question - so it's no difference if I do it asynchronous or synchronous because the user has to wait for the image anyways. I am more interested in how do I get the fastest result:
So I wanted to know if my code really downloads the picture as a whole from the URL or just "Positions" it into the UIImageView?
If in my code the picture is downloaded in its full resolution anyways, then you are right, I could download 10 pictures asynchronously when the player starts the quiz, so he hopefully doesn't have to wait after each answer as long as he would wait when I start downloading synchronously after each answer.
Edit 2:
Because my Question was tagged as similar to another some more explanation:
I already read about synchronous and asynchronous downloads, and I am aware of the downsides of synchronous loading.
I am more interested in a really basic question, and I get the feeling I had one basic thing really wrong:
My initial thought was that if I open a link in my browser, for example this one,
https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/68dd54ca-60cf-4ef7-898b-26d7cbe48ec7/10-dithering-opt.jpg
the browser doesn't download the whole picture. But I guess this isn't the case? The whole picture is downloaded?
Never use Data(contentsOf:) to display data from a remote URL. That initializer of Data is synchronous and is only meant to load local URLs into your app, not remote ones. Use URLSession.dataTask to download image data, just as you would with any other network request.
You can use below code to download an image from a remote URL asynchronously.
extension UIImage {
static func downloadFromRemoteURL(_ url: URL, completion: #escaping (UIImage?,Error?)->()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil, let image = UIImage(data: data) else {
DispatchQueue.main.async{
completion(nil,error)
}
return
}
DispatchQueue.main.async() {
completion(image,nil)
}
}.resume()
}
}
Display the image in a UIImageView:
UIImage.downloadFromRemoteURL(yourURL, completion: { image, error in
guard let image = image, error == nil else { print(error);return }
imageView1.image = image
})
You can do it this way. But in most cases it is better to download the image first by yourself and handle the displaying then (this is more or less what the OS is doing in the background). Also this method is more fail proof and allows you to respond to errors.
extension FileManager {
open func secureCopyItem(at srcURL: URL, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch (let error) {
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
return false
}
return true
}
}
func download() {
let storagePathUrl = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("image.jpg")
let imageUrl = "https://www.server.com/image.jpg"
let urlRequest = URLRequest(url: URL(string: imageUrl)!)
let task = URLSession.shared.downloadTask(with: urlRequest) { tempLocalUrl, response, error in
guard error == nil, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("error")
return
}
guard FileManager.default.secureCopyItem(at: tempLocalUrl!, to: storagePathUrl) else {
print("error")
return
}
}
task.resume()
}
I want to create URL from string in dictionary, but when I initialize it, the guard statement goes to else statement, because values["Image"] is nil and return the function. Here is a piece of my code
guard let imageURLInDatabase = URL(string: values["Image"]!) else { return }
let data = NSData(contentsOf: imageURLInDatabase)
I have create afew breakpoint in my code:
And here is my console log at this breakpoint:
What did I miss?
P.S
I tried to do it like:
let imageURLInDatabase = URL(string: values["Image"]!)
but I got error message unexpectedly found nil while unwrapping an optional value
Edit 1:
I tried to do it like
guard let imageString = values["Image"] else{return}
guard let imageURLInDatabase = URL(string: imageString) else{
return}
let data = NSData(contentsOf: imageURLInDatabase)
And set breakpoints:
So values["Image"] can't be nil
Edit 2:
This is what is in values["Image"]:
some : "https://firebasestorage.googleapis.com/v0/b/blackjack-ef580.appspot.com/o/profiles_images%2FdwMGR8sKMvYauf6uQDtyop18cwy1.jpg?alt=media&token=5df48794-d3fd-4e5c-a72c-9928a8a43c4e\""
The problem is the extra quotation mark at the end of the URL string. Try this:
let s = "https://firebasestorage.googleapis.com/v0/b/blackjack-ef580.appspot.com/o/profiles_images%2FdwMGR8sKMvYauf6uQDtyop18cwy1.jpg?alt=media&token=5df48794-d3fd-4e5c-a72c-9928a8a43c4e\""
let url = URL(string:s)
You'll get nil.
The problem isn't that values["Image"] is nil. The problem is that values["Image"] isn't a value URL string.
Make sure you properly encode spaces and other special characters in the URL.
And as a side note, do not use NSData, use Data.
According to your trace, the problem isn't that value["Image"] is nil. If that were the case, you would have crashed rather than called return. Your problem is that whatever the value of value["Image"], it is not a valid URL. Without seeing the value, it's difficult to know exactly what is wrong with it, but it is malformed in some way.
This is because the dictionary values does not contains the key Image. So values['Image'] will return nil, and force unwrapping a nil will crash the app.
Try using guard else statements to unwrap it
guard let imageValue = values["Image"] as? String else {
//imageValue is nil here, handle accordingly
return
}
guard let imageURLInDatabase = URL(string: imageValue) else {
//imageURLInDatabase is nil here, handle accordingly
return
}
let data = NSData(contentsOf: imageURLInDatabase)
Edit 1:
With your updated question, and the breakpoint image, values["Image"] is not null, imageURLInDatabase is nil due to which it goes to else case.
The reason could be that values["Image"] might not be returning a valid url, due to which URL(string: ..) fails.
Edit 2 :
Check the string url you just shared
"https://firebasestorage.googleapis.com/v0/b/blackjack-ef580.appspot.com/o/profiles_images%2FdwMGR8sKMvYauf6uQDtyop18cwy1.jpg?alt=media&token=5df48794-d3fd-4e5c-a72c-9928a8a43c4e\""
has an extra quote in the end!
I getting nil error. But I didnt understand why happaned. I can get selectedPhoto name with print. But I cant use in NSUrl. Could you help me pls?
my codes:
print(selectedPhoto)
if selectedPhoto != nil
{
let photoUrl = NSURL(string: "http://www.kerimcaglar.com/uploads/yemek-resimler/\(selectedPhoto)")
print("photo url: \(photoUrl)")
dataPhoto = NSData(contentsOfURL:photoUrl!)
yemekResim.image = UIImage(data: dataPhoto!)
}
else
{
print("Error")
}
From Apples documentation on NSData(contentsOfURL)
Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
If your app crashes because of this it will be rejected from the store.
Instead you should use NSURLSession. With the Async callback block as in my example.
Also it is not a good idea to force unwrap optionals ! as you will get run time errors instead use the if let syntax
See my example below.
if let photo = selectedPhoto{
let photoUrl = NSURL(string: "http://www.kerimcaglar.com/uploads/yemek-resimler/\(photo)")
if let url = photoUrl{
NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {(data, response, error) in
if let d = data{
dispatch_async(dispatch_get_main_queue(), {
if let image = UIImage(data: d) {
self.yemekResim.image = image
}
})
}
}).resume()
}
}
}
Replace this:
let photoUrl = NSURL(string: "http://www.kerimcaglar.com/uploads/yemek-resimler/\(selectedPhoto)")
with this:
let photoUrl = NSURL(string: "http://www.kerimcaglar.com/uploads/yemek-resimler/\(selectedPhoto!)")
(Notice the "!" after selectedPhoto)
I have an image url string:
var remoteImage: String = "http://server.com/wall-e.jpg"
I then construct a UIImage to download on a separate thread using Grand Central Dispatch with remoteImage as the NSURL string parameter:
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)!)!)
When it is finished and I return back to the main thread, I have it save internally:
UIImageJPEGRepresentation(getImage, 1.0).writeToFile(imagePath, atomically: true)
On Wi-fi and LTE it downloads fine, but when testing edge cases such as on an Edge network (no pun intended), I inconsistently get the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Now I thought I would be safe by making sure that it wasn't nil by adding in:
if getImage != nil { ... }
But it didn't seem to make a difference. It still gets the error and highlights the let getImage as written above. What am I doing wrong here? Should I be checking nil in a different manner or method?
I would recommend you to use AsyncRequest to fetch and download the image and saved it locally.
As you didn't posted any of code of your problem.
So i am posting a sample working for me.
Sample for downloading and saving image locally
var url = NSURL(string : "http://freedwallpaper.com/wp-content/uploads/2014/09/Tattoo-Girl.jpg")
let urlrequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlrequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response ,data , error in
if error != nil
{
println("error occurs")
}
else
{
let image = UIImage(data: data)
/* Storing image locally */
var documentsDirectory:String?
var paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
println(paths)
if paths.count > 0{
documentsDirectory = paths[0] as? String
var savePath = documentsDirectory! + "/bach.jpg"
NSFileManager.defaultManager().createFileAtPath(savePath, contents: data, attributes: nil)
self.bach.image = UIImage(named: savePath)
}
}
})
}
The error, does, in fact lie on the line:
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)!)!)
The reason is that it's not the UIImage that is initially returning nil, it is probably NSData returning nil. You could check if NSData is returning nil, and then create the UIImage object instead.
EDIT: What the particular line of code is doing is it is assuming that NSData is always returning a non-nil value, which may not be true when you are not connected. When you're not connected, it gets a nil value, which you are trying to say will never be a nil value using the exclamation mark (!).
I suggest you read further on how Swift works. For this particular example, take a look at what the exclamation marks actually mean in Swift: What does an exclamation mark mean in the Swift language?
I'm trying to check if a url image exist using a if statement. However when trying to test it by wrong image url it keep returning:
fatal error: unexpectedly found nil while unwrapping an Optional value.
code:
var httpUrl = subJson["image_url"].stringValue
let url = NSURL(string: httpUrl)
let data = NSData(contentsOfURL: url!)
if UIImage(data: data!) != nil {
}
You can do it this way:
var httpUrl = subJson["image_url"].stringValue
if let data = NSData(contentsOfURL: url) {
//assign your image here
}
Other answers are telling you to unwrap the optional, but really the issue is that you are force-unwrapping the optional with !, despite it being nil.
When you write something! what you are saying is “something might be nil, but if it is, I want my program to crash”.
The reason a call might return nil is if the result is not valid – for example, if you try to fetch a key from a dictionary that isn’t there, or that a URL doesn’t point to a valid downloadable image.
You can combine the check for nil with an if statement that unwraps the optional and returns a non-optional value so long as the value wasn’t nil. You can also chain them together, so if you need to unwrap a value, then pass it into a call that also returns an optional, you can do it all in one if statement:
if let httpUrl = subJson["image_url"].string,
url = NSURL(string: httpUrl),
data = NSData(contentsOfURL: url),
image = UIImage(data: data)
{
// use image value
}
else {
// log some error
}
Note, on the first line, the call is to .string rather than .stringValue – .string also returns an optional with nil if the value isn’t present.
Of course, combining all these unwraps together means you can’t tell which one failed, so you may want to break them out into separate statements instead.
This code is tested and successful: if image url not exists no crash.
let url: NSURL = NSURL(string: "http://www.example.com/images/image.png")!
do {
let imgData = try NSData(contentsOfURL: url, options: NSDataReadingOptions())
ImageView?.image = UIImage(data: imgData)
} catch {
print(error)
}
You have to unwrap the optional like this:
if let image = data {
// use image here
} else {
// the data is nil
}