Skip to content

Instantly share code, notes, and snippets.

@shawn-frank
Created March 18, 2022 10:30
Show Gist options
  • Save shawn-frank/f3abb323beecda28522daff3e21360f7 to your computer and use it in GitHub Desktop.
Save shawn-frank/f3abb323beecda28522daff3e21360f7 to your computer and use it in GitHub Desktop.
A small example of using a UIPanGestureRecognizer to swipe up and bring a UICollectionView interaction. This was created as a demo to this StackOverflow question: https://stackoverflow.com/q/71304657/1619193
//
// SwipeCollectionView.swift
// TestApp
//
// Created by Shawn Frank on 01/03/2022.
//
import UIKit
class SwipeCollectionView: UIViewController
{
private var collectionView: UICollectionView!
private let imageView = UIImageView()
private let overlayView = UIView()
private let colors: [UIColor] = [.red,
.systemBlue,
.orange,
.systemTeal,
.purple,
.systemYellow]
private var cvBottomAnchor: NSLayoutConstraint?
private var isCarouselShowing = false
private var previousSwipeLocation: CGPoint?
private let cellWidth: CGFloat = 100
private let collectionViewHeight: CGFloat = 185
private let reuseIdentifier = "cell"
lazy var button: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.red, for: .normal)
button.setTitle("Push", for: .normal)
button.backgroundColor = .blue
button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill
button.contentMode = .scaleAspectFill
button.contentEdgeInsets = UIEdgeInsets(top: .leastNormalMagnitude,
left: .leastNormalMagnitude,
bottom: .leastNormalMagnitude,
right: .leastNormalMagnitude)
return button
}()
override func viewDidLoad()
{
super.viewDidLoad()
configureNavigationBar()
configureOverlayView()
configureCollectionView()
configureButton()
}
private func configureButton()
{
view.addSubview(button)
view.addConstraints([
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
private func configureNavigationBar()
{
title = "Swipe CV"
let appearance = UINavigationBarAppearance()
// Color of the nav bar background
appearance.backgroundColor = .white // primary black for you
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
}
private func configureOverlayView()
{
imageView.image = UIImage(named: "dog")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
overlayView.backgroundColor = .black
overlayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
view.addSubview(overlayView)
// Auto layout pinning the image view and overlay view
// to the main container view
view.addConstraints([
imageView.leadingAnchor
.constraint(equalTo: view.leadingAnchor),
imageView.topAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
imageView.trailingAnchor
.constraint(equalTo: view.trailingAnchor),
imageView.bottomAnchor
.constraint(equalTo: view.bottomAnchor),
overlayView.leadingAnchor
.constraint(equalTo: view.leadingAnchor),
overlayView.topAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
overlayView.trailingAnchor
.constraint(equalTo: view.trailingAnchor),
overlayView.bottomAnchor
.constraint(equalTo: view.bottomAnchor)
])
// We will observe a swipe gesture to check if it is a swipe
// upwards and then react accordingly
let swipeGesture = UIPanGestureRecognizer(target: self,
action: #selector(didSwipe(_:)))
overlayView.addGestureRecognizer(swipeGesture)
}
private func configureCollectionView()
{
collectionView = UICollectionView(frame: .zero,
collectionViewLayout: createLayout())
collectionView.backgroundColor = .clear
collectionView.register(UICollectionViewCell.self,
forCellWithReuseIdentifier: reuseIdentifier)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.showsHorizontalScrollIndicator = false
// Add some padding for the content on the left
collectionView.contentInset = UIEdgeInsets(top: 0,
left: 15,
bottom: 0,
right: 0)
collectionView.dataSource = self
collectionView.delegate = self
overlayView.addSubview(collectionView)
// Collection View should start below the screen
// We need to persist with this constraint so we can change it later
let bottomAnchor = overlayView.safeAreaLayoutGuide.bottomAnchor
cvBottomAnchor
= collectionView.bottomAnchor.constraint(equalTo: bottomAnchor,
constant: collectionViewHeight)
// Collection View starts as hidden and will be animated in swipe up
collectionView.alpha = 0.0
// Add collection view constraints
overlayView.addConstraints([
collectionView.leadingAnchor.constraint(equalTo: overlayView.leadingAnchor),
cvBottomAnchor!,
collectionView.trailingAnchor.constraint(equalTo: overlayView.trailingAnchor),
collectionView.heightAnchor.constraint(equalToConstant: collectionViewHeight)
])
}
private func createLayout() -> UICollectionViewFlowLayout
{
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: cellWidth, height: collectionViewHeight)
layout.minimumInteritemSpacing = 20
return layout
}
@objc
private func didSwipe(_ gesture: UIGestureRecognizer)
{
if !isCarouselShowing
{
let currentSwipeLocation = gesture.location(in: view)
if gesture.state == .began
{
// record the swipe location when we start the pan gesture
previousSwipeLocation = currentSwipeLocation
}
// On swipe continuation, verify the swipe is in the upward direction
if gesture.state == .changed,
let previousSwipeLocation = previousSwipeLocation,
currentSwipeLocation.y < previousSwipeLocation.y
{
isCarouselShowing = true
revealCollectionView()
}
}
}
// Animate the y position of the collection view and the alpha
private func revealCollectionView()
{
// We need to set the top constraint (y position)
// to be somewhere above the screen plus some padding
cvBottomAnchor?.constant = 0 - 75
UIView.animate(withDuration: 0.25) { [weak self] in
// animate change in constraints
self?.overlayView.layoutIfNeeded()
// reveal the collection view
self?.collectionView.alpha = 1.0
} completion: { (finished) in
// do something
}
}
}
extension SwipeCollectionView: UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int
{
return colors.count
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell
= collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath)
cell.layer.cornerRadius = 20
cell.clipsToBounds = true
cell.contentView.backgroundColor = colors[indexPath.item]
return cell
}
}
extension SwipeCollectionView: UICollectionViewDelegate
{
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
{
overlayView.backgroundColor
= colors[indexPath.item].withAlphaComponent(0.5)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment