Created
July 12, 2018 19:18
-
-
Save antonyalkmim/0a56a93ff39273c436a742be5c98f061 to your computer and use it in GitHub Desktop.
Swipeable UITableViewCell where you can use custom view behind cell.contentView
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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