Search code examples
swiftuiimageview

Image set in storyboard is not equal with UIImage(named: "sunrise") object declared in code


I follow a Swift tutorial to implement a simple fade out/in animation on UIImageView. In the tutorial, after running the project, press a button will trigger sunRiseAndSet(sender: AnyObject) function, then the image will change from sunrise to sunset after an animation fade out first then fade in.

But in my side, the image is not changed for the first touch, when clicking again then it changed. I checked and the imageView is connected in storyboard and assign as image named "sunrise", and the code is same with the guide. And found this line if (self.imageView.image == UIImage(named: "sunrise")) is not same for the first touch on the button. Could you give me some clues what is the reason?

/ / / Code is here:

import UIKit

// MARK: - ViewController: UIViewController

class ViewController: UIViewController {

    // MARK: Outlets

    @IBOutlet weak var imageView: UIImageView!

    // MARK: Actions

    @IBAction func sunRiseAndSet(sender: AnyObject) {
        // Fade out
        imageView.fadeOut(duration: 1.0, delay: 0.0,
        completion: {
                (finished: Bool) -> Void in

                if self.imageView.image != nil {
                    print("image exist")
                }
                //Once the label is completely invisible, set the text and fade it back in
                if (self.imageView.image == UIImage(named: "sunrise")) {
                    self.imageView.image = UIImage(named:"sunset")!
                    print("sunset")
                } else {
                    self.imageView.image = UIImage(named:"sunrise")!
                    print("sunrise")
                }

                // Fade in
                self.imageView.fadeIn(duration: 1.0, delay: 0.0, completion: nil)
        })
    }
}
import Foundation
import UIKit

// MARK: - UIView (Extensions)

extension UIView {

    func fadeOut(duration: TimeInterval, delay: TimeInterval, completion: ((Bool) -> Void)?) {
        UIView.animate(withDuration: duration, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations: {
        self.alpha = 0.0
        }, completion: completion)
    }

    func fadeIn(duration: TimeInterval, delay: TimeInterval, completion: ((Bool) -> Void)?) {
        UIView.animate(withDuration: duration, delay: delay, options:UIViewAnimationOptions.curveEaseIn, animations: {
        self.alpha = 1.0
        }, completion: completion)
    }
}

/ / / Update try Wattholm's answer, 1) set imageView.image in code in viewDidLoad could get the expected behavior. 2) and I try 2nd approach too, by adding that one more print in my code as below

                if self.imageView.image != nil {
                    print("image exist")
                    print("imageView.image: ", self.imageView.image)
                }

And here is the output in console, but I didn't get the anonymous name for the first time button clicking, btw Im using real iPhone but not a simulator.

image exist
imageView.image:  Optional(<UIImage:0x281a09440 named(main: sunrise) {2560, 1440}>)
sunrise
image exist
imageView.image:  Optional(<UIImage:0x281a08fc0 named(main: sunrise) {2560, 1440}>)
sunset
image exist
imageView.image:  Optional(<UIImage:0x281a054d0 named(main: sunset) {2560, 1600}>)
sunrise
image exist
imageView.image:  Optional(<UIImage:0x281a08fc0 named(main: sunrise) {2560, 1440}>)
sunset
image exist
imageView.image:  Optional(<UIImage:0x281a09440 named(main: sunset) {2560, 1600}>)
sunrise
image exist
imageView.image:  Optional(<UIImage:0x281a054d0 named(main: sunrise) {2560, 1440}>)
sunset

Solution

  • You are correct. The equality operator that compares two different image instances (one set in storyboard and one instantiated in code) will not see them as being equal.

    If you want the code to work as intended, either (1) set the image explicitly in viewDidLoad:

    override func viewDidLoad() {
        super.viewDidLoad()
        imageView.image = UIImage(named: "sunrise")
    }
    

    ... or (2) compare them by getting their data representations, which should be equal, even if one was set in storyboard:

    if (self.imageView.image?.pngData() == UIImage(named: "sunrise")?.pngData()) { // your code}
    

    I must advise against using the 1st method however, since apparently the official docs (https://developer.apple.com/documentation/uikit/uiimage) advise against using == to compare UIImages directly. The recommended method to compare UIImages is with isEqual(_:). I tried this as well but the UIImage set in storyboard is still not found to be equal to the one set in code.

    With the original code, I did a little further testing by printing out to the console the output of:

    print(self.imageView.image)
    

    on every button tap. The results:

    image exist
    Optional(<UIImage:0x600000898510 anonymous {3220, 3155}>)
    sunrise
    image exist
    Optional(<UIImage:0x6000008b0fc0 named(sunrise) {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x600000898fc0 named(sunset) {500, 500}>)
    sunrise
    image exist
    Optional(<UIImage:0x6000008b9830 named(sunrise) {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x6000008b4f30 named(sunset) {500, 500}>)
    sunrise
    image exist
    Optional(<UIImage:0x600000898fc0 named(sunrise) {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x6000008bc900 named(sunset) {500, 500}>)
    sunrise
    

    Notice that only the first time does it show a UIImage with part of the string description 'anonymous'. All suceeding calls to the method just alternately print either "named(sunrise)" or "named(sunset)". And also notice that the hex values are always changing, indicating that each time UIImage() is called it initializes a new and different instance.

    You can check this by setting the 2 UIImages to constant properties in the VC and testing again:

    let sunriseImage = UIImage(named: "sunrise")
    let sunsetImage = UIImage(named: "sunset")
    

    In this case, substituting the constants wherever you use UIImage(named..) will result in unchanging hex values for each of the 2 UIImages, when printing to the console.

    Here's the full code I'm using with the pngData() method (recommended):

    import UIKit
    
    // MARK: - ViewController: UIViewController
    
    class ViewController: UIViewController {
    
        let sunriseImage = UIImage(named: "sunrise")
        let sunsetImage = UIImage(named: "sunset")
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        @IBOutlet weak var imageView: UIImageView!
    
        // MARK: Actions
    
        @IBAction func sunRiseAndSet(sender: AnyObject) {
            // Fade out
            imageView.fadeOut(duration: 1.0, delay: 0.0, completion: {
                [weak self] (finished: Bool) -> Void in
    
                guard let self = self else {return}
                if self.imageView.image != nil {
                    print("image exist")
                }
                //Once the label is completely invisible, set the text and fade it back in
                print(self.imageView.image as Any)
    
                //if self.imageView.image == UIImage(named: "sunrise") {
                if self.imageView.image!.pngData() == self.sunriseImage!.pngData() {
                    self.imageView.image = self.sunsetImage
                    print("sunset")
                } else {
                    self.imageView.image = self.sunriseImage
                    print("sunrise")
                }
    
                // Fade in
                self.imageView.fadeIn(duration: 1.0, delay: 0.0, completion: nil)
            })
        }
    
    }
    
    // MARK: - UIView (Extensions)
    
    extension UIView {
    
        func fadeOut(duration: TimeInterval, delay: TimeInterval, completion: ((Bool) -> Void)?) {
            UIView.animate(withDuration: duration, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations: {
            self.alpha = 0.0
            }, completion: completion)
        }
    
        func fadeIn(duration: TimeInterval, delay: TimeInterval, completion: ((Bool) -> Void)?) {
            UIView.animate(withDuration: duration, delay: delay, options:UIView.AnimationOptions.curveEaseIn, animations: {
            self.alpha = 1.0
            }, completion: completion)
        }
    
    }
    

    For this solution, the hex addresses(?) do not change for the sunrise and sunset images initialized in code:

    image exist
    Optional(<UIImage:0x6000020d4870 anonymous {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x6000020f0f30 named(sunset) {500, 500}>)
    sunrise
    image exist
    Optional(<UIImage:0x6000020f0d80 named(sunrise) {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x6000020f0f30 named(sunset) {500, 500}>)
    sunrise
    image exist
    Optional(<UIImage:0x6000020f0d80 named(sunrise) {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x6000020f0f30 named(sunset) {500, 500}>)
    sunrise
    image exist
    Optional(<UIImage:0x6000020f0d80 named(sunrise) {3220, 3155}>)
    sunset
    image exist
    Optional(<UIImage:0x6000020f0f30 named(sunset) {500, 500}>)
    sunrise
    

    And output on an iPhone running iOS 12.X.X (no more names, but the hex values are consistent after the first one):

    image exist
    Optional(<UIImage: 0x283b155e0>, {3220, 3155})
    sunset
    image exist
    Optional(<UIImage: 0x283b11420>, {500, 500})
    sunrise
    image exist
    Optional(<UIImage: 0x283b10f50>, {3220, 3155})
    sunset
    image exist
    Optional(<UIImage: 0x283b11420>, {500, 500})
    sunrise
    image exist
    Optional(<UIImage: 0x283b10f50>, {3220, 3155})
    sunset
    image exist
    Optional(<UIImage: 0x283b11420>, {500, 500})
    sunrise
    

    Hope this has helped!