Skip to content

Instantly share code, notes, and snippets.

@antonyalkmim
Created July 12, 2018 19:18
Show Gist options
  • Save antonyalkmim/0a56a93ff39273c436a742be5c98f061 to your computer and use it in GitHub Desktop.
Save antonyalkmim/0a56a93ff39273c436a742be5c98f061 to your computer and use it in GitHub Desktop.
Swipeable UITableViewCell where you can use custom view behind cell.contentView
//
// SwipeableCell.swift
// Antony Alkmim
//
// Created by Antony Alkmim on 09/07/18.
// Inspired by: https://www.raywenderlich.com/62435/make-swipeable-table-view-cell-actions-without-going-nuts-scroll-views
//
import UIKit
import PINRemoteImage
class SwipeableCell: UITableViewCell {
@IBOutlet weak var wrapperView: UIView!
@IBOutlet weak var menuView: UIView!
@IBOutlet weak var unreadButton: UIButton!
@IBOutlet weak var deleteButton: UIButton!
@IBOutlet weak var leftConstraint: NSLayoutConstraint!
@IBOutlet weak var rightConstraint: NSLayoutConstraint!
private var panStartPoint: CGPoint!
private var startingRightLayoutConstraintConstant: CGFloat = 0
private let buttonTotalWidth: CGFloat = 218.0
private var isMenuOpened = false
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .none
// swipe gesture to show menu options
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panThisCell(_:)))
panGesture.delegate = self
wrapperView.isUserInteractionEnabled = true
wrapperView.addGestureRecognizer(panGesture)
}
override func prepareForReuse() {
super.prepareForReuse()
hideOptionsButtons()
}
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // this enables tableview scroll
}
private func hideOptionsButtons() {
if startingRightLayoutConstraintConstant == 0 && rightConstraint.constant == 0 {
//Already all the way closed, no bounce necessary
return
}
rightConstraint.constant = 0
leftConstraint.constant = 0
startingRightLayoutConstraintConstant = rightConstraint.constant
isMenuOpened = false
updateConstraintsIfNeeded()
}
private func showOptionsButtons() {
if startingRightLayoutConstraintConstant == buttonTotalWidth && rightConstraint.constant == buttonTotalWidth {
return
}
leftConstraint.constant = -buttonTotalWidth //- kBounceValue
rightConstraint.constant = buttonTotalWidth //+ kBounceValue
startingRightLayoutConstraintConstant = rightConstraint.constant
isMenuOpened = true
updateConstraintsIfNeeded()
}
override func updateConstraintsIfNeeded() {
super.updateConstraintsIfNeeded()
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
})
}
private func panRecognizeChanged(_ currentPoint: CGPoint) {
let deltaX = currentPoint.x - panStartPoint.x
let panningLeft = currentPoint.x < panStartPoint.x
if startingRightLayoutConstraintConstant == 0 {
//The cell was closed and is now opening
if !panningLeft {
let constant = max(-deltaX, 0)
if constant == 0 {
hideOptionsButtons()
} else {
self.rightConstraint.constant = -constant
}
} else {
let constant = min(-deltaX, buttonTotalWidth)
if constant == buttonTotalWidth {
showOptionsButtons()
} else {
self.rightConstraint.constant = constant
}
}
} else {
//The cell was at least partially open.
let adjustment = startingRightLayoutConstraintConstant - deltaX
if !panningLeft {
let constant = max(adjustment, 0)
if constant == 0 {
hideOptionsButtons()
} else {
rightConstraint.constant = constant
}
} else {
let constant = min(adjustment, buttonTotalWidth)
if constant == buttonTotalWidth {
showOptionsButtons()
} else {
rightConstraint.constant = constant
}
}
}
leftConstraint.constant = -rightConstraint.constant
}
private func panRecognizeEnded() {
if startingRightLayoutConstraintConstant == 0 {
//Cell was opening
let halfOfButtonOne: CGFloat = 107 //button1.frame.width / 2
if rightConstraint.constant >= halfOfButtonOne {
//Open all the way
showOptionsButtons()
} else {
//Re-close
hideOptionsButtons()
}
} else {
//Cell was closing
let buttonOnePlusHalfOfButton2: CGFloat = 107 + (107/2)//self.button1.frame.width + (self.button2.frame.width / 2)
if rightConstraint.constant >= buttonOnePlusHalfOfButton2 {
//Re-open all the way
showOptionsButtons()
} else {
//Close
hideOptionsButtons()
}
}
}
@objc private func panThisCell(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
panStartPoint = recognizer.translation(in: wrapperView)
case .changed:
let currPoint = recognizer.translation(in: wrapperView)
panRecognizeChanged(currPoint)
case .ended:
panRecognizeEnded()
case .cancelled:
if startingRightLayoutConstraintConstant == 0 { //Cell was closed - reset everything to 0
hideOptionsButtons()
} else { //Cell was open - reset to the open state
showOptionsButtons()
}
default: break
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if isMenuOpened {
hideOptionsButtons()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment