From 5ae558a971dd1d22890b9b48a36340df3c31cf00 Mon Sep 17 00:00:00 2001 From: kylenewsome Date: Mon, 27 Jun 2016 21:43:54 -0400 Subject: [PATCH 1/5] (wip): Version 2.0.0 - Convert to swift 3.0 syntax - Swipe logic extracted to a generic swipe handler --- BWSwipeRevealCell.xcodeproj/project.pbxproj | 10 + BWSwipeRevealCell/BWSwipeCell.swift | 254 ++++-------------- BWSwipeRevealCell/BWSwipeRevealCell.swift | 135 ++++++---- .../BWSwipeableViewHandler.swift | 228 ++++++++++++++++ BWSwipeRevealCellExample/AppDelegate.swift | 26 +- .../MasterViewController.swift | 106 ++++---- 6 files changed, 429 insertions(+), 330 deletions(-) create mode 100644 BWSwipeRevealCell/BWSwipeableViewHandler.swift diff --git a/BWSwipeRevealCell.xcodeproj/project.pbxproj b/BWSwipeRevealCell.xcodeproj/project.pbxproj index 7c44f4a..2f004e7 100644 --- a/BWSwipeRevealCell.xcodeproj/project.pbxproj +++ b/BWSwipeRevealCell.xcodeproj/project.pbxproj @@ -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 */ @@ -62,6 +63,7 @@ 752EDC881BF65880007ED36D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 752EDC8C1BF659C7007ED36D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 752EDC8E1BF65C68007ED36D /* example.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = example.gif; sourceTree = ""; }; + 7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWSwipeableViewHandler.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -106,6 +108,7 @@ 752EDC621BF64B5B007ED36D /* BWSwipeRevealCell */ = { isa = PBXGroup; children = ( + 7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */, 752EDC6D1BF654F8007ED36D /* BWSwipeCell.swift */, 752EDC6E1BF654F8007ED36D /* BWSwipeRevealCell.swift */, 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */, @@ -191,9 +194,11 @@ TargetAttributes = { 752EDC5F1BF64B5B007ED36D = { CreatedOnToolsVersion = 7.1; + LastSwiftMigration = 0800; }; 752EDC741BF65880007ED36D = { CreatedOnToolsVersion = 7.1; + LastSwiftMigration = 0800; }; }; }; @@ -243,6 +248,7 @@ buildActionMask = 2147483647; files = ( 752EDC6C1BF65169007ED36D /* README.md in Sources */, + 7563A67C1D09C82400A53142 /* BWSwipeableViewHandler.swift in Sources */, 752EDC701BF654F8007ED36D /* BWSwipeRevealCell.swift in Sources */, 752EDC6F1BF654F8007ED36D /* BWSwipeCell.swift in Sources */, ); @@ -390,6 +396,7 @@ PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.BWSwipeRevealCell; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -406,6 +413,7 @@ PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.BWSwipeRevealCell; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -418,6 +426,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.BWSwipeRevealCellExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -430,6 +439,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.BWSwipeRevealCellExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/BWSwipeRevealCell/BWSwipeCell.swift b/BWSwipeRevealCell/BWSwipeCell.swift index 0c8a961..44a7097 100644 --- a/BWSwipeRevealCell/BWSwipeCell.swift +++ b/BWSwipeRevealCell/BWSwipeCell.swift @@ -9,238 +9,76 @@ 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) - optional func swipeCellWillRelease(cell: BWSwipeCell) - optional func swipeCellDidCompleteRelease(cell: BWSwipeCell) - optional func swipeCellDidChangeState(cell: BWSwipeCell) + @objc optional func swipeCellDidStartSwiping(_ cell: BWSwipeCell) + @objc optional func swipeCellDidSwipe(_ cell: BWSwipeCell) + @objc optional func swipeCellWillRelease(_ cell: BWSwipeCell) + @objc optional func swipeCellDidCompleteRelease(_ cell: BWSwipeCell) + @objc 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? - - public 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() } - public 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.white() - // 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() - } - } - } - - public 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.white() + backgroundView = bv } + + swipeHandler = BWSwipeViewHandler(contentView: contentView, backgroundView: bv) + swipeHandler.delegate = self } +} + +extension BWSwipeCell: BWSwipeViewHandlerDelegate { - // MARK: - Reset animations - - public func animateCellSpringRelease() { - UIView.animateWithDuration(self.animationDuration, - delay: 0, - options: .CurveEaseOut, - animations: { - self.contentView.frame = self.contentView.bounds - }, - completion: self.releaseCompletionBlock) - } - - public 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) - } - - public 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 - - public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - self.initialize() + + public func swipeViewDidSwipe(_ handler: BWSwipeViewHandler) { + delegate?.swipeCellDidSwipe?(self) } - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - self.initialize() + public func swipeViewWillRelease(_ handler: BWSwipeViewHandler) { + delegate?.swipeCellWillRelease?(self) } - public override func prepareForReuse() { - super.prepareForReuse() - self.cleanUp() + public func swipeViewDidCompleteRelease(_ handler: BWSwipeViewHandler) { + delegate?.swipeCellDidCompleteRelease?(self) } - public override func setSelected(selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) + public func swipeViewDidChangeState(_ handler: BWSwipeViewHandler) { + delegate?.swipeCellDidChangeState?(self) } - public override 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 - } -} \ No newline at end of file +} diff --git a/BWSwipeRevealCell/BWSwipeRevealCell.swift b/BWSwipeRevealCell/BWSwipeRevealCell.swift index 28677c3..531ac16 100644 --- a/BWSwipeRevealCell/BWSwipeRevealCell.swift +++ b/BWSwipeRevealCell/BWSwipeRevealCell.swift @@ -10,7 +10,7 @@ import Foundation import UIKit @objc public protocol BWSwipeRevealCellDelegate:BWSwipeCellDelegate { - optional func swipeCellActivatedAction(cell: BWSwipeCell, isActionLeft: Bool) + @objc optional func swipeCellActivatedAction(_ cell: BWSwipeCell, isActionLeft: Bool) } public class BWSwipeRevealCell: BWSwipeCell { @@ -26,7 +26,7 @@ public class BWSwipeRevealCell: BWSwipeCell { } public var shouldCleanUpBackView = true - public var bgViewInactiveColor: UIColor = UIColor.grayColor() + public var bgViewInactiveColor: UIColor = UIColor.gray() public var bgViewLeftColor: UIColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1) public var bgViewRightColor: UIColor = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1) @@ -36,11 +36,11 @@ public class BWSwipeRevealCell: BWSwipeCell { private var _leftBackButton: UIButton? public var leftBackButton:UIButton? { if _leftBackButton == nil { - _leftBackButton = UIButton(frame: CGRectMake(0, 0, CGRectGetHeight(self.frame), CGRectGetHeight(self.frame))) - _leftBackButton!.setImage(self.bgViewLeftImage, forState: .Normal) - _leftBackButton!.addTarget(self, action: #selector(BWSwipeRevealCell.leftButtonTapped), forControlEvents: .TouchUpInside) - _leftBackButton!.tintColor = UIColor.whiteColor() - _leftBackButton!.contentMode = .Center + _leftBackButton = UIButton(frame: CGRect(x: 0, y: 0, width: self.frame.height, height: self.frame.height)) + _leftBackButton!.setImage(self.bgViewLeftImage, for: UIControlState()) + _leftBackButton!.addTarget(self, action: #selector(BWSwipeRevealCell.leftButtonTapped), for: .touchUpInside) + _leftBackButton!.tintColor = UIColor.white() + _leftBackButton!.contentMode = .center self.backView!.addSubview(_leftBackButton!) } return _leftBackButton @@ -49,38 +49,34 @@ public class BWSwipeRevealCell: BWSwipeCell { private var _rightBackButton: UIButton? public var rightBackButton:UIButton? { if _rightBackButton == nil { - _rightBackButton = UIButton(frame: CGRectMake(CGRectGetMaxX(self.contentView.frame), 0, CGRectGetHeight(self.frame), CGRectGetHeight(self.frame))) - _rightBackButton!.setImage(self.bgViewRightImage, forState: .Normal) - _rightBackButton!.addTarget(self, action: #selector(BWSwipeRevealCell.rightButtonTapped), forControlEvents: .TouchUpInside) - _rightBackButton!.tintColor = UIColor.whiteColor() - _rightBackButton!.contentMode = .Center + _rightBackButton = UIButton(frame: CGRect(x: self.contentView.frame.maxX, y: 0, width: self.frame.height, height: self.frame.height)) + _rightBackButton!.setImage(self.bgViewRightImage, for: UIControlState()) + _rightBackButton!.addTarget(self, action: #selector(BWSwipeRevealCell.rightButtonTapped), for: .touchUpInside) + _rightBackButton!.tintColor = UIColor.white() + _rightBackButton!.contentMode = .center self.backView!.addSubview(_rightBackButton!) } return _rightBackButton } override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier) + super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) let bgView: UIView = UIView(frame: self.frame) self.selectedBackgroundView = bgView } public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + let bgView: UIView = UIView(frame: self.frame) self.selectedBackgroundView = bgView } public override func prepareForReuse() { + super.prepareForReuse() - } - - public override func layoutSubviews() { - super.layoutSubviews() - } - - public override func cleanUp() { - super.cleanUp() + if self.shouldCleanUpBackView { _leftBackButton?.removeFromSuperview() _leftBackButton = nil @@ -91,21 +87,48 @@ public class BWSwipeRevealCell: BWSwipeCell { } } - public override func didStartSwiping() { - super.didStartSwiping() + public override func layoutSubviews() { + super.layoutSubviews() + } + + override public func swipeViewDidStartSwiping(_ handler: BWSwipeViewHandler) { + + super.swipeViewDidStartSwiping(handler) self.backgroundView!.addSubview(self.backView!) } - public override func animateContentViewForPoint(point: CGPoint) { - super.animateContentViewForPoint(point) + override public func swipeViewWillRelease(_ handler: BWSwipeViewHandler) { + + super.swipeViewWillRelease(handler) + + if handler.type == .springRelease || handler.state == .normal { + self.animateCellSpringRelease() + } else if handler.type == .slidingDoor { + self.animateCellSlidingDoor() + } else { + self.animateCellSwipeThrough() + } + } + + override public func swipeViewDidSwipe(_ handler: BWSwipeViewHandler) { + + super.swipeViewDidSwipe(handler) + //TODO: integrate with content animation + + let position = handler.contentView.frame.origin + animateContentViewForPoint(position, progress: handler.progress) + } + + public func animateContentViewForPoint(_ point: CGPoint, progress:CGFloat) { + if point.x > 0 { let frame = self.leftBackButton!.frame let minX = getBackgroundViewImagesMaxX(point.x) - let minY = CGRectGetMinY(frame) - self.leftBackButton!.frame = CGRectMake(minX, minY, CGRectGetWidth(frame), CGRectGetHeight(frame)) - self.leftBackButton?.alpha = self.progress - UIView.transitionWithView(_leftBackButton!, duration: 0.13, options: .TransitionCrossDissolve, animations: { - if point.x >= CGRectGetHeight(self.frame) { + let minY = frame.minY + self.leftBackButton!.frame = CGRect(x: minX, y: minY, width: frame.width, height: frame.height) + self.leftBackButton?.alpha = progress + UIView.transition(with: _leftBackButton!, duration: 0.13, options: .transitionCrossDissolve, animations: { + if point.x >= self.frame.height { self.backView?.backgroundColor = self.bgViewLeftColor } else { @@ -115,11 +138,11 @@ public class BWSwipeRevealCell: BWSwipeCell { } else if point.x < 0 { let frame = self.rightBackButton!.frame let maxX = getBackgroundViewImagesMaxX(point.x) - let minY = CGRectGetMinY(frame) - self.rightBackButton!.frame = (CGRectMake(maxX, minY, CGRectGetWidth(frame), CGRectGetHeight(frame))) - self.rightBackButton?.alpha = self.progress - UIView.transitionWithView(_rightBackButton!, duration: 0.13, options: .TransitionCrossDissolve, animations: { - if -point.x >= CGRectGetHeight(self.frame) { + let minY = frame.minY + self.rightBackButton!.frame = (CGRect(x: maxX, y: minY, width: frame.width, height: frame.height)) + self.rightBackButton?.alpha = progress + UIView.transition(with: _rightBackButton!, duration: 0.13, options: .transitionCrossDissolve, animations: { + if -point.x >= self.frame.height { self.backView?.backgroundColor = self.bgViewRightColor } else { self.backView?.backgroundColor = self.bgViewInactiveColor @@ -127,60 +150,60 @@ public class BWSwipeRevealCell: BWSwipeCell { }, completion: nil) } } + // MARK: - Reveal Cell Animations - public override func animateCellSpringRelease() { - super.animateCellSpringRelease() + public func animateCellSpringRelease() { + let pointX = self.contentView.frame.origin.x - UIView.animateWithDuration(self.animationDuration, + UIView.animate(withDuration: swipeHandler.animationDuration, delay: 0, - options: .CurveLinear, + options: .curveLinear, animations: { if pointX > 0 { - self.leftBackButton!.frame.origin.x = -self.threshold + self.leftBackButton!.frame.origin.x = -self.swipeHandler.threshold } else if pointX < 0 { - self.rightBackButton!.frame.origin.x = CGRectGetMaxX(self.frame) + self.rightBackButton!.frame.origin.x = self.frame.maxX } }, completion: nil) } - public override func animateCellSwipeThrough() { - super.animateCellSwipeThrough() + public func animateCellSwipeThrough() { + let pointX = self.contentView.frame.origin.x - UIView.animateWithDuration(self.animationDuration, + UIView.animate(withDuration: swipeHandler.animationDuration, delay: 0, - options: .CurveLinear, + options: .curveLinear, animations: { if pointX > 0 { - self.leftBackButton!.frame.origin.x = CGRectGetMaxX(self.frame) + self.leftBackButton!.frame.origin.x = self.frame.maxX } else if pointX < 0 { - self.rightBackButton!.frame.origin.x = -self.threshold + self.rightBackButton!.frame.origin.x = -self.swipeHandler.threshold } }, completion: nil) } - public override func animateCellSlidingDoor() { - super.animateCellSlidingDoor() + public func animateCellSlidingDoor() { self.shouldCleanUpBackView = false } // MARK: - Reveal Cell - public func getBackgroundViewImagesMaxX(x:CGFloat) -> CGFloat { + public func getBackgroundViewImagesMaxX(_ x:CGFloat) -> CGFloat { if x > 0 { let frame = self.leftBackButton!.frame - if self.type == .SwipeThrough { + if swipeHandler.type == .swipeThrough { return self.contentView.frame.origin.x - frame.width } else { - return min(CGRectGetMinX(self.contentView.frame) - CGRectGetWidth(frame), 0) + return min(self.contentView.frame.minX - frame.width, 0) } } else { let frame = self.rightBackButton!.frame - if self.type == .SwipeThrough { - return CGRectGetMaxX(self.contentView.frame) + if swipeHandler.type == .swipeThrough { + return self.contentView.frame.maxX } else { - return max(CGRectGetMaxX(self.frame) - CGRectGetWidth(frame), CGRectGetMaxX(self.contentView.frame)) + return max(self.frame.maxX - frame.width, self.contentView.frame.maxX) } } } @@ -199,4 +222,4 @@ public class BWSwipeRevealCell: BWSwipeCell { delegate?.swipeCellActivatedAction?(self, isActionLeft: false) } -} \ No newline at end of file +} diff --git a/BWSwipeRevealCell/BWSwipeableViewHandler.swift b/BWSwipeRevealCell/BWSwipeableViewHandler.swift new file mode 100644 index 0000000..fce9b43 --- /dev/null +++ b/BWSwipeRevealCell/BWSwipeableViewHandler.swift @@ -0,0 +1,228 @@ +import Foundation +import UIKit + + +@objc public protocol BWSwipeViewHandlerDelegate: NSObjectProtocol { + @objc optional func swipeViewDidStartSwiping(_ handler: BWSwipeViewHandler) + @objc optional func swipeViewDidSwipe(_ handler: BWSwipeViewHandler) + @objc optional func swipeViewWillRelease(_ handler: BWSwipeViewHandler) + @objc optional func swipeViewDidCompleteRelease(_ handler: BWSwipeViewHandler) + @objc optional func swipeViewDidChangeState(_ handler: BWSwipeViewHandler) +} + +//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 +} + +public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { + + public let backgroundView: UIView + public let contentView: UIView + + // The interaction type for this table cell + public var type: BWSwipeCellType = .springRelease + + // The allowable swipe direction(s) + public var revealDirection: BWSwipeCellRevealDirection = .both + + // The current state of the cell (either normal or past a threshold) + public private(set) var state: BWSwipeCellState = .normal + + // 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.contentView.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(contentView.frame.origin.x) / self.threshold + return (progress > 1) ? 1 : progress + } + } + + // 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: BWSwipeViewHandlerDelegate? + + private lazy var releaseCompletionBlock:((Bool) -> Void)? = { + return { + [weak self] (finished: Bool) in + + guard let this = self else { return } + + this.delegate?.swipeViewDidCompleteRelease?(this) + this.cleanUp() + } + }() + + + //MARK: Initialization + + public init(contentView:UIView, backgroundView:UIView) { + //TODO: self.selectionStyle = .None + + self.contentView = contentView + self.backgroundView = backgroundView + + super.init() + + let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(BWSwipeViewHandler.handlePanGesture(_:))) + panGestureRecognizer.delegate = self + contentView.addGestureRecognizer(panGestureRecognizer) + } + + // MARK: - Swipe Cell Functions + + public func cleanUp() { + self.state = .normal + } + + func handlePanGesture(_ panGestureRecognizer: UIPanGestureRecognizer) { + let translation: CGPoint = panGestureRecognizer.translation(in: panGestureRecognizer.view) + var panOffset: CGFloat = translation.x + + // 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 + } + } + + // Start, continue or complete the swipe gesture + let actualTranslation: CGPoint = CGPoint(x: panOffset, y: translation.y) + if panGestureRecognizer.state == .began && panGestureRecognizer.numberOfTouches() > 0 { + let newTranslation = CGPoint(x: self.contentView.frame.origin.x, y: 0) + panGestureRecognizer.setTranslation(newTranslation, in: panGestureRecognizer.view) + self.didStartSwiping() + self.animateContentViewForPoint(newTranslation) + } + else { + if panGestureRecognizer.state == .changed && panGestureRecognizer.numberOfTouches() > 0 { + self.animateContentViewForPoint(actualTranslation) + } + else { + self.resetCellPosition() + } + } + } + + func didStartSwiping() { + delegate?.swipeViewDidStartSwiping?(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 = self.contentView.bounds.offsetBy(dx: point.x, dy: 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 { + delegate?.swipeViewDidChangeState?(self) + } + delegate?.swipeViewDidSwipe?(self) + } + else { + if (point.x > 0 && self.revealDirection == .right) || (point.x < 0 && self.revealDirection == .left) { + self.contentView.frame = self.contentView.bounds.offsetBy(dx: 0, dy: 0) + } + } + } + + public func resetCellPosition() { + + delegate?.swipeViewWillRelease?(self) + + if self.type == .springRelease || self.state == .normal { + self.animateCellSpringRelease() + } else if self.type == .slidingDoor { + self.animateCellSlidingDoor() + } else { + self.animateCellSwipeThrough() + } + } + + // MARK: - Reset animations + + func animateCellSpringRelease() { + UIView.animate(withDuration: self.animationDuration, + delay: 0, + options: .curveEaseOut, + animations: { + self.contentView.frame = self.contentView.bounds + }, + completion: self.releaseCompletionBlock) + } + + func animateCellSlidingDoor() { + UIView.animate(withDuration: 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.animate(withDuration: 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 gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer is UIPanGestureRecognizer && self.revealDirection != .none { + let pan:UIPanGestureRecognizer = gestureRecognizer as! UIPanGestureRecognizer + let translation: CGPoint = pan.translation(in: contentView.superview) //TODO: superview reference may not be accurate + return (fabs(translation.x) / fabs(translation.y) > 1) ? true : false + } + return false + } +} diff --git a/BWSwipeRevealCellExample/AppDelegate.swift b/BWSwipeRevealCellExample/AppDelegate.swift index b472b46..3e814d9 100644 --- a/BWSwipeRevealCellExample/AppDelegate.swift +++ b/BWSwipeRevealCellExample/AppDelegate.swift @@ -15,7 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. let navigationController = self.window!.rootViewController as! UINavigationController let masterNavigationController = navigationController @@ -24,25 +24,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele return true } - func applicationWillResignActive(application: UIApplication) { + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - func applicationDidEnterBackground(application: UIApplication) { + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - func applicationWillEnterForeground(application: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - func applicationDidBecomeActive(application: UIApplication) { + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - func applicationWillTerminate(application: UIApplication) { + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. // Saves changes in the application's managed object context before the application terminates. self.saveContext() @@ -50,26 +50,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele // MARK: - Core Data stack - lazy var applicationDocumentsDirectory: NSURL = { + lazy var applicationDocumentsDirectory: URL = { // The directory the application uses to store the Core Data store file. This code uses a directory named "ca.bitwit.BWSwipeRevealCellExample" in the application's documents Application Support directory. - let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) + let urls = FileManager.default().urlsForDirectory(.documentDirectory, inDomains: .userDomainMask) return urls[urls.count-1] }() lazy var managedObjectModel: NSManagedObjectModel = { // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. - let modelURL = NSBundle.mainBundle().URLForResource("BWSwipeRevealCellExample", withExtension: "momd")! - return NSManagedObjectModel(contentsOfURL: modelURL)! + let modelURL = Bundle.main().urlForResource("BWSwipeRevealCellExample", withExtension: "momd")! + return NSManagedObjectModel(contentsOf: modelURL)! }() lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. // Create the coordinator and store let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) - let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite") + let url = try! self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite") var failureReason = "There was an error creating or loading the application's saved data." do { - try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil) + try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) } catch { // Report any error we got. var dict = [String: AnyObject]() @@ -90,7 +90,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele lazy var managedObjectContext: NSManagedObjectContext = { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. let coordinator = self.persistentStoreCoordinator - var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) + var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) managedObjectContext.persistentStoreCoordinator = coordinator return managedObjectContext }() diff --git a/BWSwipeRevealCellExample/MasterViewController.swift b/BWSwipeRevealCellExample/MasterViewController.swift index f653312..ae32485 100644 --- a/BWSwipeRevealCellExample/MasterViewController.swift +++ b/BWSwipeRevealCellExample/MasterViewController.swift @@ -16,17 +16,17 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel override func viewDidLoad() { super.viewDidLoad() - let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:") + let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:))) self.navigationItem.rightBarButtonItem = addButton } - func insertNewObject(sender: AnyObject) { + func insertNewObject(_ sender: AnyObject) { let context = self.fetchedResultsController.managedObjectContext let entity = self.fetchedResultsController.fetchRequest.entity! //Create one of each type for i in 0..<3 { - let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) + let newManagedObject = NSEntityDescription.insertNewObject(forEntityName: entity.name!, into: context) newManagedObject.setValue(i, forKey: "type") } @@ -37,10 +37,10 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel } } - func removeObjectAtIndexPath(indexPath:NSIndexPath) { + func removeObjectAtIndexPath(_ indexPath:IndexPath) { let context = self.fetchedResultsController.managedObjectContext //Deleting objects regardless of done/delete for the purpose of this example - context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject) + context.delete(self.fetchedResultsController.object(at: indexPath) as! NSManagedObject) do { try context.save() } catch { @@ -50,46 +50,46 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel // MARK: - Table View - override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 72 } - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + override func numberOfSections(in tableView: UITableView) -> Int { return self.fetchedResultsController.sections?.count ?? 0 } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let sectionInfo = self.fetchedResultsController.sections![section] return sectionInfo.numberOfObjects } - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) self.configureCell(cell, atIndexPath: indexPath) return cell } - func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) { - let object = self.fetchedResultsController.objectAtIndexPath(indexPath) + func configureCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) { + let object = self.fetchedResultsController.object(at: indexPath) let swipeCell:BWSwipeRevealCell = cell as! BWSwipeRevealCell - swipeCell.bgViewLeftImage = UIImage(named:"Done")!.imageWithRenderingMode(.AlwaysTemplate) - swipeCell.bgViewLeftColor = UIColor.greenColor() + swipeCell.bgViewLeftImage = UIImage(named:"Done")!.withRenderingMode(.alwaysTemplate) + swipeCell.bgViewLeftColor = UIColor.green() - swipeCell.bgViewRightImage = UIImage(named:"Delete")!.imageWithRenderingMode(.AlwaysTemplate) - swipeCell.bgViewRightColor = UIColor.redColor() + swipeCell.bgViewRightImage = UIImage(named:"Delete")!.withRenderingMode(.alwaysTemplate) + swipeCell.bgViewRightColor = UIColor.red() - let type = BWSwipeCellType(rawValue: object.valueForKey("type") as! Int)! - swipeCell.type = type + let type = BWSwipeCellType(rawValue: object.value(forKey: "type") as! Int)! + swipeCell.swipeHandler.type = type switch type { - case .SwipeThrough: + case .swipeThrough: swipeCell.textLabel!.text = "Swipe Through" break - case .SpringRelease: + case .springRelease: swipeCell.textLabel!.text = "Spring Release" break - case .SlidingDoor: + case .slidingDoor: swipeCell.textLabel!.text = "Sliding Door" break } @@ -99,16 +99,16 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel // MARK: - Fetched results controller - var fetchedResultsController: NSFetchedResultsController { + var fetchedResultsController: NSFetchedResultsController { if _fetchedResultsController != nil { return _fetchedResultsController! } - let fetchRequest = NSFetchRequest() - let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!) + let fetchRequest:NSFetchRequest = NSFetchRequest() + let entity = NSEntityDescription.entity(forEntityName: "Event", in: self.managedObjectContext!) fetchRequest.entity = entity fetchRequest.fetchBatchSize = 20 - let sortDescriptor = NSSortDescriptor(key: "type", ascending: false) + let sortDescriptor = SortDescriptor(key: "type", ascending: false) fetchRequest.sortDescriptors = [sortDescriptor] let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master") aFetchedResultsController.delegate = self @@ -122,75 +122,75 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel return _fetchedResultsController! } - var _fetchedResultsController: NSFetchedResultsController? = nil + var _fetchedResultsController: NSFetchedResultsController? = nil - func controllerWillChangeContent(controller: NSFetchedResultsController) { + func controllerWillChangeContent(_ controller: NSFetchedResultsController) { self.tableView.beginUpdates() } - func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { + func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { switch type { - case .Insert: - self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) - case .Delete: - self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) + case .insert: + self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade) + case .delete: + self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade) default: return } } - func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + func controller(_ controller: NSFetchedResultsController, didChange anObject: AnyObject, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { switch type { - case .Insert: - tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) - case .Delete: - tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) - case .Update: - self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) - case .Move: - tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) - tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) + case .insert: + tableView.insertRows(at: [newIndexPath!], with: .fade) + case .delete: + tableView.deleteRows(at: [indexPath!], with: .fade) + case .update: + self.configureCell(tableView.cellForRow(at: indexPath!)!, atIndexPath: indexPath!) + case .move: + tableView.deleteRows(at: [indexPath!], with: .fade) + tableView.insertRows(at: [newIndexPath!], with: .fade) } } - func controllerDidChangeContent(controller: NSFetchedResultsController) { + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { self.tableView.endUpdates() } // MARK: - Reveal Cell Delegate - func swipeCellWillRelease(cell: BWSwipeCell) { + func swipeCellWillRelease(_ cell: BWSwipeCell) { print("Swipe Cell Will Release") - if cell.state != .Normal && cell.type != .SlidingDoor { - let indexPath: NSIndexPath = tableView.indexPathForCell(cell)! + if cell.swipeHandler.state != .normal && cell.swipeHandler.type != .slidingDoor { + let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) } } - func swipeCellActivatedAction(cell: BWSwipeCell, isActionLeft: Bool) { + func swipeCellActivatedAction(_ cell: BWSwipeCell, isActionLeft: Bool) { print("Swipe Cell Activated Action") - let indexPath: NSIndexPath = tableView.indexPathForCell(cell)! + let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) } - func swipeCellDidChangeState(cell: BWSwipeCell) { + func swipeCellDidChangeState(_ cell: BWSwipeCell) { print("Swipe Cell Did Change State") - if cell.state != .Normal { + if cell.swipeHandler.state != .normal { print("-> Cell Passed Threshold") } else { print("-> Cell Returned to Normal") } } - func swipeCellDidCompleteRelease(cell: BWSwipeCell) { + func swipeCellDidCompleteRelease(_ cell: BWSwipeCell) { print("Swipe Cell Did Complete Release") } - func swipeCellDidSwipe(cell: BWSwipeCell) { + func swipeCellDidSwipe(_ cell: BWSwipeCell) { print("Swipe Cell Did Swipe") } - func swipeCellDidStartSwiping(cell: BWSwipeCell) { + func swipeCellDidStartSwiping(_ cell: BWSwipeCell) { print("Swipe Cell Did Start Swiping") } From 3d127d93d912e5219749c180a93e7e9629846bbf Mon Sep 17 00:00:00 2001 From: kylenewsome Date: Mon, 4 Jul 2016 22:47:19 -0400 Subject: [PATCH 2/5] Removing BW prefix from all types, because module namespacing --- BWSwipeRevealCell.xcodeproj/project.pbxproj | 24 ++++----- .../{BWSwipeCell.swift => SwipeCell.swift} | 32 ++++++------ ...RevealCell.swift => SwipeRevealCell.swift} | 25 +++++----- ...ewHandler.swift => SwipeViewHandler.swift} | 49 ++++++++++--------- .../MasterViewController.swift | 18 +++---- 5 files changed, 75 insertions(+), 73 deletions(-) rename BWSwipeRevealCell/{BWSwipeCell.swift => SwipeCell.swift} (56%) rename BWSwipeRevealCell/{BWSwipeRevealCell.swift => SwipeRevealCell.swift} (90%) rename BWSwipeRevealCell/{BWSwipeableViewHandler.swift => SwipeViewHandler.swift} (85%) diff --git a/BWSwipeRevealCell.xcodeproj/project.pbxproj b/BWSwipeRevealCell.xcodeproj/project.pbxproj index 2f004e7..4f7f67f 100644 --- a/BWSwipeRevealCell.xcodeproj/project.pbxproj +++ b/BWSwipeRevealCell.xcodeproj/project.pbxproj @@ -9,8 +9,8 @@ /* Begin PBXBuildFile section */ 752EDC641BF64B5B007ED36D /* BWSwipeRevealCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 752EDC6C1BF65169007ED36D /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC6B1BF65169007ED36D /* README.md */; }; - 752EDC6F1BF654F8007ED36D /* BWSwipeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC6D1BF654F8007ED36D /* BWSwipeCell.swift */; }; - 752EDC701BF654F8007ED36D /* BWSwipeRevealCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC6E1BF654F8007ED36D /* BWSwipeRevealCell.swift */; }; + 752EDC6F1BF654F8007ED36D /* SwipeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC6D1BF654F8007ED36D /* SwipeCell.swift */; }; + 752EDC701BF654F8007ED36D /* SwipeRevealCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC6E1BF654F8007ED36D /* SwipeRevealCell.swift */; }; 752EDC781BF65880007ED36D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC771BF65880007ED36D /* AppDelegate.swift */; }; 752EDC7B1BF65880007ED36D /* BWSwipeRevealCellExample.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC791BF65880007ED36D /* BWSwipeRevealCellExample.xcdatamodeld */; }; 752EDC7D1BF65880007ED36D /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752EDC7C1BF65880007ED36D /* MasterViewController.swift */; }; @@ -18,7 +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 */; }; + 7563A67C1D09C82400A53142 /* SwipeViewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7563A67B1D09C82400A53142 /* SwipeViewHandler.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 */ @@ -52,8 +52,8 @@ 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BWSwipeRevealCell.h; sourceTree = ""; }; 752EDC651BF64B5B007ED36D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 752EDC6B1BF65169007ED36D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 752EDC6D1BF654F8007ED36D /* BWSwipeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWSwipeCell.swift; sourceTree = ""; }; - 752EDC6E1BF654F8007ED36D /* BWSwipeRevealCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWSwipeRevealCell.swift; sourceTree = ""; }; + 752EDC6D1BF654F8007ED36D /* SwipeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeCell.swift; sourceTree = ""; }; + 752EDC6E1BF654F8007ED36D /* SwipeRevealCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeRevealCell.swift; sourceTree = ""; }; 752EDC751BF65880007ED36D /* BWSwipeRevealCellExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BWSwipeRevealCellExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 752EDC771BF65880007ED36D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 752EDC7A1BF65880007ED36D /* BWSwipeRevealCellExample.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = BWSwipeRevealCellExample.xcdatamodel; sourceTree = ""; }; @@ -63,7 +63,7 @@ 752EDC881BF65880007ED36D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 752EDC8C1BF659C7007ED36D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 752EDC8E1BF65C68007ED36D /* example.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = example.gif; sourceTree = ""; }; - 7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWSwipeableViewHandler.swift; sourceTree = ""; }; + 7563A67B1D09C82400A53142 /* SwipeViewHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeViewHandler.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -108,9 +108,9 @@ 752EDC621BF64B5B007ED36D /* BWSwipeRevealCell */ = { isa = PBXGroup; children = ( - 7563A67B1D09C82400A53142 /* BWSwipeableViewHandler.swift */, - 752EDC6D1BF654F8007ED36D /* BWSwipeCell.swift */, - 752EDC6E1BF654F8007ED36D /* BWSwipeRevealCell.swift */, + 7563A67B1D09C82400A53142 /* SwipeViewHandler.swift */, + 752EDC6D1BF654F8007ED36D /* SwipeCell.swift */, + 752EDC6E1BF654F8007ED36D /* SwipeRevealCell.swift */, 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */, 752EDC651BF64B5B007ED36D /* Info.plist */, ); @@ -248,9 +248,9 @@ buildActionMask = 2147483647; files = ( 752EDC6C1BF65169007ED36D /* README.md in Sources */, - 7563A67C1D09C82400A53142 /* BWSwipeableViewHandler.swift in Sources */, - 752EDC701BF654F8007ED36D /* BWSwipeRevealCell.swift in Sources */, - 752EDC6F1BF654F8007ED36D /* BWSwipeCell.swift in Sources */, + 7563A67C1D09C82400A53142 /* SwipeViewHandler.swift in Sources */, + 752EDC701BF654F8007ED36D /* SwipeRevealCell.swift in Sources */, + 752EDC6F1BF654F8007ED36D /* SwipeCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BWSwipeRevealCell/BWSwipeCell.swift b/BWSwipeRevealCell/SwipeCell.swift similarity index 56% rename from BWSwipeRevealCell/BWSwipeCell.swift rename to BWSwipeRevealCell/SwipeCell.swift index 44a7097..7cfc2f3 100644 --- a/BWSwipeRevealCell/BWSwipeCell.swift +++ b/BWSwipeRevealCell/SwipeCell.swift @@ -9,18 +9,18 @@ import Foundation import UIKit -@objc public protocol BWSwipeCellDelegate: NSObjectProtocol { - @objc optional func swipeCellDidStartSwiping(_ cell: BWSwipeCell) - @objc optional func swipeCellDidSwipe(_ cell: BWSwipeCell) - @objc optional func swipeCellWillRelease(_ cell: BWSwipeCell) - @objc optional func swipeCellDidCompleteRelease(_ cell: BWSwipeCell) - @objc optional func swipeCellDidChangeState(_ cell: BWSwipeCell) +@objc public protocol SwipeCellDelegate: NSObjectProtocol { + @objc optional func swipeCellDidStartSwiping(_ cell: SwipeCell) + @objc optional func swipeCellDidSwipe(_ cell: SwipeCell) + @objc optional func swipeCellWillRelease(_ cell: SwipeCell) + @objc optional func swipeCellDidCompleteRelease(_ cell: SwipeCell) + @objc optional func swipeCellDidChangeState(_ cell: SwipeCell) } -public class BWSwipeCell: UITableViewCell { +public class SwipeCell: UITableViewCell { - public private(set) var swipeHandler: BWSwipeViewHandler! - public var delegate: BWSwipeCellDelegate? + public private(set) var swipeHandler: SwipeViewHandler! + public var delegate: SwipeCellDelegate? override public init(style: UITableViewCellStyle, reuseIdentifier: String?) { @@ -54,30 +54,30 @@ public class BWSwipeCell: UITableViewCell { backgroundView = bv } - swipeHandler = BWSwipeViewHandler(contentView: contentView, backgroundView: bv) + swipeHandler = SwipeViewHandler(contentView: contentView, backgroundView: bv) swipeHandler.delegate = self } } -extension BWSwipeCell: BWSwipeViewHandlerDelegate { +extension SwipeCell: SwipeViewHandlerDelegate { - public func swipeViewDidStartSwiping(_ handler: BWSwipeViewHandler) { + public func swipeViewDidStartSwiping(_ handler: SwipeViewHandler) { delegate?.swipeCellDidStartSwiping?(self) } - public func swipeViewDidSwipe(_ handler: BWSwipeViewHandler) { + public func swipeViewDidSwipe(_ handler: SwipeViewHandler) { delegate?.swipeCellDidSwipe?(self) } - public func swipeViewWillRelease(_ handler: BWSwipeViewHandler) { + public func swipeViewWillRelease(_ handler: SwipeViewHandler) { delegate?.swipeCellWillRelease?(self) } - public func swipeViewDidCompleteRelease(_ handler: BWSwipeViewHandler) { + public func swipeViewDidCompleteRelease(_ handler: SwipeViewHandler) { delegate?.swipeCellDidCompleteRelease?(self) } - public func swipeViewDidChangeState(_ handler: BWSwipeViewHandler) { + public func swipeViewDidChangeState(_ handler: SwipeViewHandler) { delegate?.swipeCellDidChangeState?(self) } diff --git a/BWSwipeRevealCell/BWSwipeRevealCell.swift b/BWSwipeRevealCell/SwipeRevealCell.swift similarity index 90% rename from BWSwipeRevealCell/BWSwipeRevealCell.swift rename to BWSwipeRevealCell/SwipeRevealCell.swift index 531ac16..9dec5c0 100644 --- a/BWSwipeRevealCell/BWSwipeRevealCell.swift +++ b/BWSwipeRevealCell/SwipeRevealCell.swift @@ -1,6 +1,6 @@ // -// BWSwipeRevealCell.swift -// BWSwipeCell +// SwipeRevealCell.swift +// SwipeCell // // Created by Kyle Newsome on 2015-11-10. // Copyright © 2015 Kyle Newsome. All rights reserved. @@ -9,11 +9,11 @@ import Foundation import UIKit -@objc public protocol BWSwipeRevealCellDelegate:BWSwipeCellDelegate { - @objc optional func swipeCellActivatedAction(_ cell: BWSwipeCell, isActionLeft: Bool) +@objc public protocol SwipeRevealCellDelegate:SwipeCellDelegate { + @objc optional func swipeCellActivatedAction(_ cell: SwipeCell, isActionLeft: Bool) } -public class BWSwipeRevealCell: BWSwipeCell { +public class SwipeRevealCell: SwipeCell { public var backViewbackgroundColor: UIColor = UIColor(white: 0.92, alpha: 1) private var _backView: UIView? @@ -38,7 +38,7 @@ public class BWSwipeRevealCell: BWSwipeCell { if _leftBackButton == nil { _leftBackButton = UIButton(frame: CGRect(x: 0, y: 0, width: self.frame.height, height: self.frame.height)) _leftBackButton!.setImage(self.bgViewLeftImage, for: UIControlState()) - _leftBackButton!.addTarget(self, action: #selector(BWSwipeRevealCell.leftButtonTapped), for: .touchUpInside) + _leftBackButton!.addTarget(self, action: #selector(SwipeRevealCell.leftButtonTapped), for: .touchUpInside) _leftBackButton!.tintColor = UIColor.white() _leftBackButton!.contentMode = .center self.backView!.addSubview(_leftBackButton!) @@ -51,7 +51,7 @@ public class BWSwipeRevealCell: BWSwipeCell { if _rightBackButton == nil { _rightBackButton = UIButton(frame: CGRect(x: self.contentView.frame.maxX, y: 0, width: self.frame.height, height: self.frame.height)) _rightBackButton!.setImage(self.bgViewRightImage, for: UIControlState()) - _rightBackButton!.addTarget(self, action: #selector(BWSwipeRevealCell.rightButtonTapped), for: .touchUpInside) + _rightBackButton!.addTarget(self, action: #selector(SwipeRevealCell.rightButtonTapped), for: .touchUpInside) _rightBackButton!.tintColor = UIColor.white() _rightBackButton!.contentMode = .center self.backView!.addSubview(_rightBackButton!) @@ -91,13 +91,13 @@ public class BWSwipeRevealCell: BWSwipeCell { super.layoutSubviews() } - override public func swipeViewDidStartSwiping(_ handler: BWSwipeViewHandler) { + override public func swipeViewDidStartSwiping(_ handler: SwipeViewHandler) { super.swipeViewDidStartSwiping(handler) self.backgroundView!.addSubview(self.backView!) } - override public func swipeViewWillRelease(_ handler: BWSwipeViewHandler) { + override public func swipeViewWillRelease(_ handler: SwipeViewHandler) { super.swipeViewWillRelease(handler) @@ -110,7 +110,7 @@ public class BWSwipeRevealCell: BWSwipeCell { } } - override public func swipeViewDidSwipe(_ handler: BWSwipeViewHandler) { + override public func swipeViewDidSwipe(_ handler: SwipeViewHandler) { super.swipeViewDidSwipe(handler) //TODO: integrate with content animation @@ -211,15 +211,14 @@ public class BWSwipeRevealCell: BWSwipeCell { public func leftButtonTapped () { self.shouldCleanUpBackView = true self.animateCellSpringRelease() - let delegate = self.delegate as? BWSwipeRevealCellDelegate + let delegate = self.delegate as? SwipeRevealCellDelegate delegate?.swipeCellActivatedAction?(self, isActionLeft: true) } public func rightButtonTapped () { self.shouldCleanUpBackView = true self.animateCellSpringRelease() - let delegate = self.delegate as? BWSwipeRevealCellDelegate + let delegate = self.delegate as? SwipeRevealCellDelegate delegate?.swipeCellActivatedAction?(self, isActionLeft: false) } - } diff --git a/BWSwipeRevealCell/BWSwipeableViewHandler.swift b/BWSwipeRevealCell/SwipeViewHandler.swift similarity index 85% rename from BWSwipeRevealCell/BWSwipeableViewHandler.swift rename to BWSwipeRevealCell/SwipeViewHandler.swift index fce9b43..9d87579 100644 --- a/BWSwipeRevealCell/BWSwipeableViewHandler.swift +++ b/BWSwipeRevealCell/SwipeViewHandler.swift @@ -2,47 +2,47 @@ import Foundation import UIKit -@objc public protocol BWSwipeViewHandlerDelegate: NSObjectProtocol { - @objc optional func swipeViewDidStartSwiping(_ handler: BWSwipeViewHandler) - @objc optional func swipeViewDidSwipe(_ handler: BWSwipeViewHandler) - @objc optional func swipeViewWillRelease(_ handler: BWSwipeViewHandler) - @objc optional func swipeViewDidCompleteRelease(_ handler: BWSwipeViewHandler) - @objc optional func swipeViewDidChangeState(_ handler: BWSwipeViewHandler) +@objc public protocol SwipeViewHandlerDelegate: NSObjectProtocol { + @objc optional func swipeViewDidStartSwiping(_ handler: SwipeViewHandler) + @objc optional func swipeViewDidSwipe(_ handler: SwipeViewHandler) + @objc optional func swipeViewWillRelease(_ handler: SwipeViewHandler) + @objc optional func swipeViewDidCompleteRelease(_ handler: SwipeViewHandler) + @objc optional func swipeViewDidChangeState(_ handler: SwipeViewHandler) } //Defines the interaction type of the table cell -public enum BWSwipeCellType: Int { +public enum InteractionType: 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 { +public enum SwipeDirection { case none case both case right case left } -public enum BWSwipeCellState { +public enum State { case normal case pastThresholdLeft case pastThresholdRight } -public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { +public class SwipeViewHandler: NSObject { public let backgroundView: UIView public let contentView: UIView // The interaction type for this table cell - public var type: BWSwipeCellType = .springRelease + public var type: InteractionType = .springRelease // The allowable swipe direction(s) - public var revealDirection: BWSwipeCellRevealDirection = .both + public var revealDirection: SwipeDirection = .both // The current state of the cell (either normal or past a threshold) - public private(set) var state: BWSwipeCellState = .normal + public private(set) var state: State = .normal // 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 = { @@ -67,9 +67,9 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { public var animationDuration: Double = 0.2 // BWSwipeCell Delegate - public weak var delegate: BWSwipeViewHandlerDelegate? + public weak var delegate: SwipeViewHandlerDelegate? - private lazy var releaseCompletionBlock:((Bool) -> Void)? = { + public lazy var releaseCompletionBlock:((Bool) -> Void)? = { return { [weak self] (finished: Bool) in @@ -91,7 +91,7 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { super.init() - let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(BWSwipeViewHandler.handlePanGesture(_:))) + let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeViewHandler.handlePanGesture(_:))) panGestureRecognizer.delegate = self contentView.addGestureRecognizer(panGestureRecognizer) } @@ -102,7 +102,7 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { self.state = .normal } - func handlePanGesture(_ panGestureRecognizer: UIPanGestureRecognizer) { + public func handlePanGesture(_ panGestureRecognizer: UIPanGestureRecognizer) { let translation: CGPoint = panGestureRecognizer.translation(in: panGestureRecognizer.view) var panOffset: CGFloat = translation.x @@ -136,7 +136,7 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { } } - func didStartSwiping() { + public func didStartSwiping() { delegate?.swipeViewDidStartSwiping?(self) } @@ -181,7 +181,7 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { // MARK: - Reset animations - func animateCellSpringRelease() { + public func animateCellSpringRelease() { UIView.animate(withDuration: self.animationDuration, delay: 0, options: .curveEaseOut, @@ -191,7 +191,7 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { completion: self.releaseCompletionBlock) } - func animateCellSlidingDoor() { + public func animateCellSlidingDoor() { UIView.animate(withDuration: self.animationDuration, delay: 0, options: .allowUserInteraction, @@ -206,7 +206,7 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { completion: self.releaseCompletionBlock) } - func animateCellSwipeThrough() { + public func animateCellSwipeThrough() { UIView.animate(withDuration: self.animationDuration, delay: 0, options: UIViewAnimationOptions.curveLinear, @@ -215,12 +215,15 @@ public class BWSwipeViewHandler: NSObject, UIGestureRecognizerDelegate { self.contentView.frame.origin.x = direction * (self.contentView.bounds.width + self.threshold) }, completion: self.releaseCompletionBlock) } - + +} + +extension SwipeViewHandler: UIGestureRecognizerDelegate { public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer && self.revealDirection != .none { let pan:UIPanGestureRecognizer = gestureRecognizer as! UIPanGestureRecognizer - let translation: CGPoint = pan.translation(in: contentView.superview) //TODO: superview reference may not be accurate + let translation: CGPoint = pan.translation(in: contentView.superview) return (fabs(translation.x) / fabs(translation.y) > 1) ? true : false } return false diff --git a/BWSwipeRevealCellExample/MasterViewController.swift b/BWSwipeRevealCellExample/MasterViewController.swift index ae32485..c426e80 100644 --- a/BWSwipeRevealCellExample/MasterViewController.swift +++ b/BWSwipeRevealCellExample/MasterViewController.swift @@ -10,7 +10,7 @@ import UIKit import CoreData import BWSwipeRevealCell -class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, BWSwipeRevealCellDelegate { +class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, SwipeRevealCellDelegate { var managedObjectContext: NSManagedObjectContext? = nil @@ -71,7 +71,7 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel func configureCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) { let object = self.fetchedResultsController.object(at: indexPath) - let swipeCell:BWSwipeRevealCell = cell as! BWSwipeRevealCell + let swipeCell:SwipeRevealCell = cell as! SwipeRevealCell swipeCell.bgViewLeftImage = UIImage(named:"Done")!.withRenderingMode(.alwaysTemplate) swipeCell.bgViewLeftColor = UIColor.green() @@ -79,7 +79,7 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel swipeCell.bgViewRightImage = UIImage(named:"Delete")!.withRenderingMode(.alwaysTemplate) swipeCell.bgViewRightColor = UIColor.red() - let type = BWSwipeCellType(rawValue: object.value(forKey: "type") as! Int)! + let type = InteractionType(rawValue: object.value(forKey: "type") as! Int)! swipeCell.swipeHandler.type = type switch type { @@ -159,7 +159,7 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel // MARK: - Reveal Cell Delegate - func swipeCellWillRelease(_ cell: BWSwipeCell) { + func swipeCellWillRelease(_ cell: SwipeCell) { print("Swipe Cell Will Release") if cell.swipeHandler.state != .normal && cell.swipeHandler.type != .slidingDoor { let indexPath: IndexPath = tableView.indexPath(for: cell)! @@ -167,13 +167,13 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel } } - func swipeCellActivatedAction(_ cell: BWSwipeCell, isActionLeft: Bool) { + func swipeCellActivatedAction(_ cell: SwipeCell, isActionLeft: Bool) { print("Swipe Cell Activated Action") let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) } - func swipeCellDidChangeState(_ cell: BWSwipeCell) { + func swipeCellDidChangeState(_ cell: SwipeCell) { print("Swipe Cell Did Change State") if cell.swipeHandler.state != .normal { print("-> Cell Passed Threshold") @@ -182,15 +182,15 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel } } - func swipeCellDidCompleteRelease(_ cell: BWSwipeCell) { + func swipeCellDidCompleteRelease(_ cell: SwipeCell) { print("Swipe Cell Did Complete Release") } - func swipeCellDidSwipe(_ cell: BWSwipeCell) { + func swipeCellDidSwipe(_ cell: SwipeCell) { print("Swipe Cell Did Swipe") } - func swipeCellDidStartSwiping(_ cell: BWSwipeCell) { + func swipeCellDidStartSwiping(_ cell: SwipeCell) { print("Swipe Cell Did Start Swiping") } From cbacca712ce7e73a059f5d34cb43b7f8ce78303b Mon Sep 17 00:00:00 2001 From: kylenewsome Date: Mon, 4 Jul 2016 23:13:45 -0400 Subject: [PATCH 3/5] More type renaming for clarity, consistency and conformity with Swift API Guidelines --- BWSwipeRevealCell/SwipeCell.swift | 16 ++++++------- BWSwipeRevealCell/SwipeRevealCell.swift | 22 +++++++++-------- BWSwipeRevealCell/SwipeViewHandler.swift | 30 ++++++++++++------------ 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/BWSwipeRevealCell/SwipeCell.swift b/BWSwipeRevealCell/SwipeCell.swift index 7cfc2f3..8fb292e 100644 --- a/BWSwipeRevealCell/SwipeCell.swift +++ b/BWSwipeRevealCell/SwipeCell.swift @@ -19,7 +19,7 @@ import UIKit public class SwipeCell: UITableViewCell { - public private(set) var swipeHandler: SwipeViewHandler! + public private(set) var swipeHandler: SwipeHandler! public var delegate: SwipeCellDelegate? override public init(style: UITableViewCellStyle, reuseIdentifier: String?) { @@ -54,30 +54,30 @@ public class SwipeCell: UITableViewCell { backgroundView = bv } - swipeHandler = SwipeViewHandler(contentView: contentView, backgroundView: bv) + swipeHandler = SwipeHandler(contentView: contentView, backgroundView: bv) swipeHandler.delegate = self } } -extension SwipeCell: SwipeViewHandlerDelegate { +extension SwipeCell: SwipeHandlerDelegate { - public func swipeViewDidStartSwiping(_ handler: SwipeViewHandler) { + public func swipeHandlerDidStartSwiping(_ handler: SwipeHandler) { delegate?.swipeCellDidStartSwiping?(self) } - public func swipeViewDidSwipe(_ handler: SwipeViewHandler) { + public func swipeHandlerDidSwipe(_ handler: SwipeHandler) { delegate?.swipeCellDidSwipe?(self) } - public func swipeViewWillRelease(_ handler: SwipeViewHandler) { + public func swipeHandlerWillRelease(_ handler: SwipeHandler) { delegate?.swipeCellWillRelease?(self) } - public func swipeViewDidCompleteRelease(_ handler: SwipeViewHandler) { + public func swipeHandlerDidCompleteRelease(_ handler: SwipeHandler) { delegate?.swipeCellDidCompleteRelease?(self) } - public func swipeViewDidChangeState(_ handler: SwipeViewHandler) { + public func swipeHandlerDidChangeState(_ handler: SwipeHandler) { delegate?.swipeCellDidChangeState?(self) } diff --git a/BWSwipeRevealCell/SwipeRevealCell.swift b/BWSwipeRevealCell/SwipeRevealCell.swift index 9dec5c0..75be10c 100644 --- a/BWSwipeRevealCell/SwipeRevealCell.swift +++ b/BWSwipeRevealCell/SwipeRevealCell.swift @@ -10,7 +10,7 @@ import Foundation import UIKit @objc public protocol SwipeRevealCellDelegate:SwipeCellDelegate { - @objc optional func swipeCellActivatedAction(_ cell: SwipeCell, isActionLeft: Bool) + @objc optional func swipeRevealCell(_ cell: SwipeCell, activatedAction isActionLeft: Bool) } public class SwipeRevealCell: SwipeCell { @@ -91,15 +91,15 @@ public class SwipeRevealCell: SwipeCell { super.layoutSubviews() } - override public func swipeViewDidStartSwiping(_ handler: SwipeViewHandler) { + override public func swipeHandlerDidStartSwiping(_ handler: SwipeHandler) { - super.swipeViewDidStartSwiping(handler) + super.swipeHandlerDidStartSwiping(handler) self.backgroundView!.addSubview(self.backView!) } - override public func swipeViewWillRelease(_ handler: SwipeViewHandler) { + override public func swipeHandlerWillRelease(_ handler: SwipeHandler) { - super.swipeViewWillRelease(handler) + super.swipeHandlerWillRelease(handler) if handler.type == .springRelease || handler.state == .normal { self.animateCellSpringRelease() @@ -110,10 +110,9 @@ public class SwipeRevealCell: SwipeCell { } } - override public func swipeViewDidSwipe(_ handler: SwipeViewHandler) { + override public func swipeHandlerDidSwipe(_ handler: SwipeHandler) { - super.swipeViewDidSwipe(handler) - //TODO: integrate with content animation + super.swipeHandlerDidSwipe(handler) let position = handler.contentView.frame.origin animateContentViewForPoint(position, progress: handler.progress) @@ -188,6 +187,7 @@ public class SwipeRevealCell: SwipeCell { self.shouldCleanUpBackView = false } + // MARK: - Reveal Cell public func getBackgroundViewImagesMaxX(_ x:CGFloat) -> CGFloat { @@ -212,13 +212,15 @@ public class SwipeRevealCell: SwipeCell { self.shouldCleanUpBackView = true self.animateCellSpringRelease() let delegate = self.delegate as? SwipeRevealCellDelegate - delegate?.swipeCellActivatedAction?(self, isActionLeft: true) + + delegate?.swipeRevealCell?(self, activatedAction: true) } public func rightButtonTapped () { self.shouldCleanUpBackView = true self.animateCellSpringRelease() let delegate = self.delegate as? SwipeRevealCellDelegate - delegate?.swipeCellActivatedAction?(self, isActionLeft: false) + + delegate?.swipeRevealCell?(self, activatedAction: false) } } diff --git a/BWSwipeRevealCell/SwipeViewHandler.swift b/BWSwipeRevealCell/SwipeViewHandler.swift index 9d87579..0ece6c8 100644 --- a/BWSwipeRevealCell/SwipeViewHandler.swift +++ b/BWSwipeRevealCell/SwipeViewHandler.swift @@ -2,12 +2,12 @@ import Foundation import UIKit -@objc public protocol SwipeViewHandlerDelegate: NSObjectProtocol { - @objc optional func swipeViewDidStartSwiping(_ handler: SwipeViewHandler) - @objc optional func swipeViewDidSwipe(_ handler: SwipeViewHandler) - @objc optional func swipeViewWillRelease(_ handler: SwipeViewHandler) - @objc optional func swipeViewDidCompleteRelease(_ handler: SwipeViewHandler) - @objc optional func swipeViewDidChangeState(_ handler: SwipeViewHandler) +@objc public protocol SwipeHandlerDelegate: NSObjectProtocol { + @objc optional func swipeHandlerDidStartSwiping(_ handler: SwipeHandler) + @objc optional func swipeHandlerDidSwipe(_ handler: SwipeHandler) + @objc optional func swipeHandlerWillRelease(_ handler: SwipeHandler) + @objc optional func swipeHandlerDidCompleteRelease(_ handler: SwipeHandler) + @objc optional func swipeHandlerDidChangeState(_ handler: SwipeHandler) } //Defines the interaction type of the table cell @@ -30,7 +30,7 @@ public enum State { case pastThresholdRight } -public class SwipeViewHandler: NSObject { +public class SwipeHandler: NSObject { public let backgroundView: UIView public let contentView: UIView @@ -67,7 +67,7 @@ public class SwipeViewHandler: NSObject { public var animationDuration: Double = 0.2 // BWSwipeCell Delegate - public weak var delegate: SwipeViewHandlerDelegate? + public weak var delegate: SwipeHandlerDelegate? public lazy var releaseCompletionBlock:((Bool) -> Void)? = { return { @@ -75,7 +75,7 @@ public class SwipeViewHandler: NSObject { guard let this = self else { return } - this.delegate?.swipeViewDidCompleteRelease?(this) + this.delegate?.swipeHandlerDidCompleteRelease?(this) this.cleanUp() } }() @@ -91,7 +91,7 @@ public class SwipeViewHandler: NSObject { super.init() - let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeViewHandler.handlePanGesture(_:))) + let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeHandler.handlePanGesture(_:))) panGestureRecognizer.delegate = self contentView.addGestureRecognizer(panGestureRecognizer) } @@ -137,7 +137,7 @@ public class SwipeViewHandler: NSObject { } public func didStartSwiping() { - delegate?.swipeViewDidStartSwiping?(self) + delegate?.swipeHandlerDidStartSwiping?(self) } public func animateContentViewForPoint(_ point: CGPoint) { @@ -155,9 +155,9 @@ public class SwipeViewHandler: NSObject { } if self.state != previousState { - delegate?.swipeViewDidChangeState?(self) + delegate?.swipeHandlerDidChangeState?(self) } - delegate?.swipeViewDidSwipe?(self) + delegate?.swipeHandlerDidSwipe?(self) } else { if (point.x > 0 && self.revealDirection == .right) || (point.x < 0 && self.revealDirection == .left) { @@ -168,7 +168,7 @@ public class SwipeViewHandler: NSObject { public func resetCellPosition() { - delegate?.swipeViewWillRelease?(self) + delegate?.swipeHandlerWillRelease?(self) if self.type == .springRelease || self.state == .normal { self.animateCellSpringRelease() @@ -218,7 +218,7 @@ public class SwipeViewHandler: NSObject { } -extension SwipeViewHandler: UIGestureRecognizerDelegate { +extension SwipeHandler: UIGestureRecognizerDelegate { public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer && self.revealDirection != .none { From 809d21c0cbdb9d9516612dc17fa55e014ced9bc9 Mon Sep 17 00:00:00 2001 From: kylenewsome Date: Mon, 4 Jul 2016 23:38:02 -0400 Subject: [PATCH 4/5] (wip): Moving handler configuration to its own type --- BWSwipeRevealCell.xcodeproj/project.pbxproj | 12 +++-- BWSwipeRevealCell/SwipeCell.swift | 2 +- ...peViewHandler.swift => SwipeHandler.swift} | 54 +++++-------------- .../SwipeHandlerConfiguration.swift | 42 +++++++++++++++ BWSwipeRevealCell/SwipeRevealCell.swift | 17 +++--- .../Base.lproj/Main.storyboard | 24 ++++----- .../MasterViewController.swift | 6 +-- 7 files changed, 89 insertions(+), 68 deletions(-) rename BWSwipeRevealCell/{SwipeViewHandler.swift => SwipeHandler.swift} (79%) create mode 100644 BWSwipeRevealCell/SwipeHandlerConfiguration.swift diff --git a/BWSwipeRevealCell.xcodeproj/project.pbxproj b/BWSwipeRevealCell.xcodeproj/project.pbxproj index 4f7f67f..cfc3d89 100644 --- a/BWSwipeRevealCell.xcodeproj/project.pbxproj +++ b/BWSwipeRevealCell.xcodeproj/project.pbxproj @@ -18,9 +18,10 @@ 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 /* SwipeViewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7563A67B1D09C82400A53142 /* SwipeViewHandler.swift */; }; + 7563A67C1D09C82400A53142 /* SwipeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7563A67B1D09C82400A53142 /* SwipeHandler.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, ); }; }; + 75C5BCF01D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,7 +64,8 @@ 752EDC881BF65880007ED36D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 752EDC8C1BF659C7007ED36D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 752EDC8E1BF65C68007ED36D /* example.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = example.gif; sourceTree = ""; }; - 7563A67B1D09C82400A53142 /* SwipeViewHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeViewHandler.swift; sourceTree = ""; }; + 7563A67B1D09C82400A53142 /* SwipeHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeHandler.swift; sourceTree = ""; }; + 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeHandlerConfiguration.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -108,7 +110,8 @@ 752EDC621BF64B5B007ED36D /* BWSwipeRevealCell */ = { isa = PBXGroup; children = ( - 7563A67B1D09C82400A53142 /* SwipeViewHandler.swift */, + 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */, + 7563A67B1D09C82400A53142 /* SwipeHandler.swift */, 752EDC6D1BF654F8007ED36D /* SwipeCell.swift */, 752EDC6E1BF654F8007ED36D /* SwipeRevealCell.swift */, 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */, @@ -248,7 +251,8 @@ buildActionMask = 2147483647; files = ( 752EDC6C1BF65169007ED36D /* README.md in Sources */, - 7563A67C1D09C82400A53142 /* SwipeViewHandler.swift in Sources */, + 7563A67C1D09C82400A53142 /* SwipeHandler.swift in Sources */, + 75C5BCF01D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift in Sources */, 752EDC701BF654F8007ED36D /* SwipeRevealCell.swift in Sources */, 752EDC6F1BF654F8007ED36D /* SwipeCell.swift in Sources */, ); diff --git a/BWSwipeRevealCell/SwipeCell.swift b/BWSwipeRevealCell/SwipeCell.swift index 8fb292e..8dd6efc 100644 --- a/BWSwipeRevealCell/SwipeCell.swift +++ b/BWSwipeRevealCell/SwipeCell.swift @@ -54,7 +54,7 @@ public class SwipeCell: UITableViewCell { backgroundView = bv } - swipeHandler = SwipeHandler(contentView: contentView, backgroundView: bv) + swipeHandler = SwipeHandler(configuration: SwipeHandlerConfiguration(), contentView: contentView, backgroundView: bv) swipeHandler.delegate = self } } diff --git a/BWSwipeRevealCell/SwipeViewHandler.swift b/BWSwipeRevealCell/SwipeHandler.swift similarity index 79% rename from BWSwipeRevealCell/SwipeViewHandler.swift rename to BWSwipeRevealCell/SwipeHandler.swift index 0ece6c8..3719540 100644 --- a/BWSwipeRevealCell/SwipeViewHandler.swift +++ b/BWSwipeRevealCell/SwipeHandler.swift @@ -10,20 +10,6 @@ import UIKit @objc optional func swipeHandlerDidChangeState(_ handler: SwipeHandler) } -//Defines the interaction type of the table cell -public enum InteractionType: 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 SwipeDirection { - case none - case both - case right - case left -} - public enum State { case normal case pastThresholdLeft @@ -35,11 +21,7 @@ public class SwipeHandler: NSObject { public let backgroundView: UIView public let contentView: UIView - // The interaction type for this table cell - public var type: InteractionType = .springRelease - - // The allowable swipe direction(s) - public var revealDirection: SwipeDirection = .both + public var config: SwipeHandlerConfiguration // The current state of the cell (either normal or past a threshold) public private(set) var state: State = .normal @@ -57,15 +39,6 @@ public class SwipeHandler: NSObject { } } - // 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: SwipeHandlerDelegate? @@ -83,11 +56,12 @@ public class SwipeHandler: NSObject { //MARK: Initialization - public init(contentView:UIView, backgroundView:UIView) { + public init(configuration:SwipeHandlerConfiguration, contentView:UIView, backgroundView:UIView) { //TODO: self.selectionStyle = .None self.contentView = contentView self.backgroundView = backgroundView + self.config = configuration super.init() @@ -107,10 +81,10 @@ public class SwipeHandler: NSObject { var panOffset: CGFloat = translation.x // If we have elasticity to consider, do some extra calculations for panOffset - if self.type != .swipeThrough && abs(translation.x) > self.threshold { - if self.shouldExceedThreshold { + if config.type != .swipeThrough && abs(translation.x) > self.threshold { + if config.shouldExceedThreshold { let offset: CGFloat = abs(translation.x) - panOffset = offset - ((offset - self.threshold) * self.panElasticityFactor) + panOffset = offset - ((offset - self.threshold) * config.panElasticityFactor) panOffset *= translation.x < 0 ? -1.0 : 1.0 } else { // If we don't allow exceeding the threshold @@ -141,7 +115,7 @@ public class SwipeHandler: NSObject { } public func animateContentViewForPoint(_ point: CGPoint) { - if (point.x > 0 && self.revealDirection == .left) || (point.x < 0 && self.revealDirection == .right) || self.revealDirection == .both { + if (point.x > 0 && config.revealDirection == .left) || (point.x < 0 && config.revealDirection == .right) || config.revealDirection == .both { self.contentView.frame = self.contentView.bounds.offsetBy(dx: point.x, dy: 0) let previousState = state if point.x >= self.threshold { @@ -160,7 +134,7 @@ public class SwipeHandler: NSObject { delegate?.swipeHandlerDidSwipe?(self) } else { - if (point.x > 0 && self.revealDirection == .right) || (point.x < 0 && self.revealDirection == .left) { + if (point.x > 0 && config.revealDirection == .right) || (point.x < 0 && config.revealDirection == .left) { self.contentView.frame = self.contentView.bounds.offsetBy(dx: 0, dy: 0) } } @@ -170,9 +144,9 @@ public class SwipeHandler: NSObject { delegate?.swipeHandlerWillRelease?(self) - if self.type == .springRelease || self.state == .normal { + if config.type == .springRelease || self.state == .normal { self.animateCellSpringRelease() - } else if self.type == .slidingDoor { + } else if config.type == .slidingDoor { self.animateCellSlidingDoor() } else { self.animateCellSwipeThrough() @@ -182,7 +156,7 @@ public class SwipeHandler: NSObject { // MARK: - Reset animations public func animateCellSpringRelease() { - UIView.animate(withDuration: self.animationDuration, + UIView.animate(withDuration: config.animationDuration, delay: 0, options: .curveEaseOut, animations: { @@ -192,7 +166,7 @@ public class SwipeHandler: NSObject { } public func animateCellSlidingDoor() { - UIView.animate(withDuration: self.animationDuration, + UIView.animate(withDuration: config.animationDuration, delay: 0, options: .allowUserInteraction, animations: { @@ -207,7 +181,7 @@ public class SwipeHandler: NSObject { } public func animateCellSwipeThrough() { - UIView.animate(withDuration: self.animationDuration, + UIView.animate(withDuration: config.animationDuration, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: { @@ -221,7 +195,7 @@ public class SwipeHandler: NSObject { extension SwipeHandler: UIGestureRecognizerDelegate { public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer is UIPanGestureRecognizer && self.revealDirection != .none { + if gestureRecognizer is UIPanGestureRecognizer && config.revealDirection != .none { let pan:UIPanGestureRecognizer = gestureRecognizer as! UIPanGestureRecognizer let translation: CGPoint = pan.translation(in: contentView.superview) return (fabs(translation.x) / fabs(translation.y) > 1) ? true : false diff --git a/BWSwipeRevealCell/SwipeHandlerConfiguration.swift b/BWSwipeRevealCell/SwipeHandlerConfiguration.swift new file mode 100644 index 0000000..ce92137 --- /dev/null +++ b/BWSwipeRevealCell/SwipeHandlerConfiguration.swift @@ -0,0 +1,42 @@ +// +// SwipeHandlerConfiguration.swift +// BWSwipeRevealCell +// +// Created by Kyle Newsome on 2016-07-04. +// Copyright © 2016 Kyle Newsome. All rights reserved. +// + +import Foundation + + +//Defines the interaction type of the table cell +public enum InteractionType: 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 SwipeDirection { + case none + case both + case right + case left +} + +public struct SwipeHandlerConfiguration { + + // The interaction type for this table cell + public var type: InteractionType = .springRelease + + // The allowable swipe direction(s) + public var revealDirection: SwipeDirection = .both + + // Should we allow the cell to be pulled past the threshold at all? (.swipeThrough interactions will ignore this) + public var shouldExceedThreshold: Bool = true + + public var panElasticityFactor: CGFloat = 0.7 + + // Length of the animation on release + public var animationDuration: Double = 0.2 + +} diff --git a/BWSwipeRevealCell/SwipeRevealCell.swift b/BWSwipeRevealCell/SwipeRevealCell.swift index 75be10c..f4d95d8 100644 --- a/BWSwipeRevealCell/SwipeRevealCell.swift +++ b/BWSwipeRevealCell/SwipeRevealCell.swift @@ -101,9 +101,9 @@ public class SwipeRevealCell: SwipeCell { super.swipeHandlerWillRelease(handler) - if handler.type == .springRelease || handler.state == .normal { + if handler.config.type == .springRelease || handler.state == .normal { self.animateCellSpringRelease() - } else if handler.type == .slidingDoor { + } else if handler.config.type == .slidingDoor { self.animateCellSlidingDoor() } else { self.animateCellSwipeThrough() @@ -155,8 +155,10 @@ public class SwipeRevealCell: SwipeCell { public func animateCellSpringRelease() { + swipeHandler.animateCellSpringRelease() + let pointX = self.contentView.frame.origin.x - UIView.animate(withDuration: swipeHandler.animationDuration, + UIView.animate(withDuration: swipeHandler.config.animationDuration, delay: 0, options: .curveLinear, animations: { @@ -170,8 +172,10 @@ public class SwipeRevealCell: SwipeCell { public func animateCellSwipeThrough() { + swipeHandler.animateCellSwipeThrough() + let pointX = self.contentView.frame.origin.x - UIView.animate(withDuration: swipeHandler.animationDuration, + UIView.animate(withDuration: swipeHandler.config.animationDuration, delay: 0, options: .curveLinear, animations: { @@ -193,14 +197,14 @@ public class SwipeRevealCell: SwipeCell { public func getBackgroundViewImagesMaxX(_ x:CGFloat) -> CGFloat { if x > 0 { let frame = self.leftBackButton!.frame - if swipeHandler.type == .swipeThrough { + if swipeHandler.config.type == .swipeThrough { return self.contentView.frame.origin.x - frame.width } else { return min(self.contentView.frame.minX - frame.width, 0) } } else { let frame = self.rightBackButton!.frame - if swipeHandler.type == .swipeThrough { + if swipeHandler.config.type == .swipeThrough { return self.contentView.frame.maxX } else { return max(self.frame.maxX - frame.width, self.contentView.frame.maxX) @@ -211,6 +215,7 @@ public class SwipeRevealCell: SwipeCell { public func leftButtonTapped () { self.shouldCleanUpBackView = true self.animateCellSpringRelease() + let delegate = self.delegate as? SwipeRevealCellDelegate delegate?.swipeRevealCell?(self, activatedAction: true) diff --git a/BWSwipeRevealCellExample/Base.lproj/Main.storyboard b/BWSwipeRevealCellExample/Base.lproj/Main.storyboard index bbbd5f7..11f35d6 100644 --- a/BWSwipeRevealCellExample/Base.lproj/Main.storyboard +++ b/BWSwipeRevealCellExample/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -11,7 +12,6 @@ - @@ -26,31 +26,27 @@ - + - - + - - + + - + - - - + diff --git a/BWSwipeRevealCellExample/MasterViewController.swift b/BWSwipeRevealCellExample/MasterViewController.swift index c426e80..eb86953 100644 --- a/BWSwipeRevealCellExample/MasterViewController.swift +++ b/BWSwipeRevealCellExample/MasterViewController.swift @@ -80,7 +80,7 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel swipeCell.bgViewRightColor = UIColor.red() let type = InteractionType(rawValue: object.value(forKey: "type") as! Int)! - swipeCell.swipeHandler.type = type + swipeCell.swipeHandler.config.type = type switch type { case .swipeThrough: @@ -161,13 +161,13 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel func swipeCellWillRelease(_ cell: SwipeCell) { print("Swipe Cell Will Release") - if cell.swipeHandler.state != .normal && cell.swipeHandler.type != .slidingDoor { + if cell.swipeHandler.state != .normal && cell.swipeHandler.config.type != .slidingDoor { let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) } } - func swipeCellActivatedAction(_ cell: SwipeCell, isActionLeft: Bool) { + func swipeRevealCell(_ cell: SwipeCell, activatedAction isActionLeft: Bool) { print("Swipe Cell Activated Action") let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) From fa4ced7a66d6683691ae091bd6b6e4369894ab61 Mon Sep 17 00:00:00 2001 From: kylenewsome Date: Wed, 6 Jul 2016 20:37:06 -0400 Subject: [PATCH 5/5] Introduce configuration struct which also encapsulates interaction types and lends itself to easy customization --- BWSwipeRevealCell.xcodeproj/project.pbxproj | 28 +++- BWSwipeRevealCell/SwipeCell.swift | 2 +- BWSwipeRevealCell/SwipeHandler.swift | 99 +++++-------- BWSwipeRevealCell/SwipeHandlerAnimation.swift | 25 ++++ .../SwipeHandlerConfiguration.swift | 131 ++++++++++++++++-- BWSwipeRevealCell/SwipeRevealCell.swift | 63 +++------ .../MasterViewController.swift | 28 ++-- 7 files changed, 238 insertions(+), 138 deletions(-) create mode 100644 BWSwipeRevealCell/SwipeHandlerAnimation.swift diff --git a/BWSwipeRevealCell.xcodeproj/project.pbxproj b/BWSwipeRevealCell.xcodeproj/project.pbxproj index cfc3d89..4f29ea3 100644 --- a/BWSwipeRevealCell.xcodeproj/project.pbxproj +++ b/BWSwipeRevealCell.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 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, ); }; }; 75C5BCF01D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */; }; + 75C5BCF21D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C5BCF11D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -66,6 +67,7 @@ 752EDC8E1BF65C68007ED36D /* example.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = example.gif; sourceTree = ""; }; 7563A67B1D09C82400A53142 /* SwipeHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeHandler.swift; sourceTree = ""; }; 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeHandlerConfiguration.swift; sourceTree = ""; }; + 75C5BCF11D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeHandlerAnimation.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -110,10 +112,8 @@ 752EDC621BF64B5B007ED36D /* BWSwipeRevealCell */ = { isa = PBXGroup; children = ( - 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */, - 7563A67B1D09C82400A53142 /* SwipeHandler.swift */, - 752EDC6D1BF654F8007ED36D /* SwipeCell.swift */, - 752EDC6E1BF654F8007ED36D /* SwipeRevealCell.swift */, + 75C5BCF31D2B639100F6E0C6 /* Swipe Handler */, + 75C5BCF41D2B639F00F6E0C6 /* Swipe TableView Cells */, 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */, 752EDC651BF64B5B007ED36D /* Info.plist */, ); @@ -134,6 +134,25 @@ path = BWSwipeRevealCellExample; sourceTree = ""; }; + 75C5BCF31D2B639100F6E0C6 /* Swipe Handler */ = { + isa = PBXGroup; + children = ( + 75C5BCF11D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift */, + 75C5BCEF1D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift */, + 7563A67B1D09C82400A53142 /* SwipeHandler.swift */, + ); + name = "Swipe Handler"; + sourceTree = ""; + }; + 75C5BCF41D2B639F00F6E0C6 /* Swipe TableView Cells */ = { + isa = PBXGroup; + children = ( + 752EDC6D1BF654F8007ED36D /* SwipeCell.swift */, + 752EDC6E1BF654F8007ED36D /* SwipeRevealCell.swift */, + ); + name = "Swipe TableView Cells"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -252,6 +271,7 @@ files = ( 752EDC6C1BF65169007ED36D /* README.md in Sources */, 7563A67C1D09C82400A53142 /* SwipeHandler.swift in Sources */, + 75C5BCF21D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift in Sources */, 75C5BCF01D2B5E1F00F6E0C6 /* SwipeHandlerConfiguration.swift in Sources */, 752EDC701BF654F8007ED36D /* SwipeRevealCell.swift in Sources */, 752EDC6F1BF654F8007ED36D /* SwipeCell.swift in Sources */, diff --git a/BWSwipeRevealCell/SwipeCell.swift b/BWSwipeRevealCell/SwipeCell.swift index 8dd6efc..8fb292e 100644 --- a/BWSwipeRevealCell/SwipeCell.swift +++ b/BWSwipeRevealCell/SwipeCell.swift @@ -54,7 +54,7 @@ public class SwipeCell: UITableViewCell { backgroundView = bv } - swipeHandler = SwipeHandler(configuration: SwipeHandlerConfiguration(), contentView: contentView, backgroundView: bv) + swipeHandler = SwipeHandler(contentView: contentView, backgroundView: bv) swipeHandler.delegate = self } } diff --git a/BWSwipeRevealCell/SwipeHandler.swift b/BWSwipeRevealCell/SwipeHandler.swift index 3719540..9e9e52f 100644 --- a/BWSwipeRevealCell/SwipeHandler.swift +++ b/BWSwipeRevealCell/SwipeHandler.swift @@ -21,7 +21,7 @@ public class SwipeHandler: NSObject { public let backgroundView: UIView public let contentView: UIView - public var config: SwipeHandlerConfiguration + public var config: SwipeHandlerConfiguration = .springRelease() // The current state of the cell (either normal or past a threshold) public private(set) var state: State = .normal @@ -42,26 +42,13 @@ public class SwipeHandler: NSObject { // BWSwipeCell Delegate public weak var delegate: SwipeHandlerDelegate? - public lazy var releaseCompletionBlock:((Bool) -> Void)? = { - return { - [weak self] (finished: Bool) in - - guard let this = self else { return } - - this.delegate?.swipeHandlerDidCompleteRelease?(this) - this.cleanUp() - } - }() - - //MARK: Initialization - public init(configuration:SwipeHandlerConfiguration, contentView:UIView, backgroundView:UIView) { + public init(contentView:UIView, backgroundView:UIView) { //TODO: self.selectionStyle = .None self.contentView = contentView self.backgroundView = backgroundView - self.config = configuration super.init() @@ -81,7 +68,10 @@ public class SwipeHandler: NSObject { var panOffset: CGFloat = translation.x // If we have elasticity to consider, do some extra calculations for panOffset - if config.type != .swipeThrough && abs(translation.x) > self.threshold { + if abs(translation.x) > self.threshold { + + + if config.shouldExceedThreshold { let offset: CGFloat = abs(translation.x) panOffset = offset - ((offset - self.threshold) * config.panElasticityFactor) @@ -105,7 +95,7 @@ public class SwipeHandler: NSObject { self.animateContentViewForPoint(actualTranslation) } else { - self.resetCellPosition() + self.resetCellPosition(withForce: false) } } } @@ -115,7 +105,11 @@ public class SwipeHandler: NSObject { } public func animateContentViewForPoint(_ point: CGPoint) { - if (point.x > 0 && config.revealDirection == .left) || (point.x < 0 && config.revealDirection == .right) || config.revealDirection == .both { + + if config.revealDirection == .both // if both directions are allowed + || (point.x > 0 && config.revealDirection == .left) // OR if we are revealing left and it's allowed + || (point.x < 0 && config.revealDirection == .right) { // OR "" right and it's allowed + self.contentView.frame = self.contentView.bounds.offsetBy(dx: point.x, dy: 0) let previousState = state if point.x >= self.threshold { @@ -140,54 +134,36 @@ public class SwipeHandler: NSObject { } } - public func resetCellPosition() { - - delegate?.swipeHandlerWillRelease?(self) + public func resetCellPosition(withForce forced: Bool) { - if config.type == .springRelease || self.state == .normal { - self.animateCellSpringRelease() - } else if config.type == .slidingDoor { - self.animateCellSlidingDoor() + if forced { + state = .normal } else { - self.animateCellSwipeThrough() - } - } - // MARK: - Reset animations - - public func animateCellSpringRelease() { - UIView.animate(withDuration: config.animationDuration, - delay: 0, - options: .curveEaseOut, - animations: { - self.contentView.frame = self.contentView.bounds - }, - completion: self.releaseCompletionBlock) - } - - public func animateCellSlidingDoor() { - UIView.animate(withDuration: config.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) + delegate?.swipeHandlerWillRelease?(self) + } + + animatePositionReset(withForce: forced) } - public func animateCellSwipeThrough() { - UIView.animate(withDuration: config.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 animatePositionReset(withForce forced: Bool) { + + guard let animation = config.animation else { + //TODO: Perform unanimated reset + return + } + + animation.resetAnimationBlock(self) { + [weak self] _ in + + guard forced == false, + let this = self else { + return + } + + this.delegate?.swipeHandlerDidCompleteRelease?(this) + this.cleanUp() + } } } @@ -195,6 +171,7 @@ public class SwipeHandler: NSObject { extension SwipeHandler: UIGestureRecognizerDelegate { public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer is UIPanGestureRecognizer && config.revealDirection != .none { let pan:UIPanGestureRecognizer = gestureRecognizer as! UIPanGestureRecognizer let translation: CGPoint = pan.translation(in: contentView.superview) diff --git a/BWSwipeRevealCell/SwipeHandlerAnimation.swift b/BWSwipeRevealCell/SwipeHandlerAnimation.swift new file mode 100644 index 0000000..e02cc00 --- /dev/null +++ b/BWSwipeRevealCell/SwipeHandlerAnimation.swift @@ -0,0 +1,25 @@ +// +// SwipeHandlerAnimation.swift +// BWSwipeRevealCell +// +// Created by Kyle Newsome on 2016-07-04. +// Copyright © 2016 Kyle Newsome. All rights reserved. +// + +import Foundation + +public struct SwipeHandlerAnimation { + + public typealias AnimationCompletionCallback = (Bool) -> Void + public typealias AnimationBlock = (SwipeHandler, AnimationCompletionCallback) -> Void + + public var duration: Double = 0.2 + + public var resetAnimationBlock: AnimationBlock + + public init(resetAnimationBlock: AnimationBlock) { + + self.resetAnimationBlock = resetAnimationBlock + } + +} diff --git a/BWSwipeRevealCell/SwipeHandlerConfiguration.swift b/BWSwipeRevealCell/SwipeHandlerConfiguration.swift index ce92137..a12cd7d 100644 --- a/BWSwipeRevealCell/SwipeHandlerConfiguration.swift +++ b/BWSwipeRevealCell/SwipeHandlerConfiguration.swift @@ -8,14 +8,6 @@ import Foundation - -//Defines the interaction type of the table cell -public enum InteractionType: 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 SwipeDirection { case none case both @@ -25,18 +17,127 @@ public enum SwipeDirection { public struct SwipeHandlerConfiguration { - // The interaction type for this table cell - public var type: InteractionType = .springRelease + public let name:String // The allowable swipe direction(s) public var revealDirection: SwipeDirection = .both - - // Should we allow the cell to be pulled past the threshold at all? (.swipeThrough interactions will ignore this) public var shouldExceedThreshold: Bool = true - public var panElasticityFactor: CGFloat = 0.7 + + public var animation: SwipeHandlerAnimation? = nil - // Length of the animation on release - public var animationDuration: Double = 0.2 + public init(named configName:String) { + + self.name = configName + } } + +/// SwipeHandlerConfiguration+Defaults +/// +/// + +extension SwipeHandlerConfiguration { + + /// + /// Spring release is the default configuration for the swipe handler. + /// It will always return the swipeHandler's contentView to `frame.origin.x = 0` on release. + /// + public static func springRelease() -> SwipeHandlerConfiguration { + + var config = SwipeHandlerConfiguration(named: "SpringRelease") + + config.animation = SwipeHandlerAnimation { + swipeHandler, doneBlock in + + let contentView = swipeHandler.contentView + + UIView.animate(withDuration: 0.2, + delay: 0, + options: .curveEaseOut, + animations: { + contentView.frame = contentView.bounds + }, + completion: doneBlock) + } + + return config + } + + /// + /// This configuration will animate the swipeHandler's contentView + /// to the threshold point on release if swipeHandler.state is past threshold. + /// + public static func slidingDoor() -> SwipeHandlerConfiguration { + + var config = SwipeHandlerConfiguration(named: "SlidingDoor") + + config.animation = SwipeHandlerAnimation { + swipeHandler, doneBlock in + + let contentView = swipeHandler.contentView + let isPastThreshold = swipeHandler.state != .normal + + let xDestination: CGFloat + if isPastThreshold { + + let xOrigin = contentView.frame.origin.x + let direction:CGFloat = (xOrigin > 0) ? 1 : -1 + xDestination = direction * swipeHandler.threshold + + } else { + + xDestination = 0 + } + + UIView.animate(withDuration: 0.2, + delay: 0, + options: .curveEaseOut, + animations: { + contentView.frame.origin.x = xDestination + }, + completion: doneBlock) + } + + return config + } + + /// + /// This configuration will animate the swipeHandler's contentView + /// entirely outside its bounds on release if swipeHandler.state is past threshold. + /// + public static func swipeThrough() -> SwipeHandlerConfiguration { + + var config = SwipeHandlerConfiguration(named: "SwipeThrough") + config.panElasticityFactor = 0 + + config.animation = SwipeHandlerAnimation { + swipeHandler, doneBlock in + + let contentView = swipeHandler.contentView + let isPastThreshold = swipeHandler.state != .normal + + let xDestination: CGFloat + if isPastThreshold { + + let xOrigin = contentView.frame.origin.x + let direction:CGFloat = (xOrigin > 0) ? 1 : -1 + xDestination = direction * (contentView.bounds.width + abs(xOrigin)) + } else { + + xDestination = 0 + } + + UIView.animate(withDuration: 0.2, + delay: 0, + options: UIViewAnimationOptions.curveLinear, + animations: { + contentView.frame.origin.x = xDestination + }, completion: doneBlock) + + } + + return config + } + +} diff --git a/BWSwipeRevealCell/SwipeRevealCell.swift b/BWSwipeRevealCell/SwipeRevealCell.swift index f4d95d8..9251a33 100644 --- a/BWSwipeRevealCell/SwipeRevealCell.swift +++ b/BWSwipeRevealCell/SwipeRevealCell.swift @@ -101,13 +101,7 @@ public class SwipeRevealCell: SwipeCell { super.swipeHandlerWillRelease(handler) - if handler.config.type == .springRelease || handler.state == .normal { - self.animateCellSpringRelease() - } else if handler.config.type == .slidingDoor { - self.animateCellSlidingDoor() - } else { - self.animateCellSwipeThrough() - } + self.animateRelease() } override public func swipeHandlerDidSwipe(_ handler: SwipeHandler) { @@ -153,12 +147,16 @@ public class SwipeRevealCell: SwipeCell { // MARK: - Reveal Cell Animations - public func animateCellSpringRelease() { - - swipeHandler.animateCellSpringRelease() + public func animateRelease() { + + guard swipeHandler.state == .normal else { + + return + } let pointX = self.contentView.frame.origin.x - UIView.animate(withDuration: swipeHandler.config.animationDuration, + + UIView.animate(withDuration: 0.2, delay: 0, options: .curveLinear, animations: { @@ -170,51 +168,24 @@ public class SwipeRevealCell: SwipeCell { }, completion: nil) } - public func animateCellSwipeThrough() { - - swipeHandler.animateCellSwipeThrough() - - let pointX = self.contentView.frame.origin.x - UIView.animate(withDuration: swipeHandler.config.animationDuration, - delay: 0, - options: .curveLinear, - animations: { - if pointX > 0 { - self.leftBackButton!.frame.origin.x = self.frame.maxX - } else if pointX < 0 { - self.rightBackButton!.frame.origin.x = -self.swipeHandler.threshold - } - }, completion: nil) - } - - public func animateCellSlidingDoor() { - self.shouldCleanUpBackView = false - } - - // MARK: - Reveal Cell public func getBackgroundViewImagesMaxX(_ x:CGFloat) -> CGFloat { if x > 0 { + let frame = self.leftBackButton!.frame - if swipeHandler.config.type == .swipeThrough { - return self.contentView.frame.origin.x - frame.width - } else { - return min(self.contentView.frame.minX - frame.width, 0) - } + return min(self.contentView.frame.minX - frame.width, 0) } else { + let frame = self.rightBackButton!.frame - if swipeHandler.config.type == .swipeThrough { - return self.contentView.frame.maxX - } else { - return max(self.frame.maxX - frame.width, self.contentView.frame.maxX) - } + return max(self.frame.maxX - frame.width, self.contentView.frame.maxX) } } public func leftButtonTapped () { self.shouldCleanUpBackView = true - self.animateCellSpringRelease() + swipeHandler.resetCellPosition(withForce: true) + self.animateRelease() let delegate = self.delegate as? SwipeRevealCellDelegate @@ -223,7 +194,9 @@ public class SwipeRevealCell: SwipeCell { public func rightButtonTapped () { self.shouldCleanUpBackView = true - self.animateCellSpringRelease() + swipeHandler.resetCellPosition(withForce: true) + self.animateRelease() + let delegate = self.delegate as? SwipeRevealCellDelegate delegate?.swipeRevealCell?(self, activatedAction: false) diff --git a/BWSwipeRevealCellExample/MasterViewController.swift b/BWSwipeRevealCellExample/MasterViewController.swift index eb86953..09645c3 100644 --- a/BWSwipeRevealCellExample/MasterViewController.swift +++ b/BWSwipeRevealCellExample/MasterViewController.swift @@ -79,21 +79,25 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel swipeCell.bgViewRightImage = UIImage(named:"Delete")!.withRenderingMode(.alwaysTemplate) swipeCell.bgViewRightColor = UIColor.red() - let type = InteractionType(rawValue: object.value(forKey: "type") as! Int)! - swipeCell.swipeHandler.config.type = type + let type = object.value(forKey: "type") as! Int + let swipeConfig: SwipeHandlerConfiguration switch type { - case .swipeThrough: - swipeCell.textLabel!.text = "Swipe Through" - break - case .springRelease: - swipeCell.textLabel!.text = "Spring Release" - break - case .slidingDoor: - swipeCell.textLabel!.text = "Sliding Door" - break + case 0: + swipeCell.textLabel?.text = "Swipe Through" + swipeConfig = .swipeThrough() + case 1: + swipeCell.textLabel?.text = "Spring Release" + swipeConfig = .springRelease() + case 2: + swipeCell.textLabel?.text = "Sliding Door" + swipeConfig = .slidingDoor() + default: + return + } + swipeCell.swipeHandler.config = swipeConfig swipeCell.delegate = self } @@ -161,7 +165,7 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel func swipeCellWillRelease(_ cell: SwipeCell) { print("Swipe Cell Will Release") - if cell.swipeHandler.state != .normal && cell.swipeHandler.config.type != .slidingDoor { + if cell.swipeHandler.state != .normal && cell.swipeHandler.config.name != "SlidingDoor" { let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) }