Search code examples
swiftuiviewuikituianimation

Animate UIImageView height and width changes


My goal is to animate UIImageView transition from top left corner to the center of the view. It also has to change it width and height to screen width and change corner radius to zero.

How to modify following code so UIImageView changes its width and height:

    private func imageAnimates() {
        UIView.animate(
            withDuration: 0.5,
            delay: 0.0,
            options: .curveLinear
        ) {
            self.userImage.layer.cornerRadius = 0
            self.userImage.center = CGPoint(x: self.view.center.x, y: self.view.center.y)
        } completion: { finished in
            print("Image animated")
        }
    }

UPD: Following an answer by vaibhav sharma, I updated my code to:

        UIView.animate(
            withDuration: 0.5,
            delay: 0.0,
            options: .curveLinear
        ) {
            self.userImage.alpha = 1.0
            self.userImage.layer.cornerRadius = 0
            self.userImage.frame = CGRect(
                 x: (screenWidth - finalWidth) / 2,
                 y: (self.view.frame.size.height - finalHeight) / 2,
                 width: finalWidth,
                 height: finalHeight
             )
        } completion: { finished in
            print("Image animated")
        } 

But the result I'm getting is not the one I expected. Here's a screen recording: https://sendvid.com/i1fxbf0y. It seems like the image now goes from tiny picture in the top left corner to its original frame and position. It's not even moving to the center.


Solution

  • You need to start out *simple -- work on only this image view animation until you get it working right. Then add it to your complex controller.

    You cannot "mix-and-match" constraints and direct frame setting. UIKit will automatically reset the frame to match the constraints.

    Instead, use only frame setting for your userImage view ... you can use auto-layout constraints for everything else.

    So, take a look at this - tap anywhere to show-and-grow or shrink-and-hide the image view:

    class ProfileViewController: UIViewController {
        
        private lazy var userImage: UIImageView = {
            
            //let imageView = UIImageView(image: UIImage(named: me.login))
            
            // I don't have your "me.login" so let's use SF Symbol
            let imageView = UIImageView()
            if let img = UIImage(systemName: "person.fill") {
                imageView.image = img
            }
            
            imageView.layer.cornerRadius = 45
            imageView.clipsToBounds = true
    
            // we're going to set the frame directly, so
            //  use true (it's the default so we don't really need to set it)
            imageView.translatesAutoresizingMaskIntoConstraints = true
            
            imageView.alpha = 0.0
            
            // while we're designing this,
            //  use a backgroundColor so we can see the framing
            imageView.backgroundColor = .red
            
            return imageView
        }()
        
        // these will be update in viewDidLayoutSubviews()
        //  when we know the view frame and safeAreaInsets
        private var userImageStartFrame: CGRect = .zero
        private var userImageFinalFrame: CGRect = .zero
        private var screenWidth: CGFloat = 0.0
    
        // MARK: - Lifecycle
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // we'll adjust the origin in viewDidLayoutSubviews()
            userImageStartFrame = CGRect(x: 0.0, y: 0.0, width: 90.0, height: 90.0)
            
            view.addSubview(userImage)
    
            // do NOT set constraints on userImage !!!
    
        }
    
        override func viewDidLayoutSubviews() {
            
            super.viewDidLayoutSubviews()
    
            // viewDidLayoutSubviews() will be called many times, so
            //  only set the userImage view frames when the view width changes
            if screenWidth != view.frame.width {
                
                screenWidth = view.frame.width
                
                // get the safe area rect
                let r: CGRect = self.view.bounds.inset(by:self.view.safeAreaInsets)
    
                userImageStartFrame.origin = r.origin
    
                // let's make the large-center frame 90% of the safe area width
                let finalWidth: CGFloat = r.width * 0.9
     
                userImageFinalFrame = CGRect(
                    x: r.origin.x + ((r.width - finalWidth) * 0.5),
                    y: r.origin.y + ((r.height - finalWidth) * 0.5),
                    width: finalWidth, height: finalWidth
                )
                
                userImage.frame = userImageStartFrame
    
            }
    
        }
        
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            
            UIView.animate(
                withDuration: 0.5,
                delay: 0.0,
                options: .curveLinear
            ) {
                //self.backgroundBlur.alpha = 0.5
                //self.blurCloseButton.alpha = 1.0
    
                if self.userImage.alpha == 0.0 {
                    // userImage is currently transparent (alpha: 0.0)
                    //  so we want to show it
                    self.userImage.alpha = 1.0
                    self.userImage.layer.cornerRadius = 0
                    self.userImage.frame = self.userImageFinalFrame
                } else {
                    self.userImage.alpha = 0.0
                    self.userImage.layer.cornerRadius = 45
                    self.userImage.frame = self.userImageStartFrame
                }
                
            } completion: { finished in
                print("Background blur appeared")
            }
    
        }
        
    }