diff --git a/BWSwipeRevealCell.xcodeproj/project.pbxproj b/BWSwipeRevealCell.xcodeproj/project.pbxproj index 7c44f4a..4f29ea3 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,8 +18,11 @@ 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 /* 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 */; }; + 75C5BCF21D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C5BCF11D2B638B00F6E0C6 /* SwipeHandlerAnimation.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,8 +54,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 = ""; }; @@ -62,6 +65,9 @@ 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 /* 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 */ @@ -106,8 +112,8 @@ 752EDC621BF64B5B007ED36D /* BWSwipeRevealCell */ = { isa = PBXGroup; children = ( - 752EDC6D1BF654F8007ED36D /* BWSwipeCell.swift */, - 752EDC6E1BF654F8007ED36D /* BWSwipeRevealCell.swift */, + 75C5BCF31D2B639100F6E0C6 /* Swipe Handler */, + 75C5BCF41D2B639F00F6E0C6 /* Swipe TableView Cells */, 752EDC631BF64B5B007ED36D /* BWSwipeRevealCell.h */, 752EDC651BF64B5B007ED36D /* Info.plist */, ); @@ -128,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 */ @@ -191,9 +216,11 @@ TargetAttributes = { 752EDC5F1BF64B5B007ED36D = { CreatedOnToolsVersion = 7.1; + LastSwiftMigration = 0800; }; 752EDC741BF65880007ED36D = { CreatedOnToolsVersion = 7.1; + LastSwiftMigration = 0800; }; }; }; @@ -243,8 +270,11 @@ buildActionMask = 2147483647; files = ( 752EDC6C1BF65169007ED36D /* README.md in Sources */, - 752EDC701BF654F8007ED36D /* BWSwipeRevealCell.swift in Sources */, - 752EDC6F1BF654F8007ED36D /* BWSwipeCell.swift 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 */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -390,6 +420,7 @@ PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.BWSwipeRevealCell; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -406,6 +437,7 @@ PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.BWSwipeRevealCell; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -418,6 +450,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 +463,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 deleted file mode 100644 index 0c8a961..0000000 --- a/BWSwipeRevealCell/BWSwipeCell.swift +++ /dev/null @@ -1,246 +0,0 @@ -// -// BWSwipeCell.swift -// BWSwipeCell -// -// Created by Kyle Newsome on 2015-10-20. -// Copyright © 2015 Kyle Newsome. All rights reserved. -// - -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) -} - -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 - - // 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.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 - } - } - - // 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) - - let backgroundView: UIView = UIView(frame: self.frame) - backgroundView.backgroundColor = UIColor.whiteColor() - self.backgroundView = backgroundView - } - - public func cleanUp() { - self.state = .Normal - } - - public func handlePanGesture(panGestureRecognizer: UIPanGestureRecognizer) { - let translation: CGPoint = panGestureRecognizer.translationInView(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 = 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() - } else { - self.animateCellSwipeThrough() - } - } - - // 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) - } - - // MARK: - UITableViewCell Overrides - - public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - self.initialize() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - self.initialize() - } - - public override func prepareForReuse() { - super.prepareForReuse() - self.cleanUp() - } - - public override func setSelected(selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - } - - 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 deleted file mode 100644 index 28677c3..0000000 --- a/BWSwipeRevealCell/BWSwipeRevealCell.swift +++ /dev/null @@ -1,202 +0,0 @@ -// -// BWSwipeRevealCell.swift -// BWSwipeCell -// -// Created by Kyle Newsome on 2015-11-10. -// Copyright © 2015 Kyle Newsome. All rights reserved. -// - -import Foundation -import UIKit - -@objc public protocol BWSwipeRevealCellDelegate:BWSwipeCellDelegate { - optional func swipeCellActivatedAction(cell: BWSwipeCell, isActionLeft: Bool) -} - -public class BWSwipeRevealCell: BWSwipeCell { - - public var backViewbackgroundColor: UIColor = UIColor(white: 0.92, alpha: 1) - private var _backView: UIView? - public var backView: UIView? { - if _backView == nil { - _backView = UIView(frame: self.contentView.bounds) - _backView!.backgroundColor = self.backViewbackgroundColor - } - return _backView - } - public var shouldCleanUpBackView = true - - public var bgViewInactiveColor: UIColor = UIColor.grayColor() - 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) - - public var bgViewLeftImage: UIImage? - public var bgViewRightImage: UIImage? - - 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 - self.backView!.addSubview(_leftBackButton!) - } - return _leftBackButton - } - - 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 - self.backView!.addSubview(_rightBackButton!) - } - return _rightBackButton - } - - override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - 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 - _rightBackButton?.removeFromSuperview() - _rightBackButton = nil - _backView?.removeFromSuperview() - _backView = nil - } - } - - public override func didStartSwiping() { - super.didStartSwiping() - self.backgroundView!.addSubview(self.backView!) - } - - public override func animateContentViewForPoint(point: CGPoint) { - super.animateContentViewForPoint(point) - 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) { - self.backView?.backgroundColor = self.bgViewLeftColor - } - else { - self.backView?.backgroundColor = self.bgViewInactiveColor - } - }, completion: nil) - } 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) { - self.backView?.backgroundColor = self.bgViewRightColor - } else { - self.backView?.backgroundColor = self.bgViewInactiveColor - } - }, completion: nil) - } - } - - // MARK: - Reveal Cell Animations - - public override func animateCellSpringRelease() { - super.animateCellSpringRelease() - let pointX = self.contentView.frame.origin.x - UIView.animateWithDuration(self.animationDuration, - delay: 0, - options: .CurveLinear, - animations: { - if pointX > 0 { - self.leftBackButton!.frame.origin.x = -self.threshold - } else if pointX < 0 { - self.rightBackButton!.frame.origin.x = CGRectGetMaxX(self.frame) - } - }, completion: nil) - } - - public override func animateCellSwipeThrough() { - super.animateCellSwipeThrough() - let pointX = self.contentView.frame.origin.x - UIView.animateWithDuration(self.animationDuration, - delay: 0, - options: .CurveLinear, - animations: { - if pointX > 0 { - self.leftBackButton!.frame.origin.x = CGRectGetMaxX(self.frame) - } else if pointX < 0 { - self.rightBackButton!.frame.origin.x = -self.threshold - } - }, completion: nil) - } - - public override func animateCellSlidingDoor() { - super.animateCellSlidingDoor() - self.shouldCleanUpBackView = false - } - - // MARK: - Reveal Cell - - public func getBackgroundViewImagesMaxX(x:CGFloat) -> CGFloat { - if x > 0 { - let frame = self.leftBackButton!.frame - if self.type == .SwipeThrough { - return self.contentView.frame.origin.x - frame.width - } else { - return min(CGRectGetMinX(self.contentView.frame) - CGRectGetWidth(frame), 0) - } - } else { - let frame = self.rightBackButton!.frame - if self.type == .SwipeThrough { - return CGRectGetMaxX(self.contentView.frame) - } else { - return max(CGRectGetMaxX(self.frame) - CGRectGetWidth(frame), CGRectGetMaxX(self.contentView.frame)) - } - } - } - - public func leftButtonTapped () { - self.shouldCleanUpBackView = true - self.animateCellSpringRelease() - let delegate = self.delegate as? BWSwipeRevealCellDelegate - delegate?.swipeCellActivatedAction?(self, isActionLeft: true) - } - - public func rightButtonTapped () { - self.shouldCleanUpBackView = true - self.animateCellSpringRelease() - let delegate = self.delegate as? BWSwipeRevealCellDelegate - delegate?.swipeCellActivatedAction?(self, isActionLeft: false) - } - -} \ No newline at end of file diff --git a/BWSwipeRevealCell/SwipeCell.swift b/BWSwipeRevealCell/SwipeCell.swift new file mode 100644 index 0000000..8fb292e --- /dev/null +++ b/BWSwipeRevealCell/SwipeCell.swift @@ -0,0 +1,84 @@ +// +// BWSwipeCell.swift +// BWSwipeCell +// +// Created by Kyle Newsome on 2015-10-20. +// Copyright © 2015 Kyle Newsome. All rights reserved. +// + +import Foundation +import UIKit + +@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 SwipeCell: UITableViewCell { + + public private(set) var swipeHandler: SwipeHandler! + public var delegate: SwipeCellDelegate? + + override public init(style: UITableViewCellStyle, reuseIdentifier: String?) { + + super.init(style: style, reuseIdentifier: reuseIdentifier) + + self.initialize() + } + + required public init?(coder aDecoder: NSCoder) { + + super.init(coder: aDecoder) + self.initialize() + } + + override public func prepareForReuse() { + + super.prepareForReuse() + swipeHandler.cleanUp() + } + + public func initialize() { + + contentView.backgroundColor = UIColor.white() + + let bv:UIView + if let b = backgroundView { + bv = b + } else { + bv = UIView(frame: self.frame) + bv.backgroundColor = UIColor.white() + backgroundView = bv + } + + swipeHandler = SwipeHandler(contentView: contentView, backgroundView: bv) + swipeHandler.delegate = self + } +} + +extension SwipeCell: SwipeHandlerDelegate { + + public func swipeHandlerDidStartSwiping(_ handler: SwipeHandler) { + delegate?.swipeCellDidStartSwiping?(self) + } + + public func swipeHandlerDidSwipe(_ handler: SwipeHandler) { + delegate?.swipeCellDidSwipe?(self) + } + + public func swipeHandlerWillRelease(_ handler: SwipeHandler) { + delegate?.swipeCellWillRelease?(self) + } + + public func swipeHandlerDidCompleteRelease(_ handler: SwipeHandler) { + delegate?.swipeCellDidCompleteRelease?(self) + } + + public func swipeHandlerDidChangeState(_ handler: SwipeHandler) { + delegate?.swipeCellDidChangeState?(self) + } + +} diff --git a/BWSwipeRevealCell/SwipeHandler.swift b/BWSwipeRevealCell/SwipeHandler.swift new file mode 100644 index 0000000..9e9e52f --- /dev/null +++ b/BWSwipeRevealCell/SwipeHandler.swift @@ -0,0 +1,182 @@ +import Foundation +import UIKit + + +@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) +} + +public enum State { + case normal + case pastThresholdLeft + case pastThresholdRight +} + +public class SwipeHandler: NSObject { + + public let backgroundView: UIView + public let contentView: UIView + + public var config: SwipeHandlerConfiguration = .springRelease() + + // The current state of the cell (either normal or past a threshold) + 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 = { + 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 + } + } + + // BWSwipeCell Delegate + public weak var delegate: SwipeHandlerDelegate? + + //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(SwipeHandler.handlePanGesture(_:))) + panGestureRecognizer.delegate = self + contentView.addGestureRecognizer(panGestureRecognizer) + } + + // MARK: - Swipe Cell Functions + + public func cleanUp() { + self.state = .normal + } + + public 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 abs(translation.x) > self.threshold { + + + + if config.shouldExceedThreshold { + let offset: CGFloat = abs(translation.x) + panOffset = offset - ((offset - self.threshold) * config.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(withForce: false) + } + } + } + + public func didStartSwiping() { + delegate?.swipeHandlerDidStartSwiping?(self) + } + + public func animateContentViewForPoint(_ point: CGPoint) { + + 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 { + self.state = .pastThresholdLeft + } + else if point.x < -self.threshold { + self.state = .pastThresholdRight + } + else { + self.state = .normal + } + + if self.state != previousState { + delegate?.swipeHandlerDidChangeState?(self) + } + delegate?.swipeHandlerDidSwipe?(self) + } + else { + if (point.x > 0 && config.revealDirection == .right) || (point.x < 0 && config.revealDirection == .left) { + self.contentView.frame = self.contentView.bounds.offsetBy(dx: 0, dy: 0) + } + } + } + + public func resetCellPosition(withForce forced: Bool) { + + if forced { + state = .normal + } else { + + delegate?.swipeHandlerWillRelease?(self) + } + + animatePositionReset(withForce: forced) + } + + 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() + } + } + +} + +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) + return (fabs(translation.x) / fabs(translation.y) > 1) ? true : false + } + return false + } +} 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 new file mode 100644 index 0000000..a12cd7d --- /dev/null +++ b/BWSwipeRevealCell/SwipeHandlerConfiguration.swift @@ -0,0 +1,143 @@ +// +// SwipeHandlerConfiguration.swift +// BWSwipeRevealCell +// +// Created by Kyle Newsome on 2016-07-04. +// Copyright © 2016 Kyle Newsome. All rights reserved. +// + +import Foundation + +public enum SwipeDirection { + case none + case both + case right + case left +} + +public struct SwipeHandlerConfiguration { + + public let name:String + + // The allowable swipe direction(s) + public var revealDirection: SwipeDirection = .both + public var shouldExceedThreshold: Bool = true + public var panElasticityFactor: CGFloat = 0.7 + + public var animation: SwipeHandlerAnimation? = nil + + 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 new file mode 100644 index 0000000..9251a33 --- /dev/null +++ b/BWSwipeRevealCell/SwipeRevealCell.swift @@ -0,0 +1,204 @@ +// +// SwipeRevealCell.swift +// SwipeCell +// +// Created by Kyle Newsome on 2015-11-10. +// Copyright © 2015 Kyle Newsome. All rights reserved. +// + +import Foundation +import UIKit + +@objc public protocol SwipeRevealCellDelegate:SwipeCellDelegate { + @objc optional func swipeRevealCell(_ cell: SwipeCell, activatedAction isActionLeft: Bool) +} + +public class SwipeRevealCell: SwipeCell { + + public var backViewbackgroundColor: UIColor = UIColor(white: 0.92, alpha: 1) + private var _backView: UIView? + public var backView: UIView? { + if _backView == nil { + _backView = UIView(frame: self.contentView.bounds) + _backView!.backgroundColor = self.backViewbackgroundColor + } + return _backView + } + public var shouldCleanUpBackView = true + + 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) + + public var bgViewLeftImage: UIImage? + public var bgViewRightImage: UIImage? + + private var _leftBackButton: UIButton? + public var leftBackButton:UIButton? { + 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(SwipeRevealCell.leftButtonTapped), for: .touchUpInside) + _leftBackButton!.tintColor = UIColor.white() + _leftBackButton!.contentMode = .center + self.backView!.addSubview(_leftBackButton!) + } + return _leftBackButton + } + + private var _rightBackButton: UIButton? + public var rightBackButton:UIButton? { + 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(SwipeRevealCell.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) + 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() + + if self.shouldCleanUpBackView { + _leftBackButton?.removeFromSuperview() + _leftBackButton = nil + _rightBackButton?.removeFromSuperview() + _rightBackButton = nil + _backView?.removeFromSuperview() + _backView = nil + } + } + + public override func layoutSubviews() { + super.layoutSubviews() + } + + override public func swipeHandlerDidStartSwiping(_ handler: SwipeHandler) { + + super.swipeHandlerDidStartSwiping(handler) + self.backgroundView!.addSubview(self.backView!) + } + + override public func swipeHandlerWillRelease(_ handler: SwipeHandler) { + + super.swipeHandlerWillRelease(handler) + + self.animateRelease() + } + + override public func swipeHandlerDidSwipe(_ handler: SwipeHandler) { + + super.swipeHandlerDidSwipe(handler) + + 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 = 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 { + self.backView?.backgroundColor = self.bgViewInactiveColor + } + }, completion: nil) + } else if point.x < 0 { + let frame = self.rightBackButton!.frame + let maxX = getBackgroundViewImagesMaxX(point.x) + 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 + } + }, completion: nil) + } + } + + + // MARK: - Reveal Cell Animations + + public func animateRelease() { + + guard swipeHandler.state == .normal else { + + return + } + + let pointX = self.contentView.frame.origin.x + + UIView.animate(withDuration: 0.2, + delay: 0, + options: .curveLinear, + animations: { + if pointX > 0 { + self.leftBackButton!.frame.origin.x = -self.swipeHandler.threshold + } else if pointX < 0 { + self.rightBackButton!.frame.origin.x = self.frame.maxX + } + }, completion: nil) + } + + // MARK: - Reveal Cell + + public func getBackgroundViewImagesMaxX(_ x:CGFloat) -> CGFloat { + if x > 0 { + + let frame = self.leftBackButton!.frame + return min(self.contentView.frame.minX - frame.width, 0) + } else { + + let frame = self.rightBackButton!.frame + return max(self.frame.maxX - frame.width, self.contentView.frame.maxX) + } + } + + public func leftButtonTapped () { + self.shouldCleanUpBackView = true + swipeHandler.resetCellPosition(withForce: true) + self.animateRelease() + + let delegate = self.delegate as? SwipeRevealCellDelegate + + delegate?.swipeRevealCell?(self, activatedAction: true) + } + + public func rightButtonTapped () { + self.shouldCleanUpBackView = true + swipeHandler.resetCellPosition(withForce: true) + self.animateRelease() + + let delegate = self.delegate as? SwipeRevealCellDelegate + + delegate?.swipeRevealCell?(self, activatedAction: 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/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 f653312..09645c3 100644 --- a/BWSwipeRevealCellExample/MasterViewController.swift +++ b/BWSwipeRevealCellExample/MasterViewController.swift @@ -10,23 +10,23 @@ import UIKit import CoreData import BWSwipeRevealCell -class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, BWSwipeRevealCellDelegate { +class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, SwipeRevealCellDelegate { var managedObjectContext: NSManagedObjectContext? = nil 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,65 +50,69 @@ 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) - let swipeCell:BWSwipeRevealCell = cell as! BWSwipeRevealCell + func configureCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) { + let object = self.fetchedResultsController.object(at: indexPath) + let swipeCell:SwipeRevealCell = cell as! SwipeRevealCell - 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 = 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 } // 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 +126,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: SwipeCell) { 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.config.name != "SlidingDoor" { + let indexPath: IndexPath = tableView.indexPath(for: cell)! self.removeObjectAtIndexPath(indexPath) } } - func swipeCellActivatedAction(cell: BWSwipeCell, isActionLeft: Bool) { + func swipeRevealCell(_ cell: SwipeCell, activatedAction 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: SwipeCell) { 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: 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") }