import Foundation import AsyncDisplayKit open class ContextControllerSourceNode: ContextReferenceContentNode { public private(set) var contextGesture: ContextGesture? public var isGestureEnabled: Bool = true { didSet { self.contextGesture?.isEnabled = self.isGestureEnabled } } public var beginDelay: Double = 0.12 { didSet { self.contextGesture?.beginDelay = self.beginDelay } } public var animateScale: Bool = true public var activated: ((ContextGesture, CGPoint) -> Void)? public var shouldBegin: ((CGPoint) -> Bool)? public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? public weak var additionalActivationProgressLayer: CALayer? public var targetNodeForActivationProgress: ASDisplayNode? public var targetNodeForActivationProgressContentRect: CGRect? public func cancelGesture() { self.contextGesture?.cancel() self.contextGesture?.isEnabled = false self.contextGesture?.isEnabled = self.isGestureEnabled } override open func didLoad() { super.didLoad() let contextGesture = ContextGesture(target: self, action: nil) self.contextGesture = contextGesture self.view.addGestureRecognizer(contextGesture) contextGesture.beginDelay = self.beginDelay contextGesture.isEnabled = self.isGestureEnabled contextGesture.shouldBegin = { [weak self] point in guard let strongSelf = self, !strongSelf.bounds.width.isZero else { return false } return strongSelf.shouldBegin?(point) ?? true } contextGesture.activationProgress = { [weak self] progress, update in guard let strongSelf = self, !strongSelf.bounds.width.isZero else { return } if let customActivationProgress = strongSelf.customActivationProgress { customActivationProgress(progress, update) } else if strongSelf.animateScale { let targetNode: ASDisplayNode let targetContentRect: CGRect if let targetNodeForActivationProgress = strongSelf.targetNodeForActivationProgress { targetNode = targetNodeForActivationProgress if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { targetContentRect = targetNodeForActivationProgressContentRect } else { targetContentRect = CGRect(origin: CGPoint(), size: targetNode.bounds.size) } } else { targetNode = strongSelf targetContentRect = CGRect(origin: CGPoint(), size: targetNode.bounds.size) } let scaleSide = targetContentRect.width let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) let currentScale = 1.0 * (1.0 - progress) + minScale * progress let originalCenterOffsetX: CGFloat = targetNode.bounds.width / 2.0 - targetContentRect.midX let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale let originalCenterOffsetY: CGFloat = targetNode.bounds.height / 2.0 - targetContentRect.midY let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY switch update { case .update: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) targetNode.layer.sublayerTransform = sublayerTransform if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { additionalActivationProgressLayer.transform = sublayerTransform } case .begin: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) targetNode.layer.sublayerTransform = sublayerTransform if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { additionalActivationProgressLayer.transform = sublayerTransform } case .ended: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) let previousTransform = targetNode.layer.sublayerTransform targetNode.layer.sublayerTransform = sublayerTransform targetNode.layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { additionalActivationProgressLayer.transform = sublayerTransform }) } } } } contextGesture.activated = { [weak self] gesture, location in guard let strongSelf = self else { gesture.cancel() return } if let customActivationProgress = strongSelf.customActivationProgress { customActivationProgress(0.0, .ended(0.0)) } if let activated = strongSelf.activated { activated(gesture, location) } else { gesture.cancel() } } contextGesture.isEnabled = self.isGestureEnabled } } /*open class ContextControllerSourceNode: ASDisplayNode { private var viewImpl: ContextControllerSourceView { return self.view as! ContextControllerSourceView } public var contextGesture: ContextGesture? { if self.isNodeLoaded { return self.viewImpl.contextGesture } else { return nil } } public var isGestureEnabled: Bool = true { didSet { if self.isNodeLoaded { self.viewImpl.isGestureEnabled = self.isGestureEnabled } } } public var beginDelay: Double = 0.12 { didSet { if self.isNodeLoaded { self.viewImpl.beginDelay = self.beginDelay } } } public var animateScale: Bool = true { didSet { if self.isNodeLoaded { self.viewImpl.animateScale = self.animateScale } } } public var activated: ((ContextGesture, CGPoint) -> Void)? { didSet { if self.isNodeLoaded { self.viewImpl.activated = self.activated } } } public var shouldBegin: ((CGPoint) -> Bool)? { didSet { if self.isNodeLoaded { self.viewImpl.shouldBegin = self.shouldBegin } } } public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? { didSet { if self.isNodeLoaded { self.viewImpl.customActivationProgress = self.customActivationProgress } } } public weak var additionalActivationProgressLayer: CALayer? { didSet { if self.isNodeLoaded { self.viewImpl.additionalActivationProgressLayer = self.additionalActivationProgressLayer } } } public var targetNodeForActivationProgress: ASDisplayNode? { didSet { if self.isNodeLoaded { self.viewImpl.targetNodeForActivationProgress = self.targetNodeForActivationProgress } } } public var targetViewForActivationProgress: UIView? { didSet { if self.isNodeLoaded { self.viewImpl.targetViewForActivationProgress = self.targetViewForActivationProgress } } } public var targetNodeForActivationProgressContentRect: CGRect? { didSet { if self.isNodeLoaded { self.viewImpl.targetNodeForActivationProgressContentRect = self.targetNodeForActivationProgressContentRect } } } override public init() { super.init() self.setViewBlock({ return ContextControllerSourceView(frame: CGRect()) }) } override open func didLoad() { super.didLoad() self.viewImpl.isGestureEnabled = self.isGestureEnabled self.viewImpl.beginDelay = self.beginDelay self.viewImpl.animateScale = self.animateScale self.viewImpl.activated = self.activated self.viewImpl.shouldBegin = self.shouldBegin self.viewImpl.customActivationProgress = self.customActivationProgress self.viewImpl.additionalActivationProgressLayer = self.additionalActivationProgressLayer self.viewImpl.targetNodeForActivationProgress = self.targetNodeForActivationProgress self.viewImpl.targetViewForActivationProgress = self.targetViewForActivationProgress self.viewImpl.targetNodeForActivationProgressContentRect = self.targetNodeForActivationProgressContentRect } public func cancelGesture() { if self.isNodeLoaded { self.viewImpl.cancelGesture() } } }*/ open class ContextControllerSourceView: UIView { public private(set) var contextGesture: ContextGesture? public var isGestureEnabled: Bool = true { didSet { self.contextGesture?.isEnabled = self.isGestureEnabled } } public var beginDelay: Double = 0.12 { didSet { self.contextGesture?.beginDelay = self.beginDelay } } public var animateScale: Bool = true public var activated: ((ContextGesture, CGPoint) -> Void)? public var shouldBegin: ((CGPoint) -> Bool)? public var customActivationProgress: ((CGFloat, ContextGestureTransition) -> Void)? public weak var additionalActivationProgressLayer: CALayer? public var targetNodeForActivationProgress: ASDisplayNode? public var targetViewForActivationProgress: UIView? public var targetNodeForActivationProgressContentRect: CGRect? override public init(frame: CGRect) { super.init(frame: frame) let contextGesture = ContextGesture(target: self, action: nil) self.contextGesture = contextGesture self.addGestureRecognizer(contextGesture) contextGesture.beginDelay = self.beginDelay contextGesture.isEnabled = self.isGestureEnabled contextGesture.shouldBegin = { [weak self] point in guard let strongSelf = self, !strongSelf.bounds.width.isZero else { return false } return strongSelf.shouldBegin?(point) ?? true } contextGesture.activationProgress = { [weak self] progress, update in guard let strongSelf = self, !strongSelf.bounds.width.isZero else { return } if let customActivationProgress = strongSelf.customActivationProgress { customActivationProgress(progress, update) } else if strongSelf.animateScale { let targetView: UIView let targetContentRect: CGRect if let targetNodeForActivationProgress = strongSelf.targetNodeForActivationProgress { targetView = targetNodeForActivationProgress.view if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { targetContentRect = targetNodeForActivationProgressContentRect } else { targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) } } else if let targetViewForActivationProgress = strongSelf.targetViewForActivationProgress { targetView = targetViewForActivationProgress if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { targetContentRect = targetNodeForActivationProgressContentRect } else { targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) } } else { targetView = strongSelf targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) } let scaleSide = targetContentRect.width let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) let currentScale = 1.0 * (1.0 - progress) + minScale * progress let originalCenterOffsetX: CGFloat = targetView.bounds.width / 2.0 - targetContentRect.midX let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale let originalCenterOffsetY: CGFloat = targetView.bounds.height / 2.0 - targetContentRect.midY let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY switch update { case .update: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) targetView.layer.sublayerTransform = sublayerTransform if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { additionalActivationProgressLayer.transform = sublayerTransform } case .begin: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) targetView.layer.sublayerTransform = sublayerTransform if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { additionalActivationProgressLayer.transform = sublayerTransform } case .ended: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) let previousTransform = targetView.layer.sublayerTransform targetView.layer.sublayerTransform = sublayerTransform targetView.layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { additionalActivationProgressLayer.transform = sublayerTransform }) } } } } contextGesture.activated = { [weak self] gesture, location in guard let strongSelf = self else { gesture.cancel() return } if let customActivationProgress = strongSelf.customActivationProgress { customActivationProgress(0.0, .ended(0.0)) } if let activated = strongSelf.activated { activated(gesture, location) } else { gesture.cancel() } } contextGesture.isEnabled = self.isGestureEnabled } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } public func cancelGesture() { self.contextGesture?.cancel() self.contextGesture?.isEnabled = false self.contextGesture?.isEnabled = self.isGestureEnabled } }