Files

131 lines
5.5 KiB
Swift

// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
/// An interface for generating behavior information to fine-tune the behavior of a panel.
@objc
public protocol FloatingPanelBehavior {
/// A floating-point value that determines the rate of oscillation magnitude reduction after the user lifts their finger.
///
/// The oscillation magnitude to attract a panel to an anchor can be adjusted this value between 0.979 and 1.0
/// in increments of 0.001. When this value is around 0.979, the attraction uses a critically damped spring system.
/// When this value is between 0.978 and 1.0, it uses a underdamped spring system with a damping ratio computed by
/// this value. You shouldn't return less than 0.979 because the system is overdamped. If the pan gesture's velocity
/// is less than 300, this value is ignored and a panel applies a critically damped system.
@objc optional
var springDecelerationRate: CGFloat { get }
/// A floating-point value that determines the approximate time until a panel stops to an anchor after the user lifts their finger.
@objc optional
var springResponseTime: CGFloat { get }
/// Returns a deceleration rate to calculate a target position projected a dragging momentum.
///
/// The default implementation of this method returns the normal deceleration rate of UIScrollView.
@objc optional
var momentumProjectionRate: CGFloat { get }
/// Asks the behavior if a panel should project a momentum of a user interaction to move the
/// proposed state.
///
/// The default implementation of this method returns `false`. This method is called for called
/// for all states defined by the current layout object.
@objc optional
func shouldProjectMomentum(
_ fpc: FloatingPanelController,
to proposedState: FloatingPanelState
) -> Bool
/// Returns the progress to redirect to the previous position.
///
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates a panel is impossible to move to the next position. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
@objc optional
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat
/// Asks the behavior whether the rubber band effect is enabled in moving over a given edge of the surface view.
///
/// This method allows a panel to activate the rubber band effect to a given edge of the surface view. By default, the effect is disabled.
@objc optional
func allowsRubberBanding(for edge: UIRectEdge) -> Bool
/// Returns the velocity threshold for the default interactive removal gesture.
///
/// In case ``FloatingPanel/FloatingPanelControllerDelegate/floatingPanel(_:shouldRemoveAt:with:)`` is implemented, this value will not be used. The default value of ``FloatingPanelDefaultBehavior`` is 5.5
@objc optional
var removalInteractionVelocityThreshold: CGFloat { get }
}
/// The default behavior object for a panel
///
/// This behavior object is fine-tuned to behave as a search panel(card) in Apple Maps on iPhone portrait orientation.
open class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
public init() {}
open var springDecelerationRate: CGFloat {
return UIScrollView.DecelerationRate.fast.rawValue + 0.001
}
open var springResponseTime: CGFloat {
return 0.4
}
open var momentumProjectionRate: CGFloat {
return UIScrollView.DecelerationRate.normal.rawValue
}
open func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat {
return 0.5
}
open func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
return false
}
open var removalInteractionVelocityThreshold: CGFloat = 5.5
}
class BehaviorAdapter {
unowned let vc: FloatingPanelController
fileprivate var behavior: FloatingPanelBehavior
init(vc: FloatingPanelController, behavior: FloatingPanelBehavior) {
self.vc = vc
self.behavior = behavior
}
var springDecelerationRate: CGFloat {
behavior.springDecelerationRate ?? FloatingPanelDefaultBehavior().springDecelerationRate
}
var springResponseTime: CGFloat {
behavior.springResponseTime ?? FloatingPanelDefaultBehavior().springResponseTime
}
var momentumProjectionRate: CGFloat {
behavior.momentumProjectionRate ?? FloatingPanelDefaultBehavior().momentumProjectionRate
}
var removalInteractionVelocityThreshold: CGFloat {
behavior.removalInteractionVelocityThreshold ?? FloatingPanelDefaultBehavior().removalInteractionVelocityThreshold
}
func redirectionalProgress(from: FloatingPanelState, to: FloatingPanelState) -> CGFloat {
behavior.redirectionalProgress?(vc, from: from, to: to) ?? FloatingPanelDefaultBehavior().redirectionalProgress(vc,from: from, to: to)
}
func shouldProjectMomentum(to: FloatingPanelState) -> Bool {
behavior.shouldProjectMomentum?(vc, to: to) ?? false
}
func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
behavior.allowsRubberBanding?(for: edge) ?? false
}
}
extension FloatingPanelController {
var _behavior: FloatingPanelBehavior {
get { floatingPanel.behaviorAdapter.behavior }
set { floatingPanel.behaviorAdapter.behavior = newValue}
}
}