Updated 26 October 2021
We’ve seen a feature in our iOS devices home screen on long press to remove an app and drag and drop any of the app. So, I have written this blog to show how we can create this feature in our app while development.
I have used UICollectionView DataSources and Delegates to implement this functionality with a shaking animation.
Here is my view that looks like with few sets of images in a UICollectionView:
Add UILongPressGestureRecognizer on UICollectionView in viewDidLoad:
1 2 3 |
//adding longpress gesture over UICollectionView longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longTap(_:))) imgcollection.addGestureRecognizer(longPressGesture) |
UILongPressGestureRecognizer action func definition as given below:
Different states of Long press is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@objc func longTap(_ gesture: UIGestureRecognizer){ switch(gesture.state) { case .began: guard let selectedIndexPath = imgcollection.indexPathForItem(at: gesture.location(in: imgcollection)) else { return } imgcollection.beginInteractiveMovementForItem(at: selectedIndexPath) case .changed: imgcollection.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!)) case .ended: imgcollection.endInteractiveMovement() doneBtn.isHidden = false longPressedEnabled = true self.imgcollection.reloadData() default: imgcollection.cancelInteractiveMovement() } } |
imgArr is an array of [String] type. The array consists of a set of sample images.
1 2 3 |
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return imgArr.count } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SmallImgCell.identifier, for: indexPath) as! SmallImgCell cell.backgroundColor = UIColor.clear cell.imgView.image = UIImage(named: "\(imgArr[indexPath.row])") cell.removeBtn.addTarget(self, action: #selector(removeBtnClick(_:)), for: .touchUpInside) if longPressedEnabled { cell.startAnimate() }else{ cell.stopAnimate() } return cell } |
1 2 3 |
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: UIScreen.main.bounds.size.width/4 - 20, height: UIScreen.main.bounds.size.width/4 - 20) } |
UICollectionViewCell consisting of an image view and a remove button.
The UIImageView is for displaying an image over the UICollectionView and UIButton for showing the cross button over the cell for deleting an item from collection view.
1 2 3 4 |
@IBOutlet weak var imgView: UIImageView! @IBOutlet weak var removeBtn: UIButton! var isAnimate: Bool! = true |
startAnimate snippet for animation over image in collection view:
The func will start toggling of the images.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//Animation of image func startAnimate() { let shakeAnimation = CABasicAnimation(keyPath: "transform.rotation") shakeAnimation.duration = 0.05 shakeAnimation.repeatCount = 4 shakeAnimation.autoreverses = true shakeAnimation.duration = 0.2 shakeAnimation.repeatCount = 99999 let startAngle: Float = (-2) * 3.14159/180 let stopAngle = -startAngle shakeAnimation.fromValue = NSNumber(value: startAngle as Float) shakeAnimation.toValue = NSNumber(value: 3 * stopAngle as Float) shakeAnimation.autoreverses = true shakeAnimation.timeOffset = 290 * drand48() let layer: CALayer = self.layer layer.add(shakeAnimation, forKey:"animate") removeBtn.isHidden = false isAnimate = true } |
stopAnimate snippet for animation over image in collection view:
The func will stop toggling of images.
1 2 3 4 5 6 |
func stopAnimate() { let layer: CALayer = self.layer layer.removeAnimation(forKey: "animate") self.removeBtn.isHidden = true isAnimate = false } |
Remove Button and done button is shown like in iPhone X :
On clicking Remove button action remove the item at that index from UICollectionView and reload it.
On Done button action hide the done button, stop the animation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@IBAction func removeBtnClick(_ sender: UIButton) { let hitPoint = sender.convert(CGPoint.zero, to: self.imgcollection) let hitIndex = self.imgcollection.indexPathForItem(at: hitPoint) //remove the image and refresh the collection view self.imgArr.remove(at: (hitIndex?.row)!) self.imgcollection.reloadData() } @IBAction func doneBtnClick(_ sender: UIButton) { //disable the shake and hide done button doneBtn.isHidden = true longPressedEnabled = false self.imgcollection.reloadData() } |
For changing the cell locations from one place to another or dragging the cells write following snippets:
–> Return true for enabling the drag feature.
–>
–> Swap item from source to destination
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool { return true } func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { print("Start index :- \(sourceIndexPath.item)") print("End index :- \(destinationIndexPath.item)") let tmp = imgArr[sourceIndexPath.item] imgArr[sourceIndexPath.item] = imgArr[destinationIndexPath.item] imgArr[destinationIndexPath.item] = tmp imgcollection.reloadData() } |
I hope from this, it will make you more comfortable using a LongPress and drag and drop feature of UICollectionView. Thanks for tuning in once again!
If you have more details or questions, you can reply to the received confirmation email.
Back to Home
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
print(“Start index :- \(sourceIndexPath.item)”)
print(“End index :- \(destinationIndexPath.item)”)
let tmp = imgArr[sourceIndexPath.item]
if sourceIndexPath.item <= destinationIndexPath.item {
var index = sourceIndexPath.item
while (index destinationIndexPath.item) {
imgArr[index] = imgArr[index – 1]
index -= 1
}
imgArr[destinationIndexPath.item] = tmp
}
collectionView.reloadData()
}