diff --git a/.swift-version b/.swift-version index 8012ebbb..6e636605 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.2 \ No newline at end of file +5.0 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 311e1cc7..16570dca 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "facebook/pop" "1.0.9" +github "facebook/pop" "1.0.12" diff --git a/Example/Koloda.xcodeproj/project.pbxproj b/Example/Koloda.xcodeproj/project.pbxproj index be195c9e..c361ec79 100644 --- a/Example/Koloda.xcodeproj/project.pbxproj +++ b/Example/Koloda.xcodeproj/project.pbxproj @@ -62,11 +62,11 @@ 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( - 607FACF51AFB993E008FA782 /* Podspec Metadata */, 607FACD21AFB9204008FA782 /* Example */, - 607FACD11AFB9204008FA782 /* Products */, - AB88095976C7B6CA809587FB /* Pods */, 85B3405D0F2A73D9A709B3FA /* Frameworks */, + AB88095976C7B6CA809587FB /* Pods */, + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + 607FACD11AFB9204008FA782 /* Products */, ); sourceTree = ""; }; @@ -82,18 +82,18 @@ isa = PBXGroup; children = ( 607FACD51AFB9204008FA782 /* AppDelegate.swift */, - 607FACD71AFB9204008FA782 /* ViewController.swift */, 440FB7681B515995009FC9FC /* BackgroundAnimationViewController.swift */, 446BFD081CB0184A00619E78 /* BackgroundKolodaAnimator.swift */, - 4420661E1B4457E800FD4CAD /* ExampleOverlayView.swift */, 440FB7661B51499A009FC9FC /* CustomKolodaView.swift */, 44B5EC281B660C8500895E3D /* CustomOverlayView.swift */, - 607FACD91AFB9204008FA782 /* Main.storyboard */, + 44B5EC2A1B660C9100895E3D /* CustomOverlayView.xib */, + 4420661E1B4457E800FD4CAD /* ExampleOverlayView.swift */, 607FACDC1AFB9204008FA782 /* Images.xcassets */, 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, + 607FACD91AFB9204008FA782 /* Main.storyboard */, 4420661B1B44577500FD4CAD /* OverlayView.xib */, - 44B5EC2A1B660C9100895E3D /* CustomOverlayView.xib */, 607FACD31AFB9204008FA782 /* Supporting Files */, + 607FACD71AFB9204008FA782 /* ViewController.swift */, ); name = Example; path = Koloda; @@ -111,8 +111,8 @@ isa = PBXGroup; children = ( D6781607B5327BAF503C32E3 /* Koloda.podspec */, - 8C6AF94B3192C604963C53B3 /* README.md */, 5C6DBB80685DABAE785B8715 /* LICENSE */, + 8C6AF94B3192C604963C53B3 /* README.md */, ); name = "Podspec Metadata"; sourceTree = ""; @@ -170,13 +170,13 @@ TargetAttributes = { 607FACCF1AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; }; }; }; buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "Koloda" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -232,7 +232,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Koloda_Example/Pods-Koloda_Example-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Koloda_Example/Pods-Koloda_Example-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", ); @@ -243,7 +243,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Koloda_Example/Pods-Koloda_Example-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Koloda_Example/Pods-Koloda_Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -408,8 +408,7 @@ MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.Koloda-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -425,8 +424,7 @@ MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.Koloda-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Example/Koloda.xcodeproj/xcshareddata/xcschemes/Koloda-Example.xcscheme b/Example/Koloda.xcodeproj/xcshareddata/xcschemes/Koloda-Example.xcscheme index 3d1759a0..afef7db3 100644 --- a/Example/Koloda.xcodeproj/xcshareddata/xcschemes/Koloda-Example.xcscheme +++ b/Example/Koloda.xcodeproj/xcshareddata/xcschemes/Koloda-Example.xcscheme @@ -1,6 +1,6 @@ "https://github.com/Yalantis/Koloda.git", :tag => s.version } + s.author = 'Yalantis' + s.source = { :git => 'https://github.com/Yalantis/Koloda.git', :tag => s.version } s.social_media_url = 'https://twitter.com/yalantis' s.platform = :ios, '8.0' diff --git a/Pod/Classes/KolodaView/DraggableCardView/DraggableCardView.swift b/Pod/Classes/KolodaView/DraggableCardView/DraggableCardView.swift index fc698d98..03137fd8 100644 --- a/Pod/Classes/KolodaView/DraggableCardView/DraggableCardView.swift +++ b/Pod/Classes/KolodaView/DraggableCardView/DraggableCardView.swift @@ -19,7 +19,7 @@ public enum DragSpeed: TimeInterval { protocol DraggableCardDelegate: class { func card(_ card: DraggableCardView, wasDraggedWithFinishPercentage percentage: CGFloat, inDirection direction: SwipeResultDirection) - func card(_ card: DraggableCardView, wasSwipedIn direction: SwipeResultDirection) + func card(_ card: DraggableCardView, wasSwipedIn direction: SwipeResultDirection, forced: Bool) func card(_ card: DraggableCardView, shouldSwipeIn direction: SwipeResultDirection) -> Bool func card(cardWasReset card: DraggableCardView) func card(cardWasTapped card: DraggableCardView) @@ -46,7 +46,7 @@ private let cardResetAnimationDuration: TimeInterval = 0.2 internal var cardSwipeActionAnimationDuration: TimeInterval = DragSpeed.default.rawValue public class DraggableCardView: UIView, UIGestureRecognizerDelegate { - + //Drag animation constants public var rotationMax = defaultRotationMax public var rotationAngle = defaultRotationAngle @@ -57,16 +57,22 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { configureSwipeSpeed() } } + + internal var dragBegin = false private var overlayView: OverlayView? - private(set) var contentView: UIView? + public private(set) var contentView: UIView? private var panGestureRecognizer: UIPanGestureRecognizer! private var tapGestureRecognizer: UITapGestureRecognizer! private var animationDirectionY: CGFloat = 1.0 - private var dragBegin = false private var dragDistance = CGPoint.zero - private var swipePercentageMargin: CGFloat = 0.0 + + private var swipePercentageMargin: CGFloat { + let percentage = delegate?.card(cardSwipeThresholdRatioMargin: self) ?? 0.0 + + return percentage != 0.0 ? percentage : 1.0 + } //MARK: Lifecycle @@ -85,16 +91,6 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { setup() } - override public var frame: CGRect { - didSet { - if let ratio = delegate?.card(cardSwipeThresholdRatioMargin: self) , ratio != 0 { - swipePercentageMargin = ratio - } else { - swipePercentageMargin = 1.0 - } - } - } - deinit { removeGestureRecognizer(panGestureRecognizer) removeGestureRecognizer(tapGestureRecognizer) @@ -108,7 +104,7 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { tapGestureRecognizer.delegate = self tapGestureRecognizer.cancelsTouchesInView = false addGestureRecognizer(tapGestureRecognizer) - + if let delegate = delegate { cardSwipeActionAnimationDuration = delegate.card(cardSwipeSpeed: self).rawValue } @@ -128,45 +124,45 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { } else { self.addSubview(view) } - + self.contentView = view configureContentView() } - + private func configureOverlayView() { if let overlay = self.overlayView { overlay.translatesAutoresizingMaskIntoConstraints = false let width = NSLayoutConstraint( item: overlay, - attribute: NSLayoutConstraint.Attribute.width, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .width, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.width, + attribute: .width, multiplier: 1.0, constant: 0) let height = NSLayoutConstraint( item: overlay, - attribute: NSLayoutConstraint.Attribute.height, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .height, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.height, + attribute: .height, multiplier: 1.0, constant: 0) let top = NSLayoutConstraint ( item: overlay, - attribute: NSLayoutConstraint.Attribute.top, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .top, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.top, + attribute: .top, multiplier: 1.0, constant: 0) let leading = NSLayoutConstraint ( item: overlay, - attribute: NSLayoutConstraint.Attribute.leading, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .leading, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.leading, + attribute: .leading, multiplier: 1.0, constant: 0) addConstraints([width,height,top,leading]) @@ -179,34 +175,34 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { let width = NSLayoutConstraint( item: contentView, - attribute: NSLayoutConstraint.Attribute.width, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .width, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.width, + attribute: .width, multiplier: 1.0, constant: 0) let height = NSLayoutConstraint( item: contentView, - attribute: NSLayoutConstraint.Attribute.height, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .height, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.height, + attribute: .height, multiplier: 1.0, constant: 0) let top = NSLayoutConstraint ( item: contentView, - attribute: NSLayoutConstraint.Attribute.top, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .top, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.top, + attribute: .top, multiplier: 1.0, constant: 0) let leading = NSLayoutConstraint ( item: contentView, - attribute: NSLayoutConstraint.Attribute.leading, - relatedBy: NSLayoutConstraint.Relation.equal, + attribute: .leading, + relatedBy: .equal, toItem: self, - attribute: NSLayoutConstraint.Attribute.leading, + attribute: .leading, multiplier: 1.0, constant: 0) @@ -249,7 +245,7 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { let rotationAngle = animationDirectionY * self.rotationAngle * rotationStrength let scaleStrength = 1 - ((1 - scaleMin) * abs(rotationStrength)) let scale = max(scaleStrength, scaleMin) - + var transform = CATransform3DIdentity transform = CATransform3DScale(transform, scale, scale, 1) transform = CATransform3DRotate(transform, rotationAngle, 0, 0, 1) @@ -275,12 +271,17 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { - if let touchView = touch.view, let _ = touchView as? UIControl { - return false + guard gestureRecognizer == tapGestureRecognizer, touch.view is UIControl else { + return true } - - panGestureRecognizer.isEnabled = delegate?.card(cardShouldDrag: self) ?? true - return true + return false + } + + public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + guard gestureRecognizer == panGestureRecognizer else { + return true + } + return delegate?.card(cardShouldDrag: self) ?? true } @objc func tapRecognized(_ recogznier: UITapGestureRecognizer) { @@ -296,13 +297,15 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { private var dragDirection: SwipeResultDirection? { //find closest direction let normalizedDragPoint = dragDistance.normalizedDistanceForSize(bounds.size) - return directions.reduce((distance:CGFloat.infinity, direction:nil)) { closest, direction in - let distance = direction.point.distanceTo(normalizedDragPoint) - if distance < closest.distance { - return (distance, direction) - } - return closest - }.direction + return directions + .reduce((distance:CGFloat.infinity, direction:SwipeResultDirection?.none)) { closest, direction in + guard direction.hasPoint(normalizedDragPoint) else { return closest } + + let distance = direction.point.distanceTo(normalizedDragPoint) + if distance < closest.distance { return (distance, direction) } + + return closest + }.direction } private var dragPercentage: CGFloat { @@ -324,9 +327,9 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { // check 4 borders for intersection with line between touchpoint and center of card // return smallest percentage of distance to edge point or 0 return rect.perimeterLines - .compactMap { CGPoint.intersectionBetweenLines(targetLine, line2: $0) } - .map { centerDistance / $0.distanceTo(.zero) } - .min() ?? 0 + .compactMap { CGPoint.intersectionBetweenLines(targetLine, line2: $0) } + .map { centerDistance / $0.distanceTo(.zero) } + .min() ?? 0 } } @@ -349,10 +352,24 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { } func animationPointForDirection(_ direction: SwipeResultDirection) -> CGPoint { - let point = direction.point - let animatePoint = CGPoint(x: point.x * 4, y: point.y * 4) //should be 2 - let retPoint = animatePoint.screenPointForSize(screenSize) - return retPoint + guard let superview = self.superview else { + return .zero + } + + let superSize = superview.bounds.size + let space = max(screenSize.width, screenSize.height) + switch direction { + case .left, .right: + // Optimize left and right position + let x = direction.point.x * (superSize.width + space) + let y = 0.5 * superSize.height + return CGPoint(x: x, y: y) + + default: + let x = direction.point.x * (superSize.width + space) + let y = direction.point.y * (superSize.height + space) + return CGPoint(x: x, y: y) + } } func animationRotationForDirection(_ direction: SwipeResultDirection) -> CGFloat { @@ -362,7 +379,7 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { private func swipeAction(_ direction: SwipeResultDirection) { overlayView?.overlayState = direction overlayView?.alpha = 1.0 - delegate?.card(self, wasSwipedIn: direction) + delegate?.card(self, wasSwipedIn: direction, forced: false) let translationAnimation = POPBasicAnimation(propertyNamed: kPOPLayerTranslationXY) translationAnimation?.duration = cardSwipeActionAnimationDuration translationAnimation?.fromValue = NSValue(cgPoint: POPLayerGetTranslationXY(layer)) @@ -418,9 +435,9 @@ public class DraggableCardView: UIView, UIGestureRecognizerDelegate { layer.pop_removeAllAnimations() } - func swipe(_ direction: SwipeResultDirection, completionHandler: @escaping () -> Void) { + func swipe(_ direction: SwipeResultDirection, forced: Bool = false, completionHandler: @escaping () -> Void) { if !dragBegin { - delegate?.card(self, wasSwipedIn: direction) + delegate?.card(self, wasSwipedIn: direction, forced: forced) let swipePositionAnimation = POPBasicAnimation(propertyNamed: kPOPLayerTranslationXY) swipePositionAnimation?.fromValue = NSValue(cgPoint:POPLayerGetTranslationXY(layer)) diff --git a/Pod/Classes/KolodaView/KolodaView.swift b/Pod/Classes/KolodaView/KolodaView.swift index f3d74a72..9b4dab32 100644 --- a/Pod/Classes/KolodaView/KolodaView.swift +++ b/Pod/Classes/KolodaView/KolodaView.swift @@ -9,7 +9,7 @@ import UIKit import pop -//Default values +// Default values private let defaultCountOfVisibleCards = 3 private let defaultBackgroundCardsTopMargin: CGFloat = 4.0 private let defaultBackgroundCardsScalePercent: CGFloat = 0.95 @@ -18,11 +18,16 @@ private let defaultBackgroundCardFrameAnimationDuration: TimeInterval = 0.2 private let defaultAppearanceAnimationDuration: TimeInterval = 0.8 private let defaultReverseAnimationDuration: TimeInterval = 0.3 -//Opacity values +// Opacity values private let defaultAlphaValueOpaque: CGFloat = 1.0 private let defaultAlphaValueTransparent: CGFloat = 0.0 private let defaultAlphaValueSemiTransparent: CGFloat = 0.7 +// Direction of visible cards +public enum VisibleCardsDirection: Int { + case top, bottom +} + public protocol KolodaViewDataSource: class { func kolodaNumberOfCards(_ koloda: KolodaView) -> Int @@ -36,10 +41,10 @@ public extension KolodaViewDataSource { func koloda(_ koloda: KolodaView, viewForCardOverlayAt index: Int) -> OverlayView? { return nil } - + func kolodaSpeedThatCardShouldDrag(_ koloda: KolodaView) -> DragSpeed { - return .default - } + return .default + } } public protocol KolodaViewDelegate: class { @@ -55,7 +60,9 @@ public protocol KolodaViewDelegate: class { func koloda(_ koloda: KolodaView, draggedCardWithPercentage finishPercentage: CGFloat, in direction: SwipeResultDirection) func kolodaDidResetCard(_ koloda: KolodaView) func kolodaSwipeThresholdRatioMargin(_ koloda: KolodaView) -> CGFloat? + func koloda(_ koloda: KolodaView, willShow card: UIView, at index: Int) func koloda(_ koloda: KolodaView, didShowCardAt index: Int) + func koloda(_ koloda: KolodaView, didRewindTo index: Int) func koloda(_ koloda: KolodaView, shouldDragCardAt index: Int ) -> Bool func kolodaPanBegan(_ koloda: KolodaView, card: DraggableCardView) func kolodaPanFinished(_ koloda: KolodaView, card: DraggableCardView) @@ -74,8 +81,9 @@ public extension KolodaViewDelegate { func kolodaShouldTransparentizeNextCard(_ koloda: KolodaView) -> Bool { return true } func koloda(_ koloda: KolodaView, draggedCardWithPercentage finishPercentage: CGFloat, in direction: SwipeResultDirection) {} func kolodaDidResetCard(_ koloda: KolodaView) {} - func kolodaSwipeThresholdRatioMargin(_ koloda: KolodaView) -> CGFloat? { return nil} + func kolodaSwipeThresholdRatioMargin(_ koloda: KolodaView) -> CGFloat? { return nil } func koloda(_ koloda: KolodaView, didShowCardAt index: Int) {} + func koloda(_ koloda: KolodaView, didRewindTo index: Int) {} func koloda(_ koloda: KolodaView, shouldDragCardAt index: Int ) -> Bool { return true } func kolodaPanBegan(_ koloda: KolodaView, card: DraggableCardView) {} func kolodaPanFinished(_ koloda: KolodaView, card: DraggableCardView) {} @@ -83,19 +91,37 @@ public extension KolodaViewDelegate { open class KolodaView: UIView, DraggableCardDelegate { - //Opacity values + // MARK: Public + + // Opacity values public var alphaValueOpaque = defaultAlphaValueOpaque public var alphaValueTransparent = defaultAlphaValueTransparent public var alphaValueSemiTransparent = defaultAlphaValueSemiTransparent public var shouldPassthroughTapsWhenNoVisibleCards = false - + public var backgroundCardsTopMargin = defaultBackgroundCardsTopMargin + + public var scaleMin: CGFloat? + //Drag animation constants public var rotationMax: CGFloat? public var rotationAngle: CGFloat? - public var scaleMin: CGFloat? - + + public var appearanceAnimationDuration = defaultAppearanceAnimationDuration + public var backgroundCardFrameAnimationDuration = defaultBackgroundCardFrameAnimationDuration + public var reverseAnimationDuration = defaultReverseAnimationDuration + + public var countOfVisibleCards = defaultCountOfVisibleCards + public var backgroundCardsScalePercent = defaultBackgroundCardsScalePercent + // Visible cards direction (defaults to bottom) + public var visibleCardsDirection: VisibleCardsDirection = .bottom + + public var isLoop = false + + private(set) public var currentCardIndex = 0 + private(set) public var countOfCards = 0 + public weak var dataSource: KolodaViewDataSource? { didSet { setupDeck() @@ -105,39 +131,41 @@ open class KolodaView: UIView, DraggableCardDelegate { public weak var delegate: KolodaViewDelegate? public var animator: KolodaViewAnimator { - set { - self._animator = newValue - } - get { - return self._animator - } + set { self._animator = newValue } + get { return self._animator } } - private lazy var _animator: KolodaViewAnimator = { - return KolodaViewAnimator(koloda: self) - }() - - internal var shouldTransparentizeNextCard: Bool { - return delegate?.kolodaShouldTransparentizeNextCard(self) ?? true + public var isAnimating: Bool { + return animationSemaphore.isAnimating } - internal var animationSemaphore = KolodaAnimationSemaphore() - public var isRunOutOfCards: Bool { - return visibleCards.isEmpty } - private(set) public var currentCardIndex = 0 - private(set) public var countOfCards = 0 - public var countOfVisibleCards = defaultCountOfVisibleCards - private var visibleCards = [DraggableCardView]() - public var isLoop = false + // MARK: Private + internal var shouldTransparentizeNextCard: Bool { + return delegate?.kolodaShouldTransparentizeNextCard(self) ?? true + } + internal var animationSemaphore = KolodaAnimationSemaphore() + + private lazy var _animator: KolodaViewAnimator = { + return KolodaViewAnimator(koloda: self) + }() + private var visibleCards = [DraggableCardView]() + + private var cardIsDragging: Bool { + guard let frontCard = visibleCards.first else { + return false + } + return frontCard.dragBegin + } + override open func layoutSubviews() { super.layoutSubviews() - if !animationSemaphore.isAnimating { + if !animationSemaphore.isAnimating, !cardIsDragging { layoutDeck() } } @@ -147,7 +175,7 @@ open class KolodaView: UIView, DraggableCardDelegate { private func setupDeck() { if let dataSource = dataSource { countOfCards = dataSource.kolodaNumberOfCards(self) - + if countOfCards - currentCardIndex > 0 { let countOfNeededCards = min(countOfVisibleCards, countOfCards - currentCardIndex) @@ -161,9 +189,9 @@ open class KolodaView: UIView, DraggableCardDelegate { nextCardView.alpha = alphaValueSemiTransparent } visibleCards.append(nextCardView) + if isTop { delegate?.koloda(self, willShow: nextCardView, at: index) } isTop ? addSubview(nextCardView) : insertSubview(nextCardView, belowSubview: visibleCards[index - 1]) } - self.delegate?.koloda(self, didShowCardAt: currentCardIndex) } } } @@ -172,6 +200,9 @@ open class KolodaView: UIView, DraggableCardDelegate { for (index, card) in visibleCards.enumerated() { layoutCard(card, at: index) } + if currentCardIndex == 0 { + delegate?.koloda(self, didShowCardAt: currentCardIndex) + } } private func layoutCard(_ card: DraggableCardView, at index: Int) { @@ -189,17 +220,27 @@ open class KolodaView: UIView, DraggableCardDelegate { // MARK: Frames open func frameForCard(at index: Int) -> CGRect { let bottomOffset: CGFloat = 0 - let topOffset = defaultBackgroundCardsTopMargin * CGFloat(countOfVisibleCards - 1) + let topOffset = backgroundCardsTopMargin * CGFloat(countOfVisibleCards - 1) let scalePercent = defaultBackgroundCardsScalePercent let width = self.frame.width * pow(scalePercent, CGFloat(index)) let xOffset = (self.frame.width - width) / 2 let height = (self.frame.height - bottomOffset - topOffset) * pow(scalePercent, CGFloat(index)) - let multiplier: CGFloat = index > 0 ? 1.0 : 0.0 - let prevCardFrame = index > 0 ? frameForCard(at: max(index - 1, 0)) : .zero - let yOffset = (prevCardFrame.height - height + prevCardFrame.origin.y + defaultBackgroundCardsTopMargin) * multiplier - let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height) - - return frame + + if visibleCardsDirection == .bottom { + let multiplier: CGFloat = index > 0 ? 1.0 : 0.0 + let prevCardFrame = index > 0 ? frameForCard(at: max(index - 1, 0)) : .zero + let yOffset = (prevCardFrame.height - height + prevCardFrame.origin.y + backgroundCardsTopMargin) * multiplier + let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height) + + return frame + } else { + let multiplier: CGFloat = index < (countOfVisibleCards - 1) ? 1.0 : 0.0 + let nextCardFrame = index < (countOfVisibleCards - 1) ? frameForCard(at: min(index + 1, (countOfVisibleCards - 1))) : .zero + let yOffset = (nextCardFrame.origin.y + backgroundCardsTopMargin) * multiplier + let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height) + + return frame + } } internal func frameForTopCard() -> CGRect { @@ -297,12 +338,14 @@ open class KolodaView: UIView, DraggableCardDelegate { } func card(cardAllowedDirections card: DraggableCardView) -> [SwipeResultDirection] { - let index = currentCardIndex + visibleCards.index(of: card)! + guard let firstIndex = visibleCards.firstIndex(of: card) else { return [.left, .right] } + + let index = currentCardIndex + firstIndex return delegate?.koloda(self, allowedDirectionsForIndex: index) ?? [.left, .right] } - func card(_ card: DraggableCardView, wasSwipedIn direction: SwipeResultDirection) { - swipedAction(direction) + func card(_ card: DraggableCardView, wasSwipedIn direction: SwipeResultDirection, forced: Bool) { + swipedAction(direction, manually: forced) } func card(cardWasReset card: DraggableCardView) { @@ -330,7 +373,7 @@ open class KolodaView: UIView, DraggableCardDelegate { } func card(cardWasTapped card: DraggableCardView) { - guard let visibleIndex = visibleCards.index(of: card) else { return } + guard let visibleIndex = visibleCards.firstIndex(of: card) else { return } let index = currentCardIndex + visibleIndex delegate?.koloda(self, didSelectCardAt: index) @@ -341,24 +384,24 @@ open class KolodaView: UIView, DraggableCardDelegate { } func card(cardShouldDrag card: DraggableCardView) -> Bool { - guard let visibleIndex = visibleCards.index(of: card) else { return true} + guard let visibleIndex = visibleCards.firstIndex(of: card) else { return true} let index = currentCardIndex + visibleIndex return delegate?.koloda(self, shouldDragCardAt: index) ?? true } - + func card(cardSwipeSpeed card: DraggableCardView) -> DragSpeed { return dataSource?.kolodaSpeedThatCardShouldDrag(self) ?? DragSpeed.default } - + func card(cardPanBegan card: DraggableCardView) { delegate?.kolodaPanBegan(self, card: card) } - + func card(cardPanFinished card: DraggableCardView) { delegate?.kolodaPanFinished(self, card: card) } - + // MARK: Private private func clear() { currentCardIndex = 0 @@ -371,7 +414,7 @@ open class KolodaView: UIView, DraggableCardDelegate { } // MARK: Actions - private func swipedAction(_ direction: SwipeResultDirection) { + private func swipedAction(_ direction: SwipeResultDirection, manually: Bool = false) { animationSemaphore.increment() visibleCards.removeFirst() @@ -386,7 +429,7 @@ open class KolodaView: UIView, DraggableCardDelegate { || (isLoop && realCountOfCards > 0 && realCountOfCards > visibleCards.count) { loadNextCard() } - + if !visibleCards.isEmpty { animateCardsAfterLoadingWithCompletion { [weak self] in guard let _self = self else { @@ -395,12 +438,13 @@ open class KolodaView: UIView, DraggableCardDelegate { _self.visibleCards.last?.isHidden = false _self.animationSemaphore.decrement() - _self.delegate?.koloda(_self, didSwipeCardAt: swipedCardIndex, in: direction) + + if !manually { _self.delegate?.koloda(_self, didSwipeCardAt: swipedCardIndex, in: direction) } _self.delegate?.koloda(_self, didShowCardAt: _self.currentCardIndex) } } else { animationSemaphore.decrement() - delegate?.koloda(self, didSwipeCardAt: swipedCardIndex, in: direction) + if !manually { delegate?.koloda(self, didSwipeCardAt: swipedCardIndex, in: direction) } delegate?.kolodaDidRunOutOfCards(self) } } @@ -428,6 +472,7 @@ open class KolodaView: UIView, DraggableCardDelegate { } else { addSubview(lastCard) } + delegate?.koloda(self, willShow: lastCard, at: indexToBeMake) visibleCards.append(lastCard) } @@ -458,7 +503,7 @@ open class KolodaView: UIView, DraggableCardDelegate { currentCard, scale: cardParameters.scale, frame: cardParameters.frame, - duration: defaultBackgroundCardFrameAnimationDuration, + duration: backgroundCardFrameAnimationDuration, completion: animationCompletion ) } @@ -488,12 +533,13 @@ open class KolodaView: UIView, DraggableCardDelegate { visibleCards.insert(firstCardView, at: 0) animationSemaphore.increment() - animator.applyReverseAnimation(firstCardView, direction: direction, duration: defaultReverseAnimationDuration, completion: { [weak self] _ in + animator.applyReverseAnimation(firstCardView, direction: direction, duration: reverseAnimationDuration, completion: { [weak self] _ in guard let _self = self else { return } _self.animationSemaphore.decrement() + _self.delegate?.koloda(_self, didRewindTo: _self.currentCardIndex) _self.delegate?.koloda(_self, didShowCardAt: _self.currentCardIndex) }) } @@ -509,7 +555,7 @@ open class KolodaView: UIView, DraggableCardDelegate { card, scale: cardParameters.scale, frame: cardParameters.frame, - duration: defaultBackgroundCardFrameAnimationDuration, + duration: backgroundCardFrameAnimationDuration, completion: nil ) } @@ -537,7 +583,7 @@ open class KolodaView: UIView, DraggableCardDelegate { } } - private func reconfigureCards() { + public func reconfigureCards() { if dataSource != nil { for (index, card) in visibleCards.enumerated() { var actualIndex = currentCardIndex + index @@ -557,7 +603,9 @@ open class KolodaView: UIView, DraggableCardDelegate { public func reloadData() { guard let numberOfCards = dataSource?.kolodaNumberOfCards(self), numberOfCards > 0 else { + countOfCards = 0 clear() + return } @@ -581,8 +629,15 @@ open class KolodaView: UIView, DraggableCardDelegate { } public func swipe(_ direction: SwipeResultDirection, force: Bool = false) { - if !animationSemaphore.isAnimating { - if let frontCard = visibleCards.first { + let shouldSwipe = delegate?.koloda(self, shouldSwipeCardAt: currentCardIndex, in: direction) ?? true + guard force || shouldSwipe else { return } + + let validDirection = delegate?.koloda(self, allowedDirectionsForIndex: currentCardIndex).contains(direction) ?? true + guard force || validDirection else { return } + + if !animationSemaphore.isAnimating || force { + if let frontCard = visibleCards.first, !frontCard.dragBegin { + if visibleCards.count > 1 { let nextCard = visibleCards[1] nextCard.alpha = shouldTransparentizeNextCard ? alphaValueSemiTransparent : alphaValueOpaque @@ -590,7 +645,7 @@ open class KolodaView: UIView, DraggableCardDelegate { animationSemaphore.increment() - frontCard.swipe(direction) { + frontCard.swipe(direction, forced: force) { self.animationSemaphore.decrement() } frontCard.delegate = nil @@ -662,7 +717,7 @@ open class KolodaView: UIView, DraggableCardDelegate { cards, completion: { _ in self.removeCards(cards) - } + } ) } else { self.removeCards(cards) @@ -688,7 +743,7 @@ open class KolodaView: UIView, DraggableCardDelegate { insertedCards, completion: { _ in self.animationSemaphore.decrement() - } + } ) } diff --git a/Pod/Classes/KolodaView/SwipeResultDirection.swift b/Pod/Classes/KolodaView/SwipeResultDirection.swift index 5afcae98..82c965e9 100644 --- a/Pod/Classes/KolodaView/SwipeResultDirection.swift +++ b/Pod/Classes/KolodaView/SwipeResultDirection.swift @@ -21,6 +21,10 @@ public enum SwipeResultDirection: String { case bottomRight } +#if swift(>=4.2) +extension SwipeResultDirection: CaseIterable { } +#endif + extension SwipeResultDirection { private var swipeDirection: Direction { @@ -49,6 +53,24 @@ extension SwipeResultDirection { let h = VerticalPosition.bottom.rawValue - VerticalPosition.top.rawValue return CGRect(x: HorizontalPosition.left.rawValue, y: VerticalPosition.top.rawValue, width: w, height: h) } + + func hasPoint(_ point: CGPoint) -> Bool { + switch self { + case .up: + return point.y < 0 && + (point.x > 0 && point.y < -point.x || point.x < 0 && point.y < point.x) + case .down: + return point.y > 0 && + (point.x > 0 && point.y > point.x || point.x < 0 && point.y > -point.x) + case .left: + return point.x < 0 && + (point.y > 0 && point.y < -point.x || point.y < 0 && point.y > point.x) + case .right: + return point.x > 0 && + (point.y > 0 && point.y < point.x || point.y < 0 && point.y > -point.x) + default: return true + } + } } diff --git a/README.md b/README.md index 0408fef4..a82e258c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -KolodaView [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg) +KolodaView ![cocoapods](https://img.shields.io/cocoapods/v/Koloda.svg)[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg) -------------- [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToMakeSoup/master/PullToMakeSoupDemo/Resouces/badge_dark.png)](https://Yalantis.com/?utm_source=github) @@ -24,13 +24,6 @@ ARC Compatibility KolodaView requires ARC. -Сocoapods version ------------------- - -```ruby -pod 'Koloda', '~> 4.5.1' -``` - Thread Safety -------------- @@ -237,6 +230,11 @@ This method is fired after resetting the card. func koloda(_ koloda: KolodaView, didShowCardAt index: Int) ``` This method is called after a card has been shown, after animation is complete +```swift +func koloda(_ koloda: KolodaView, didRewindTo index: Int) +``` +This method is called after a card was rewound, after animation is complete + ```swift func koloda(_ koloda: KolodaView, shouldDragCardAt index: Int) -> Bool ``` @@ -246,6 +244,21 @@ not move. Release Notes ---------------- +Version 5.0.1 +- added posibility to determine index of rewound card +- fixed crash after drugging card + +Version 5.0 +- Swift 5.0 via [@maxxfrazer](https://github.com/maxxfrazer) + +Version 4.7 +- fixed a bug with card responding during swiping via [@lixiang1994](https://github.com/lixiang1994) +- fixed a bug with inappropriate layouting via [@soundsmitten](https://github.com/soundsmitten) + +Version 4.6 +- update some properties to be publicitly settable via [@sroik](https://github.com/sroik) and [@leonardoherbert](https://github.com/leonardoherbert) +- Xcode 9 back compatibility via [@seriyvolk83](https://github.com/seriyvolk83) +- added posibility to have the card stack at the top or bottom via [@lorenzOliveto](https://github.com/lorenzOliveto) Version 4.5 - Swift 4.2 via [@evilmint](https://github.com/evilmint) @@ -309,7 +322,7 @@ License The MIT License (MIT) -Copyright © 2018 Yalantis +Copyright © 2019 Yalantis Permission is hereby granted free of charge to any person obtaining a copy of this software and associated documentation files (the "Software") to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: