Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 12.2, and Swift 5.3
- You will be expected to be aware of how to make a Single View Application in Swift.
- Subclass UICollectionViewCell
- This article builds on my Fade the First and Last Elements in a UICollectionView article
UICollectionView: An object that manages an ordered collection of data items and presents them using customizable layouts
If you want to create a UICollectionView
where you can pinch to zoom the images, then you're in the right place!
This project built upon an article where I managed to get UICollectionViewCell
instances to fade in a UICollectionView
if they are either the first or last element in the view. This particular article, therefore, focuses on the gestures that go into the UICollectionViewCell
instances.
I've even got an article about adding pinch gestures to a UIImageView
We will need to conform to SubclassedCollectionViewCell
to receive messages from the gesture recognizer, which is set up as follows:
// set up the pinch gesture
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(self.pinch(sender:)))
// this class is the delegate
pinch.delegate = self
// add the gesture to hotelImageView
self.hotelImageView.addGestureRecognizer(pinch)
Which will call a delegate function @objc func pinch(sender:UIPinchGestureRecognizer)
that must be visible to Objective-C and therefore has the keyword @objc
.
More than that, in our cell we will store two properties (initialCenter and isZooming)
var initialCenter: CGPoint?
var isZooming = false
If we are beginning our first pinch we will run the following code:
// calculate the image scale
let currentScale = self.hotelImageView.frame.size.width / self.hotelImageView.bounds.size.width
// calculate the image scale after the pinch
let newScale = currentScale * sender.scale
// if we are really zooming, set the boolean
if newScale > 1 {
self.isZooming = true
// bring the cell to the front of the superview
self.superview?.bringSubviewToFront(self)
}
Yet if this is a continuous gesture we will run:
guard let view = sender.view else {return}
// set the center of the gesture
let pinchCenter = CGPoint(x: sender.location(in: view).x - view.bounds.midX,
y: sender.location(in: view).y - view.bounds.midY)
// set the transformation
let transform = view.transform.translatedBy(x: pinchCenter.x, y: pinchCenter.y)
.scaledBy(x: sender.scale, y: sender.scale)
.translatedBy(x: -pinchCenter.x, y: -pinchCenter.y)
// set the current scale of the image
let currentScale = self.hotelImageView.frame.size.width / self.hotelImageView.bounds.size.width
// set the scale once the gesture is complete
var newScale = currentScale * sender.scale
// if the scale is to reduce the size of the cell
if newScale < 1 {
// set the cell back to the original point
newScale = 1
let transform = CGAffineTransform(scaleX: newScale, y: newScale)
self.hotelImageView.transform = transform
// set the scale back to one
sender.scale = 1
} else {
// make the transformation
view.transform = transform
// set the scale back to one
sender.scale = 1
}
If we are finishing the gesture, the following:
// animate the pop back to the original place for the UIImageView
UIView.animate(withDuration: 0.3, animations: {
// set the transform
self.hotelImageView.transform = CGAffineTransform.identity
}, completion: { _ in
// we are no longer zooming
self.isZooming = false
})
The full code is in the repo
You should know if you follow this logic for a UITableViewCell
you don't have to conform to UIGestureRecognizerDelegate
. In this case, you do follow the same logic, but don't worry too much as you can do it!
If you've any questions, comments or suggestions please hit me up on Twitter