Skip to content

Commit

Permalink
(wip): Swipe logic extracted to a generic swipe handler
Browse files Browse the repository at this point in the history
  • Loading branch information
bitwit committed Jun 28, 2016
1 parent a30b5c7 commit 4f45343
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 232 deletions.
4 changes: 4 additions & 0 deletions BWSwipeRevealCell.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
752EDC871BF65880007ED36D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 752EDC851BF65880007ED36D /* LaunchScreen.storyboard */; };
752EDC8D1BF659C7007ED36D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 752EDC8C1BF659C7007ED36D /* Assets.xcassets */; };
752EDC8F1BF65C68007ED36D /* example.gif in Resources */ = {isa = PBXBuildFile; fileRef = 752EDC8E1BF65C68007ED36D /* example.gif */; };
7563A67C1D09C82400A53142 /* BWSwipeableViewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */; };
75ABE4201C0F88AD00F42894 /* BWSwipeRevealCell.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 752EDC601BF64B5B007ED36D /* BWSwipeRevealCell.framework */; };
75ABE4221C0F8AF300F42894 /* BWSwipeRevealCell.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 752EDC601BF64B5B007ED36D /* BWSwipeRevealCell.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -62,6 +63,7 @@
752EDC881BF65880007ED36D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
752EDC8C1BF659C7007ED36D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
752EDC8E1BF65C68007ED36D /* example.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = example.gif; sourceTree = "<group>"; };
7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWSwipeableViewHandler.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -106,6 +108,7 @@
752EDC621BF64B5B007ED36D /* BWSwipeRevealCell */ = {
isa = PBXGroup;
children = (
7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */,
752EDC6D1BF654F8007ED36D /* BWSwipeCell.swift */,
752EDC6E1BF654F8007ED36D /* BWSwipeRevealCell.swift */,
752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */,
Expand Down Expand Up @@ -243,6 +246,7 @@
buildActionMask = 2147483647;
files = (
752EDC6C1BF65169007ED36D /* README.md in Sources */,
7563A67C1D09C82400A53142 /* BWSwipeableViewHandler.swift in Sources */,
752EDC701BF654F8007ED36D /* BWSwipeRevealCell.swift in Sources */,
752EDC6F1BF654F8007ED36D /* BWSwipeCell.swift in Sources */,
);
Expand Down
242 changes: 40 additions & 202 deletions BWSwipeRevealCell/BWSwipeCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,6 @@
import Foundation
import UIKit

//Defines the interaction type of the table cell
public enum BWSwipeCellType: Int {
case SwipeThrough = 0 // swipes with finger and animates through
case SpringRelease // resists pulling and bounces back
case SlidingDoor // swipe to a stopping position where underlying buttons can be revealed
}

public enum BWSwipeCellRevealDirection {
case None
case Both
case Right
case Left
}

public enum BWSwipeCellState {
case Normal
case PastThresholdLeft
case PastThresholdRight
}

@objc public protocol BWSwipeCellDelegate: NSObjectProtocol {
optional func swipeCellDidStartSwiping(cell: BWSwipeCell)
optional func swipeCellDidSwipe(cell: BWSwipeCell)
Expand All @@ -37,210 +17,68 @@ public enum BWSwipeCellState {
optional func swipeCellDidChangeState(cell: BWSwipeCell)
}

public class BWSwipeCell:UITableViewCell {

// The interaction type for this table cell
public var type:BWSwipeCellType = .SpringRelease

// The allowable swipe direction(s)
public var revealDirection: BWSwipeCellRevealDirection = .Both
public class BWSwipeCell: UITableViewCell {

// The current state of the cell (either normal or past a threshold)
public private(set) var state: BWSwipeCellState = .Normal
public private(set) var swipeHandler: BWSwipeViewHandler!
public var delegate: BWSwipeCellDelegate?

// The point at which pan elasticity starts, and `state` changes. Defaults to the height of the `UITableViewCell` (i.e. when it form a perfect square)
public lazy var threshold: CGFloat = {
return self.frame.height
}()

// A number between 0 and 1 to indicate progress toward reaching threshold in the current swiping direction. Useful for changing UI gradually as the user swipes.
public var progress: CGFloat {
get {
let progress = abs(self.contentView.frame.origin.x) / self.threshold
return (progress > 1) ? 1 : progress
}
override public init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

self.initialize()
}

// Should we allow the cell to be pulled past the threshold at all? (.SwipeThrough cells will ignore this)
public var shouldExceedThreshold: Bool = true

// Control how much elastic resistance there is past threshold, if it can be exceeded. Default is `0.7` and `1.0` would mean no elastic resistance
public var panElasticityFactor: CGFloat = 0.7

// Length of the animation on release
public var animationDuration: Double = 0.2

// BWSwipeCell Delegate
public weak var delegate: BWSwipeCellDelegate?

private lazy var releaseCompletionBlock:((Bool) -> Void)? = {
return {
[weak self] (finished: Bool) in

guard let this = self else { return }

this.delegate?.swipeCellDidCompleteRelease?(this)
this.cleanUp()
}
}()

// MARK: - Swipe Cell Functions

public func initialize() {
self.selectionStyle = .None
self.contentView.backgroundColor = UIColor.whiteColor()
let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(BWSwipeCell.handlePanGesture(_:)))
panGestureRecognizer.delegate = self
self.addGestureRecognizer(panGestureRecognizer)
required public init?(coder aDecoder: NSCoder) {

let backgroundView: UIView = UIView(frame: self.frame)
backgroundView.backgroundColor = UIColor.whiteColor()
self.backgroundView = backgroundView
super.init(coder: aDecoder)
self.initialize()
}

public func cleanUp() {
self.state = .Normal
override public func prepareForReuse() {

super.prepareForReuse()
swipeHandler.cleanUp()
}

func handlePanGesture(panGestureRecognizer: UIPanGestureRecognizer) {
let translation: CGPoint = panGestureRecognizer.translationInView(panGestureRecognizer.view)
var panOffset: CGFloat = translation.x
public func initialize() {

// If we have elasticity to consider, do some extra calculations for panOffset
if self.type != .SwipeThrough && abs(translation.x) > self.threshold {
if self.shouldExceedThreshold {
let offset: CGFloat = abs(translation.x)
panOffset = offset - ((offset - self.threshold) * self.panElasticityFactor)
panOffset *= translation.x < 0 ? -1.0 : 1.0
} else {
// If we don't allow exceeding the threshold
panOffset = translation.x < 0 ? -self.threshold : self.threshold
}
}
contentView.backgroundColor = UIColor.whiteColor()

// Start, continue or complete the swipe gesture
let actualTranslation: CGPoint = CGPointMake(panOffset, translation.y)
if panGestureRecognizer.state == .Began && panGestureRecognizer.numberOfTouches() > 0 {
let newTranslation = CGPointMake(self.contentView.frame.origin.x, 0)
panGestureRecognizer.setTranslation(newTranslation, inView: panGestureRecognizer.view)
self.didStartSwiping()
self.animateContentViewForPoint(newTranslation)
}
else {
if panGestureRecognizer.state == .Changed && panGestureRecognizer.numberOfTouches() > 0 {
self.animateContentViewForPoint(actualTranslation)
}
else {
self.resetCellPosition()
}
}
}

func didStartSwiping() {
self.delegate?.swipeCellDidStartSwiping?(self)
}

public func animateContentViewForPoint(point: CGPoint) {
if (point.x > 0 && self.revealDirection == .Left) || (point.x < 0 && self.revealDirection == .Right) || self.revealDirection == .Both {
self.contentView.frame = CGRectOffset(self.contentView.bounds, point.x, 0)
let previousState = state
if point.x >= self.threshold {
self.state = .PastThresholdLeft
}
else if point.x < -self.threshold {
self.state = .PastThresholdRight
}
else {
self.state = .Normal
}

if self.state != previousState {
self.delegate?.swipeCellDidChangeState?(self)
}
self.delegate?.swipeCellDidSwipe?(self)
}
else {
if (point.x > 0 && self.revealDirection == .Right) || (point.x < 0 && self.revealDirection == .Left) {
self.contentView.frame = CGRectOffset(self.contentView.bounds, 0, 0)
}
}
}

public func resetCellPosition() {
self.delegate?.swipeCellWillRelease?(self)
if self.type == .SpringRelease || self.state == .Normal {
self.animateCellSpringRelease()
} else if self.type == .SlidingDoor {
self.animateCellSlidingDoor()
let bv:UIView
if let b = backgroundView {
bv = b
} else {
self.animateCellSwipeThrough()
bv = UIView(frame: self.frame)
bv.backgroundColor = UIColor.whiteColor()
backgroundView = bv
}

swipeHandler = BWSwipeViewHandler(contentView: contentView, backgroundView: bv)
swipeHandler.delegate = self
}
}

extension BWSwipeCell: BWSwipeViewHandlerDelegate {

// MARK: - Reset animations

func animateCellSpringRelease() {
UIView.animateWithDuration(self.animationDuration,
delay: 0,
options: .CurveEaseOut,
animations: {
self.contentView.frame = self.contentView.bounds
},
completion: self.releaseCompletionBlock)
}

func animateCellSlidingDoor() {
UIView.animateWithDuration(self.animationDuration,
delay: 0,
options: .AllowUserInteraction,
animations: {
let pointX = self.contentView.frame.origin.x
if pointX > 0 {
self.contentView.frame.origin.x = self.threshold
} else if pointX < 0 {
self.contentView.frame.origin.x = -self.threshold
}
},
completion: self.releaseCompletionBlock)
}

func animateCellSwipeThrough() {
UIView.animateWithDuration(self.animationDuration,
delay: 0,
options: UIViewAnimationOptions.CurveLinear,
animations: {
let direction:CGFloat = (self.contentView.frame.origin.x > 0) ? 1 : -1
self.contentView.frame.origin.x = direction * (self.contentView.bounds.width + self.threshold)
}, completion: self.releaseCompletionBlock)
public func swipeViewDidStartSwiping(handler: BWSwipeViewHandler) {
delegate?.swipeCellDidStartSwiping?(self)
}

// MARK: - UITableViewCell Overrides

override public init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.initialize()

public func swipeViewDidSwipe(handler: BWSwipeViewHandler) {
delegate?.swipeCellDidSwipe?(self)
}

required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
public func swipeViewWillRelease(handler: BWSwipeViewHandler) {
delegate?.swipeCellWillRelease?(self)
}

override public func prepareForReuse() {
super.prepareForReuse()
self.cleanUp()
public func swipeViewDidCompleteRelease(handler: BWSwipeViewHandler) {
delegate?.swipeCellDidCompleteRelease?(self)
}

override public func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
public func swipeViewDidChangeState(handler: BWSwipeViewHandler) {
delegate?.swipeCellDidChangeState?(self)
}

override public func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isKindOfClass(UIPanGestureRecognizer) && self.revealDirection != .None {
let pan:UIPanGestureRecognizer = gestureRecognizer as! UIPanGestureRecognizer
let translation: CGPoint = pan.translationInView(self.superview)
return (fabs(translation.x) / fabs(translation.y) > 1) ? true : false
}
return false
}
}
Loading

0 comments on commit 4f45343

Please sign in to comment.