Animate image crop from right to left

61 Views Asked by At

I want to animate a change in the width of an ImageView in Swift, in a way it will be appeared as if the image is being cropped from right to left. I mean that I want the image to always stick to the left edge of its superView and only its right edge will be changed during the animation, without changing the image scale.

I've managed to animate the change in the width of the image while preserving its scale, but the image is being cropped from both sides towards its centerX and not from right to left only.

imageView.contentMode = .scaleAspectFill
imageView.clipToBounds = true

let animation = CABasicAnimation(keyPath: "bounds.size.width")
animation.fromValue = 250
animation.toValue = 50
imageView.layer.add(animation, forKey: nil)

According to this SO thread one should change the anchorPoint of imageView.layer to have x=0, but doing so moves the left edge of the image to the center of the view, and also moves the image when animating so that when it is in its smaller width, the image's centerX point will be visible at the center of the screen.

1

There are 1 best solutions below

0
Matic Oblak On

I would suggest you to use additional view (panel) that defines the size and place your image view on this view. Panel may then clip the image view to create a desired effect.

I created a short example all in code just to demonstrate the approach:

class ViewController: UIViewController {

    private var isImageExtended: Bool = true
    private var imagePanel: UIView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let panel = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 100.0))
        panel.backgroundColor = .lightGray
        
        let imageView = UIImageView(image: UIImage(named: "test_image"))
        imageView.frame = CGRect(x: 0.0, y: 0.0, width: panel.bounds.width, height: panel.bounds.height)
        imageView.contentMode = .scaleAspectFill
        imageView.translatesAutoresizingMaskIntoConstraints = false
        panel.addSubview(imageView)
        imageView.backgroundColor = .red
        
        view.addSubview(panel)
        panel.center = view.center
        panel.clipsToBounds = true
        
        imagePanel = panel
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
    }
    
    @objc private func onTap() {
        guard let imagePanel else { return }
        isImageExtended = !isImageExtended
        let panelWidth: CGFloat = isImageExtended ? 200 : 50
        UIView.animate(withDuration: 0.5) {
            imagePanel.frame.size.width = panelWidth
        }
    }

}

I could easily achieve the same result using storyboard and constraints with significantly less code:

class ViewController: UIViewController {

    @IBOutlet private var panelWidthConstrain: NSLayoutConstraint?
    
    private var isImageExtended: Bool = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
    }
    
    @objc private func onTap() {
        isImageExtended = !isImageExtended
        let panelWidth: CGFloat = isImageExtended ? 200 : 50
        UIView.animate(withDuration: 0.5) {
            self.panelWidthConstrain?.constant = panelWidth
            self.view.layoutIfNeeded()
        }
    }

}

I hope the code speaks enough for itself.