-3

I'd like to use 'if let' here but I can't get it to work. Can someone help me?

guard animal?.img != nil else {
    print ("image is nil")
    return
}
    
let animalImage = UIImage(data: animal!.img!) as UIImage?
saveImageView.image = animalImage
pkamb
  • 33,281
  • 23
  • 160
  • 191
Linus Bicker
  • 129
  • 1
  • 8
  • 1
    Why would you like to use "if let"? You don't need it here. You code is clean/pretty enough as it is. One thing I would change though is to use `as? UIImage` rather than `as UIImage?`. – Sweeper Nov 03 '20 at 04:41
  • 1
    Does this answer your question? [Swift: guard let vs if let](https://stackoverflow.com/questions/32256834/swift-guard-let-vs-if-let) – えるまる Nov 03 '20 at 05:27

6 Answers6

3

I think you mean "guard let". BTW, you almost always want to avoid force unwrapping.

Also since UIImageView.image is optional you don't need to check the returned value from UIImage constructor

 guard let data = animal?.img else {
    print ("image data is nil")
    return
}

saveImageView.image = UIImage(data: data)
Dale
  • 3,193
  • 24
  • 29
1

This should do it, you assign your image data to first variable, then use it to assign to the second one:

if let img = animal?.img, let animalImage = UIImage(data: img) {
  //do something
}

Probably don't need as? Data and as? UIImage, it depends on your model.

Tj3n
  • 9,837
  • 2
  • 24
  • 35
1

There are several things you can do with an optional value:

var optionalText:String?

...

var text4:String? = optionalText // assign it to another optional variable

var text5 = optionalText // the same, the compiler will infer that text5 is also an optional string

optionalText?.append(" hello") // will only call `append(_:)` on `text` if it's not nil, otherwise this will be ignored

let text6:String = optionalText ?? "It was nil!" // use the coalescing operator

let text7:String = optionalText! // force-unwrap it into a String; if it's nil, your app will crash

// you CANNOT, however, treat it as a non-optional:
let newText = "Hello " + optionalText // this won't compile

// you can unwrap it:
if let text8 = optionalText {
    // this code block will ONLY execute if textField.text was not nil
    let newString = "Hello "+ text8 // this constant will only work in this block
    // in that case, text8 will be of type String (non-optional)
    // so it shouldn't be treated as an optional
}
let newString = "Hello "+ text8 // this won't compile

// unwrap it with `guard`:
guard let text8 = optionalText else {
    // this code block will ONLY execute if textField.text WAS nil
    // and it must return
    return
}
let newString = "Hello "+ text8 // this is okay, text8 lives on in case of `guard` 

These are the differences between guard let and if let:

  • if let nonOptional = optional {} will assign a value to a non-optional constant by unwrapping an optional value and execute the code block, but only if the optional isn't nil. The non-optional constant will only live inside the if let { } block. Use this if you want to handle both cases (nil or otherwise) and then move on with your code.
  • guard let nonOptional = optional else { } will do the same assignment if possible, but code flow will be different afterwards: it will execute the else block in case the optional value is nil, and that else block will have to quit the scope (return, continue, or break); it must not fall through, i.e. execution must not continue right after the block (the compiler will make sure of that). However, your nonOptional constant will live on after this statement. Use this if your code largely depends on the optional value not being a nil: quit early if the condition fails, and otherwise hold on to the non-optional value and use if for the rest of your enclosing scope.

Btw., you can also use var instead of let if it makes sense, in both cases.

The main purpose of guard is to avoid the "pyramid of doom" type of nested checks:

// pyramid of doom
func doomed() {
    if condition1 {
        if condition2 {
            if condition3 {

                // success scenario
                print("all good")
                // ...

            } else {
                print("failed condition 3")
                return
            }
        } else {
            print("failed condition 2")
            return
        }
    } else {
        print("failed condition 1")
        return
    }
}

// avoiding pyramid of doom
func nonDoomed() {
    guard condition1 else {
        print("failed condition 1")
        return
    }

    guard condition2 else {
        print("failed condition 2")
        return
    }

    guard condition3 else {
        print("failed condition 3")
        return
    }

    // success scenario
    print("all good")
    // ...
}

Without guard, your success scenario is hidden in the middle of nested if statements related to error conditions, making your code difficult to read or edit.

With guard, each error condition is handled separately, and after you get them out of the way, you can go on with the success scenario. With guard let, you can also ensure that all the necessary constants and variables are available for the rest of the scope.

What you seem to need is one of two things:

Optionally unwrap and use your optional:

if let realImage = animal?.img {
    let animalImage = UIImage(data: animal!.img!) as UIImage?
    saveImageView.image = animalImage
}
// otherwise, saveImageView.image will not be assigned a new value

Simply pass an optional value

saveImageView.image = animal?.img

This should work because the left-hand side and the right-hand side are both UIImage? optionals.

pua666
  • 326
  • 1
  • 7
  • Thank you this is very helpful. The guard isn't quite right for me because I still want the code to resume if the image is nil. As it is, I just put this block of code at the end of my viewDidLoad and managed to get it to do what I want, but I got to work as more of a workaround. – Linus Bicker Nov 07 '20 at 03:07
  • @LinusBicker okay, but then it's really not clear what your goal is. What do you want to do if `animal?.img` is `nil`, and what if it's not? You can use `if animal?.img == nil` in a simple `if` block, or you can use `if let data = animal?.img` for an `if` block that also receives `data`, the non-optional constant that you can use in its scope. Both can resume, unlike a `guard` block that must exit the scope when triggered. – pua666 Nov 07 '20 at 23:40
  • Sorry, I am really new so I may be confused and/or not describing things properly.I have a view that contains the properties associated with Animal class.One property is an image.on that page, the user is able to insert an image into a UIImage that is on the page. so if that UIImage is not nil, I want to set the data in the UIImage to animal.img . The code I have up top works, but only works because I stuffed the code at the bottom of the ViewDidLoad where nothing else follows it. I thought an IF LET would let me do the same thing but contain the relevant code to an enclosure – Linus Bicker Nov 08 '20 at 01:09
  • and what if it's `nil`? – pua666 Nov 08 '20 at 02:16
  • if there was no image added to the UIImage in the view, then the animal.img property should just stay nil. – Linus Bicker Nov 08 '20 at 02:36
  • Can you edit your question so that it contains this information? People will read this years from now and they should understand what it's all about. By the way, it's still not clear for me if `saveImageView.image = animalImage` would still need to run in the `nil` case. Can you list the code you want to run if `animal?.img != nil` is true, and the code to run if it isn't? If you do the edit, we'll be able to provide a correct answer that will also help other users later. Thanks! – pua666 Nov 08 '20 at 07:47
0

You can do it like below...

if let imgName = animal?.img {
     saveImageView.image = UIImage(data: imgName)
}
Mahendra
  • 8,448
  • 3
  • 33
  • 56
0

I would extend Data and create an image property to return an optional image from your data object:

extension Data {
    var image: UIImage? { UIImage(data: self) }
}

Usage:

saveImageView.image = animal?.img?.image 

This would allow you to provide a default image as well in case of nil using the nil coalescing operator:

saveImageView.image = animal?.img?.image ?? UIImage(named: "defaultImage")
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

guard let and if let are effectively the same thing. In fact, if you are guarding the first statement to check if it's nil anyway, you can combine them into a single check and do something like this:

guard let animalImageData = animal?.img,
    let animalImage = UIImage(data: animalImageData) else { return }
saveImageView.image = animalImage

or alternatively,

if let animalImageData = animal?.img, let animalImage = UIImage(data: animalImageData) {
    saveImageView.image = animalImage
}

When you force-unwrap animal!.img!, it is unsafe practice and should generally be avoided since it could lead to fatal exceptions.

Jeriel Ng
  • 191
  • 1
  • 4
  • 14