From b9b7f940b95e3cb1382fa2d6d2835d88b4a9514c Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Tue, 9 Jul 2019 20:06:16 +0900 Subject: [PATCH] Prevent 'unexpectedly found nil' fatal error Use FloatingPanel.viewcontroller as an optional value instead of an implicitly unwrapping optional one. --- Framework/Sources/FloatingPanel.swift | 88 +++++++++++++++++---------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/Framework/Sources/FloatingPanel.swift b/Framework/Sources/FloatingPanel.swift index f336f43..fc40554 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, UIScrollViewDelegate { // 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, UIScrollViewDelegate } private(set) var state: FloatingPanelPosition = .hidden { - didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) } + didSet { + if let vc = viewcontroller { + vc.delegate?.floatingPanelDidChangePosition(vc) + } + } } private var isBottomState: Bool { @@ -91,6 +95,10 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate } private func move(from: FloatingPanelPosition, to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) { + guard let vc = viewcontroller else { + completion?() + return + } if state != layoutAdapter.topMostState { lockScrollView() } @@ -100,11 +108,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 @@ -165,7 +173,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate /* 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 } @@ -212,11 +221,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate } } - 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, @@ -314,7 +323,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate } if interactionInProgress == false, - viewcontroller.delegate?.floatingPanelShouldBeginDragging(viewcontroller) == false { + let vc = viewcontroller, + vc.delegate?.floatingPanelShouldBeginDragging(vc) == false { return } @@ -427,7 +437,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 { @@ -445,11 +457,12 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate // 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 @@ -464,8 +477,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 @@ -506,17 +519,18 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 @@ -549,8 +563,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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) @@ -589,7 +603,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate initialTranslationY = translation.y - viewcontroller.delegate?.floatingPanelWillBeginDragging(viewcontroller) + if let vc = viewcontroller { + vc.delegate?.floatingPanelWillBeginDragging(vc) + } layoutAdapter.startInteraction(at: state) @@ -621,12 +637,14 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 @@ -646,7 +664,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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)") @@ -715,6 +735,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate } private func targetPosition(with velocity: CGPoint) -> (FloatingPanelPosition) { + guard let vc = viewcontroller else { return state } let currentY = surfaceView.frame.minY let supportedPositions = layoutAdapter.supportedPositions @@ -757,7 +778,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 @@ -770,7 +791,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 @@ -780,7 +801,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate case .. FloatingPanelPosition { + guard let vc = viewcontroller else { return state } assert(positions.count == 2) let top = positions[0] @@ -835,11 +857,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate 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 {