diff --git a/Framework/Sources/FloatingPanel.swift b/Framework/Sources/FloatingPanel.swift index d675867..8de39af 100644 --- a/Framework/Sources/FloatingPanel.swift +++ b/Framework/Sources/FloatingPanel.swift @@ -10,7 +10,7 @@ import UIKit.UIGestureRecognizerSubclass // For Xcode 9.4.1 /// class FloatingPanel: NSObject, UIGestureRecognizerDelegate { // MUST be a weak reference to prevent UI freeze on the presentation modally - weak var viewcontroller: FloatingPanelController! + weak var viewcontroller: FloatingPanelController? let surfaceView: FloatingPanelSurfaceView let backdropView: FloatingPanelBackdropView @@ -25,7 +25,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { } private(set) var state: FloatingPanelPosition = .hidden { - didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) } + didSet { + if let vc = viewcontroller { + vc.delegate?.floatingPanelDidChangePosition(vc) + } + } } private var isBottomState: Bool { @@ -89,6 +93,10 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { } private func move(from: FloatingPanelPosition, to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) { + guard let vc = viewcontroller else { + completion?() + return + } if state != layoutAdapter.topMostState { lockScrollView() } @@ -98,11 +106,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { let animator: UIViewPropertyAnimator switch (from, to) { case (.hidden, let to): - animator = behavior.addAnimator(self.viewcontroller, to: to) + animator = behavior.addAnimator(vc, to: to) case (let from, .hidden): - animator = behavior.removeAnimator(self.viewcontroller, from: from) + animator = behavior.removeAnimator(vc, from: from) case (let from, let to): - animator = behavior.moveAnimator(self.viewcontroller, from: from, to: to) + animator = behavior.moveAnimator(vc, from: from, to: to) } animator.addAnimations { [weak self] in @@ -167,7 +175,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { /* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */ - if viewcontroller.delegate?.floatingPanel(viewcontroller, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false { + if let vc = viewcontroller, + vc.delegate?.floatingPanel(vc, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false { return true } @@ -214,11 +223,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { } } - if viewcontroller.delegate?.floatingPanel(viewcontroller, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false { + if let vc = viewcontroller, + vc.delegate?.floatingPanel(vc, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false { return false } - switch otherGestureRecognizer { case is UIPanGestureRecognizer, is UISwipeGestureRecognizer, @@ -316,7 +325,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { } if interactionInProgress == false, - viewcontroller.delegate?.floatingPanelShouldBeginDragging(viewcontroller) == false { + let vc = viewcontroller, + vc.delegate?.floatingPanelShouldBeginDragging(vc) == false { return } @@ -429,7 +439,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { let didMove = (pre != surfaceView.frame.minY) guard didMove else { return } - viewcontroller.delegate?.floatingPanelDidMove(viewcontroller) + if let vc = viewcontroller { + vc.delegate?.floatingPanelDidMove(vc) + } } private func allowsTopBuffer(for translationY: CGFloat) -> Bool { @@ -447,11 +459,12 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { // Prevent stretching a view having a constraint to SafeArea.bottom in an overflow // from the full position because SafeArea is global in a screen. private func preserveContentVCLayoutIfNeeded() { + guard let vc = viewcontroller else { return } // Must include topY if (surfaceView.frame.minY <= layoutAdapter.topY) { if !disabledBottomAutoLayout { - viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in - switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor { + vc.contentViewController?.view?.constraints.forEach({ (const) in + switch vc.contentViewController?.layoutGuide.bottomAnchor { case const.firstAnchor: (const.secondItem as? UIView)?.disableAutoLayout() const.isActive = false @@ -466,8 +479,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { disabledBottomAutoLayout = true } else { if disabledBottomAutoLayout { - viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in - switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor { + vc.contentViewController?.view?.constraints.forEach({ (const) in + switch vc.contentViewController?.layoutGuide.bottomAnchor { case const.firstAnchor: (const.secondItem as? UIView)?.enableAutoLayout() const.isActive = true @@ -508,17 +521,18 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(abs(velocity.y)/distance, behavior.removalVelocity)) : .zero - if shouldStartRemovalAnimation(with: velocityVector) { - - viewcontroller.delegate?.floatingPanelDidEndDraggingToRemove(viewcontroller, withVelocity: velocity) - self.startRemovalAnimation(with: velocityVector) { [weak self] in + if shouldStartRemovalAnimation(with: velocityVector), let vc = viewcontroller { + vc.delegate?.floatingPanelDidEndDraggingToRemove(vc, withVelocity: velocity) + startRemovalAnimation(vc, with: velocityVector) { [weak self] in self?.finishRemovalAnimation() } return } } - viewcontroller.delegate?.floatingPanelDidEndDragging(viewcontroller, withVelocity: velocity, targetPosition: targetPosition) + if let vc = viewcontroller { + vc.delegate?.floatingPanelDidEndDragging(vc, withVelocity: velocity, targetPosition: targetPosition) + } // Workaround: Disable a tracking scroll to prevent bouncing a scroll content in a panel animating let isScrollEnabled = scrollView?.isScrollEnabled @@ -551,8 +565,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { return true } - private func startRemovalAnimation(with velocityVector: CGVector, completion: (() -> Void)?) { - let animator = self.behavior.removalInteractionAnimator(self.viewcontroller, with: velocityVector) + private func startRemovalAnimation(_ vc: FloatingPanelController, with velocityVector: CGVector, completion: (() -> Void)?) { + let animator = behavior.removalInteractionAnimator(vc, with: velocityVector) animator.addAnimations { [weak self] in self?.updateLayout(to: .hidden) @@ -591,7 +605,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { initialTranslationY = translation.y - viewcontroller.delegate?.floatingPanelWillBeginDragging(viewcontroller) + if let vc = viewcontroller { + vc.delegate?.floatingPanelWillBeginDragging(vc) + } layoutAdapter.startInteraction(at: state) @@ -623,12 +639,14 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { private func startAnimation(to targetPosition: FloatingPanelPosition, at distance: CGFloat, with velocity: CGPoint) { log.debug("startAnimation to \(targetPosition) -- distance = \(distance), velocity = \(velocity.y)") + guard let vc = viewcontroller else { return } isDecelerating = true - viewcontroller.delegate?.floatingPanelWillBeginDecelerating(viewcontroller) + + vc.delegate?.floatingPanelWillBeginDecelerating(vc) let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(abs(velocity.y)/distance, 30.0)) : .zero - let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector) + let animator = behavior.interactionAnimator(vc, to: targetPosition, with: velocityVector) animator.addAnimations { [weak self] in guard let `self` = self else { return } self.state = targetPosition @@ -648,7 +666,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { self.isDecelerating = false self.animator = nil - self.viewcontroller.delegate?.floatingPanelDidEndDecelerating(self.viewcontroller) + if let vc = viewcontroller { + vc.delegate?.floatingPanelDidEndDecelerating(vc) + } if let scrollView = scrollView { log.debug("finishAnimation -- scroll offset = \(scrollView.contentOffset)") @@ -705,6 +725,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { } private func targetPosition(with velocity: CGPoint) -> (FloatingPanelPosition) { + guard let vc = viewcontroller else { return state } let currentY = surfaceView.frame.minY let supportedPositions = layoutAdapter.supportedPositions @@ -747,7 +768,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { fatalError("Now .hidden must not be used for a user interaction") } - let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: nextState), 1.0), 0.0) + let redirectionalProgress = max(min(behavior.redirectionalProgress(vc, from: state, to: nextState), 1.0), 0.0) let th1: CGFloat let th2: CGFloat @@ -760,7 +781,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { th2 = bottomY - (bottomY - middleY) * redirectionalProgress } - let decelerationRate = behavior.momentumProjectionRate(viewcontroller) + let decelerationRate = behavior.momentumProjectionRate(vc) let baseY = abs(bottomY - topY) let vecY = velocity.y / baseY @@ -770,7 +791,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { case .. FloatingPanelPosition { + guard let vc = viewcontroller else { return state } assert(positions.count == 2) let top = positions[0] @@ -825,11 +847,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate { let bottomY = layoutAdapter.positionY(for: bottom) let target = top == state ? bottom : top - let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: target), 1.0), 0.0) + let redirectionalProgress = max(min(behavior.redirectionalProgress(vc, from: state, to: target), 1.0), 0.0) let th = topY + (bottomY - topY) * redirectionalProgress - let decelerationRate = behavior.momentumProjectionRate(viewcontroller) + let decelerationRate = behavior.momentumProjectionRate(vc) let pY = project(initialVelocity: velocity.y, decelerationRate: decelerationRate) + currentY switch currentY {