Merge pull request #231 from SCENEE/prevent-found-nil-error

Prevent 'unexpectedly found nil' fatal error
This commit is contained in:
Shin Yamamoto
2019-07-09 21:53:40 +09:00
committed by GitHub
+55 -33
View File
@@ -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 ..<th1:
switch pY {
case bottomY...:
return behavior.shouldProjectMomentum(viewcontroller, for: .tip) ? .tip : .half
return behavior.shouldProjectMomentum(vc, for: .tip) ? .tip : .half
case middleY...:
return .half
case topY...:
@@ -781,7 +802,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
case ...middleY:
switch pY {
case bottomY...:
return behavior.shouldProjectMomentum(viewcontroller, for: .tip) ? .tip : .half
return behavior.shouldProjectMomentum(vc, for: .tip) ? .tip : .half
case middleY...:
return .half
case topY...:
@@ -798,7 +819,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
case topY...:
return .half
default:
return behavior.shouldProjectMomentum(viewcontroller, for: .full) ? .full : .half
return behavior.shouldProjectMomentum(vc, for: .full) ? .full : .half
}
default:
switch pY {
@@ -809,13 +830,14 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
case topY...:
return .half
default:
return behavior.shouldProjectMomentum(viewcontroller, for: .full) ? .full : .half
return behavior.shouldProjectMomentum(vc, for: .full) ? .full : .half
}
}
}
}
private func targetPosition(from positions: [FloatingPanelPosition], at currentY: CGFloat, velocity: CGPoint) -> 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 {