Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac675d505b | |||
| 53bb530537 | |||
| d6d8834423 |
@@ -9,6 +9,34 @@ on:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
SWIFT_FORMAT: build-tools/.build/release/swift-format
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- xcode: "14.3"
|
||||
runsOn: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache swift-format
|
||||
id: cache_swift-format
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache_swift-format
|
||||
with:
|
||||
path: ${{ env.SWIFT_FORMAT }}
|
||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('build-tools/Package.resolved') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('build-tools/Package.resolved') }}
|
||||
- name: Run swift-format
|
||||
run: |
|
||||
[[ -f $SWIFT_FORMAT ]] || xcrun swift build --package-path "build-tools" -c release
|
||||
$SWIFT_FORMAT lint --configuration .swift-format -s -r Sources Tests
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"fileScopedDeclarationPrivacy" : {
|
||||
"accessLevel" : "private"
|
||||
},
|
||||
"indentation" : {
|
||||
"spaces" : 4
|
||||
},
|
||||
"indentConditionalCompilationBlocks" : false,
|
||||
"indentSwitchCaseLabels" : false,
|
||||
"lineBreakAroundMultilineExpressionChainComponents" : false,
|
||||
"lineBreakBeforeControlFlowKeywords" : false,
|
||||
"lineBreakBeforeEachArgument" : false,
|
||||
"lineBreakBeforeEachGenericRequirement" : false,
|
||||
"lineLength" : 240,
|
||||
"maximumBlankLines" : 1,
|
||||
"prioritizeKeepingFunctionOutputTogether" : false,
|
||||
"respectsExistingLineBreaks" : true,
|
||||
"rules" : {
|
||||
"AllPublicDeclarationsHaveDocumentation" : false,
|
||||
"AlwaysUseLowerCamelCase" : false,
|
||||
"AmbiguousTrailingClosureOverload" : true,
|
||||
"BeginDocumentationCommentWithOneLineSummary" : false,
|
||||
"DoNotUseSemicolons" : true,
|
||||
"DontRepeatTypeInStaticProperties" : true,
|
||||
"FileScopedDeclarationPrivacy" : true,
|
||||
"FullyIndirectEnum" : true,
|
||||
"GroupNumericLiterals" : true,
|
||||
"IdentifiersMustBeASCII" : true,
|
||||
"NeverForceUnwrap" : true,
|
||||
"NeverUseForceTry" : true,
|
||||
"NeverUseImplicitlyUnwrappedOptionals" : false,
|
||||
"NoAccessLevelOnExtensionDeclaration" : true,
|
||||
"NoBlockComments" : true,
|
||||
"NoCasesWithOnlyFallthrough" : true,
|
||||
"NoEmptyTrailingClosureParentheses" : true,
|
||||
"NoLabelsInCasePatterns" : true,
|
||||
"NoLeadingUnderscores" : false,
|
||||
"NoParensAroundConditions" : true,
|
||||
"NoVoidReturnOnFunctionSignature" : true,
|
||||
"OneCasePerLine" : true,
|
||||
"OneVariableDeclarationPerLine" : true,
|
||||
"OnlyOneTrailingClosureArgument" : true,
|
||||
"OrderedImports" : true,
|
||||
"ReturnVoidInsteadOfEmptyTuple" : true,
|
||||
"UseEarlyExits" : false,
|
||||
"UseLetInEveryBoundCaseVariable" : true,
|
||||
"UseShorthandTypeNames" : true,
|
||||
"UseSingleLinePropertyGetter" : true,
|
||||
"UseSynthesizedInitializer" : true,
|
||||
"UseTripleSlashForDocumentationComments" : true,
|
||||
"UseWhereClausesInForLoops" : false,
|
||||
"ValidateDocumentationComments" : false
|
||||
},
|
||||
"tabWidth" : 8,
|
||||
"version" : 1
|
||||
}
|
||||
@@ -190,6 +190,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
54B58FC929EB95880009567E /* PBXTargetDependency */,
|
||||
);
|
||||
name = FloatingPanel;
|
||||
productName = FloatingModalController;
|
||||
@@ -242,6 +243,8 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 545DB9B72151169500CA77B8;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 545DB9C22151169500CA77B8 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -315,6 +318,10 @@
|
||||
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
|
||||
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
54B58FC929EB95880009567E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 54B58FC829EB95880009567E /* SwiftFormatBuildTool */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -694,6 +701,13 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
54B58FC829EB95880009567E /* SwiftFormatBuildTool */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "plugin:SwiftFormatBuildTool";
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
}
|
||||
|
||||
+19
-19
@@ -12,43 +12,43 @@ public protocol FloatingPanelBehavior {
|
||||
/// 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 }
|
||||
@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 }
|
||||
@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 }
|
||||
@objc
|
||||
optional var momentumProjectionRate: CGFloat { get }
|
||||
|
||||
/// Asks the behavior if a panel should project a momentum of a user interaction to move the proposed position.
|
||||
///
|
||||
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
|
||||
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelState.tip` or `FloatingPanelState.full`.
|
||||
@objc optional
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool
|
||||
@objc
|
||||
optional func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: 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
|
||||
@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
|
||||
|
||||
@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 }
|
||||
@objc
|
||||
optional var removalInteractionVelocityThreshold: CGFloat { get }
|
||||
}
|
||||
|
||||
/// The default behavior object for a panel
|
||||
@@ -76,7 +76,7 @@ open class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
|
||||
open func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
open var removalInteractionVelocityThreshold: CGFloat = 5.5
|
||||
}
|
||||
|
||||
@@ -100,13 +100,13 @@ class BehaviorAdapter {
|
||||
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)
|
||||
behavior.redirectionalProgress?(vc, from: from, to: to) ?? FloatingPanelDefaultBehavior().redirectionalProgress(vc, from: from, to: to)
|
||||
}
|
||||
|
||||
func shouldProjectMomentum(to: FloatingPanelState) -> Bool {
|
||||
@@ -121,6 +121,6 @@ class BehaviorAdapter {
|
||||
extension FloatingPanelController {
|
||||
var _behavior: FloatingPanelBehavior {
|
||||
get { floatingPanel.behaviorAdapter.behavior }
|
||||
set { floatingPanel.behaviorAdapter.behavior = newValue}
|
||||
set { floatingPanel.behaviorAdapter.behavior = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
+68
-60
@@ -7,78 +7,76 @@ import UIKit
|
||||
/// dragging, attracting a panel, layout of a panel and the content, and transition animations.
|
||||
@objc public protocol FloatingPanelControllerDelegate {
|
||||
/// Returns a FloatingPanelLayout object. If you use the default one, you can use a `FloatingPanelBottomLayout` object.
|
||||
@objc(floatingPanel:layoutForTraitCollection:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout
|
||||
@objc(floatingPanel:layoutForTraitCollection:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout
|
||||
|
||||
/// Returns a FloatingPanelLayout object. If you use the default one, you can use a `FloatingPanelBottomLayout` object.
|
||||
@objc(floatingPanel:layoutForSize:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout
|
||||
@objc(floatingPanel:layoutForSize:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to add/present the panel to a position.
|
||||
///
|
||||
/// Default is the spring animation with 0.25 secs.
|
||||
@objc(floatingPanel:animatorForPresentingToState:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator
|
||||
@objc(floatingPanel:animatorForPresentingToState:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to remove/dismiss a panel from a position.
|
||||
///
|
||||
/// Default is the spring animator with 0.25 secs.
|
||||
@objc(floatingPanel:animatorForDismissingWithVelocity:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator
|
||||
@objc(floatingPanel:animatorForDismissingWithVelocity:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator
|
||||
|
||||
/// Called when a panel has changed to a new state.
|
||||
///
|
||||
/// This can be called inside an animation block for presenting, dismissing a panel or moving a panel with your
|
||||
/// animation. So any view properties set inside this function will be automatically animated alongside a panel.
|
||||
@objc optional
|
||||
func floatingPanelDidChangeState(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelDidChangeState(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
|
||||
@objc optional
|
||||
func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool
|
||||
@objc
|
||||
optional func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool
|
||||
|
||||
/// Called while the user drags the surface or the surface moves to a state anchor.
|
||||
@objc optional
|
||||
func floatingPanelDidMove(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelDidMove(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Called on start of dragging (may require some time and or distance to move)
|
||||
@objc optional
|
||||
func floatingPanelWillBeginDragging(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelWillBeginDragging(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Called on finger up if the user dragged. velocity is in points/second.
|
||||
@objc optional
|
||||
func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>)
|
||||
@objc
|
||||
optional func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>)
|
||||
|
||||
/// Called on finger up if the user dragged.
|
||||
///
|
||||
/// If `attract` is true, it will continue moving afterwards to a nearby state anchor.
|
||||
@objc optional
|
||||
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
|
||||
@objc
|
||||
optional func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
|
||||
|
||||
/// Called when it is about to be attracted to a state anchor.
|
||||
@objc optional
|
||||
func floatingPanelWillBeginAttracting(_ fpc: FloatingPanelController, to state: FloatingPanelState) // called on finger up as a panel are moving
|
||||
@objc
|
||||
optional func floatingPanelWillBeginAttracting(_ fpc: FloatingPanelController, to state: FloatingPanelState) // called on finger up as a panel are moving
|
||||
|
||||
/// Called when attracting it is completed.
|
||||
@objc optional
|
||||
func floatingPanelDidEndAttracting(_ fpc: FloatingPanelController) // called when a panel stops
|
||||
@objc
|
||||
optional func floatingPanelDidEndAttracting(_ fpc: FloatingPanelController) // called when a panel stops
|
||||
|
||||
/// Asks the delegate whether a panel should be removed when dragging ended at the specified location
|
||||
///
|
||||
/// This delegate method is called only where ``FloatingPanel/FloatingPanelController/isRemovalInteractionEnabled`` is `true`.
|
||||
/// The velocity vector is calculated from the distance to a point of the hidden state and the pan gesture's velocity.
|
||||
@objc(floatingPanel:shouldRemoveAtLocation:withVelocity:)
|
||||
optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, shouldRemoveAt location: CGPoint, with velocity: CGVector) -> Bool
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, shouldRemoveAt location: CGPoint, with velocity: CGVector) -> Bool
|
||||
|
||||
/// Called on start to remove its view controller from the parent view controller.
|
||||
@objc(floatingPanelWillRemove:)
|
||||
optional
|
||||
func floatingPanelWillRemove(_ fpc: FloatingPanelController)
|
||||
optional func floatingPanelWillRemove(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Called when a panel is removed from the parent view controller.
|
||||
@objc optional
|
||||
func floatingPanelDidRemove(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelDidRemove(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Asks the delegate for a content offset of the tracking scroll view to be pinned when a panel moves
|
||||
///
|
||||
@@ -88,8 +86,7 @@ import UIKit
|
||||
///
|
||||
/// This method will not be called if the controller doesn't track any scroll view.
|
||||
@objc(floatingPanel:contentOffsetForPinningScrollView:)
|
||||
optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint
|
||||
}
|
||||
|
||||
///
|
||||
@@ -114,9 +111,9 @@ open class FloatingPanelController: UIViewController {
|
||||
}
|
||||
|
||||
/// The delegate of a panel controller object.
|
||||
@objc
|
||||
public weak var delegate: FloatingPanelControllerDelegate?{
|
||||
didSet{
|
||||
@objc
|
||||
public weak var delegate: FloatingPanelControllerDelegate? {
|
||||
didSet {
|
||||
didUpdateDelegate()
|
||||
}
|
||||
}
|
||||
@@ -193,7 +190,7 @@ open class FloatingPanelController: UIViewController {
|
||||
/// The behavior for determining the adjusted content offsets.
|
||||
///
|
||||
/// This property specifies how the content area of the tracking scroll view is modified using ``adjustedContentInsets``. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always.
|
||||
@objc
|
||||
@objc
|
||||
public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always
|
||||
|
||||
/// A Boolean value that determines whether the removal interaction is enabled.
|
||||
@@ -228,7 +225,7 @@ open class FloatingPanelController: UIViewController {
|
||||
private var _contentViewController: UIViewController?
|
||||
|
||||
private(set) var floatingPanel: Core!
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var safeAreaInsetsObservation: NSKeyValueObservation?
|
||||
private let modalTransition = ModalTransition()
|
||||
|
||||
@@ -267,7 +264,7 @@ open class FloatingPanelController: UIViewController {
|
||||
floatingPanel = Core(self, layout: initialLayout, behavior: initialBehavior)
|
||||
}
|
||||
|
||||
private func didUpdateDelegate(){
|
||||
private func didUpdateDelegate() {
|
||||
if let layout = delegate?.floatingPanel?(self, layoutFor: traitCollection) {
|
||||
_layout = layout
|
||||
}
|
||||
@@ -373,7 +370,7 @@ open class FloatingPanelController: UIViewController {
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
guard
|
||||
preSafeAreaInsets != safeAreaInsets
|
||||
else { return }
|
||||
else { return }
|
||||
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
|
||||
@@ -454,17 +451,21 @@ open class FloatingPanelController: UIViewController {
|
||||
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
|
||||
}
|
||||
|
||||
move(to: floatingPanel.layoutAdapter.initialState,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
move(
|
||||
to: floatingPanel.layoutAdapter.initialState,
|
||||
animated: animated,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/// Hides the surface view to the hidden position
|
||||
@objc(hide:completion:)
|
||||
public func hide(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
move(to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
move(
|
||||
to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/// Adds the view managed by the controller as a child of the specified view controller.
|
||||
@@ -493,14 +494,14 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
parent.addChild(self)
|
||||
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
self.view.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 0.0),
|
||||
self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0),
|
||||
self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0),
|
||||
self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
|
||||
])
|
||||
])
|
||||
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
@@ -594,7 +595,7 @@ open class FloatingPanelController: UIViewController {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// [Experimental] Allows the panel to move as its tracking scroll view bounces.
|
||||
///
|
||||
/// This method must be called in the delegate method, `UIScrollViewDelegate.scrollViewDidScroll(_:)`,
|
||||
@@ -674,21 +675,27 @@ extension FloatingPanelController {
|
||||
if let animator = delegate?.floatingPanel?(self, animatorForPresentingTo: to) {
|
||||
return animator
|
||||
}
|
||||
let timingParameters = UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25)
|
||||
return UIViewPropertyAnimator(duration: 0.0,
|
||||
timingParameters: timingParameters)
|
||||
let timingParameters = UISpringTimingParameters(
|
||||
decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25)
|
||||
return UIViewPropertyAnimator(
|
||||
duration: 0.0,
|
||||
timingParameters: timingParameters
|
||||
)
|
||||
}
|
||||
|
||||
func animatorForDismissing(with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
if let animator = delegate?.floatingPanel?(self, animatorForDismissingWith: velocity) {
|
||||
return animator
|
||||
}
|
||||
let timingParameters = UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25,
|
||||
initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0.0,
|
||||
timingParameters: timingParameters)
|
||||
let timingParameters = UISpringTimingParameters(
|
||||
decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25,
|
||||
initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(
|
||||
duration: 0.0,
|
||||
timingParameters: timingParameters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,10 +705,11 @@ private var originalDismissImp: IMP?
|
||||
private typealias __dismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Void = {
|
||||
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
|
||||
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
|
||||
if let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:))),
|
||||
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:))) {
|
||||
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
|
||||
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:)))
|
||||
{
|
||||
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
+118
-99
@@ -43,7 +43,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
fileprivate var isSuspended: Bool = false // Prevent a memory leak in the modal transition
|
||||
fileprivate var isSuspended: Bool = false // Prevent a memory leak in the modal transition
|
||||
fileprivate var transitionAnimator: UIViewPropertyAnimator?
|
||||
fileprivate var moveAnimator: NumericSpringAnimator?
|
||||
|
||||
@@ -148,7 +148,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let shouldDoubleLayout = from == .hidden
|
||||
let shouldDoubleLayout =
|
||||
from == .hidden
|
||||
&& surfaceView.hasStackView()
|
||||
&& layoutAdapter.isIntrinsicAnchor(state: to)
|
||||
|
||||
@@ -228,7 +229,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func getBackdropAlpha(at cur: CGFloat, with translation: CGFloat) -> CGFloat {
|
||||
/* log.debug("currentY: \(currentY) translation: \(translation)") */
|
||||
// log.debug("currentY: \(currentY) translation: \(translation)")
|
||||
let forwardY = (translation >= 0)
|
||||
|
||||
let segment = layoutAdapter.segment(at: cur, forward: forwardY)
|
||||
@@ -248,30 +249,32 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if pre == next {
|
||||
return preAlpha
|
||||
}
|
||||
return preAlpha + max(min(1.0, 1.0 - (next - cur) / (next - pre) ), 0.0) * (nextAlpha - preAlpha)
|
||||
return preAlpha + max(min(1.0, 1.0 - (next - cur) / (next - pre)), 0.0) * (nextAlpha - preAlpha)
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
public func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
if let result = panGestureRecognizer.delegateProxy?.gestureRecognizer?(gestureRecognizer, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) {
|
||||
return result
|
||||
}
|
||||
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
/* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
|
||||
// log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer)
|
||||
|
||||
switch otherGestureRecognizer {
|
||||
case is FloatingPanelPanGestureRecognizer:
|
||||
// All visible panels' pan gesture should be recognized simultaneously.
|
||||
return true
|
||||
case is UIPanGestureRecognizer,
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
return true
|
||||
}
|
||||
@@ -297,8 +300,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if #available(iOS 11.0, *),
|
||||
otherGestureRecognizer.name == "_UISheetInteractionBackgroundDismissRecognizer" {
|
||||
if #available(iOS 11.0, *), otherGestureRecognizer.name == "_UISheetInteractionBackgroundDismissRecognizer" {
|
||||
// The dismiss gesture of a sheet modal should not begin until the pan gesture fails.
|
||||
return true
|
||||
}
|
||||
@@ -328,7 +330,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// On short contents scroll, `_UISwipeActionPanGestureRecognizer` blocks
|
||||
// the panel's pan gesture if not returns false
|
||||
if let scrollGestureRecognizers = scrollView.gestureRecognizers,
|
||||
scrollGestureRecognizers.contains(otherGestureRecognizer) {
|
||||
scrollGestureRecognizers.contains(otherGestureRecognizer)
|
||||
{
|
||||
switch otherGestureRecognizer {
|
||||
case scrollView.panGestureRecognizer:
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
@@ -350,12 +353,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
return true
|
||||
case is UIPanGestureRecognizer,
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
if #available(iOS 11.0, *),
|
||||
otherGestureRecognizer.name == "_UISheetInteractionBackgroundDismissRecognizer" {
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
if #available(iOS 11.0, *), otherGestureRecognizer.name == "_UISheetInteractionBackgroundDismissRecognizer" {
|
||||
// Should begin the pan gesture without waiting the dismiss gesture of a sheet modal.
|
||||
return false
|
||||
}
|
||||
@@ -387,13 +389,15 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
let insideMostExpandedAnchor = 0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)
|
||||
|
||||
log.debug("""
|
||||
log.debug(
|
||||
"""
|
||||
scroll gesture(\(state):\(panGesture.state)) -- \
|
||||
inside expanded anchor = \(insideMostExpandedAnchor), \
|
||||
interactionInProgress = \(interactionInProgress), \
|
||||
scroll offset = \(value(of: scrollView.contentOffset)), \
|
||||
location = \(value(of: location)), velocity = \(velocity)
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
let offsetDiff = value(of: scrollView.contentOffset - contentOffsetForPinning(of: scrollView))
|
||||
|
||||
@@ -479,19 +483,22 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
case panGestureRecognizer:
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
let translation = panGesture.translation(in: panGesture.view?.superview)
|
||||
let velocity = panGesture.velocity(in: panGesture.view)
|
||||
let location = panGesture.location(in: panGesture.view)
|
||||
|
||||
log.debug("""
|
||||
log.debug(
|
||||
"""
|
||||
panel gesture(\(state):\(panGesture.state)) -- \
|
||||
translation = \(value(of: translation)), \
|
||||
location = \(value(of: location)), \
|
||||
velocity = \(value(of: velocity))
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
if interactionInProgress == false, isAttracting == false,
|
||||
let vc = ownerVC, vc.delegate?.floatingPanelShouldBeginDragging?(vc) == false {
|
||||
let vc = ownerVC, vc.delegate?.floatingPanelShouldBeginDragging?(vc) == false
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
@@ -518,9 +525,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// Workaround: Prevent stopping the surface view b/w anchors if the pan gesture
|
||||
// doesn't pass through .changed state after an interruptible animator is interrupted.
|
||||
let diff = translation - .leastNonzeroMagnitude
|
||||
layoutAdapter.updateInteractiveEdgeConstraint(diff: value(of: diff),
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:))
|
||||
layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: value(of: diff),
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
}
|
||||
panningEnd(with: translation, velocity: velocity)
|
||||
default:
|
||||
@@ -573,7 +582,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
guard
|
||||
state == layoutAdapter.mostExpandedState, // When not top most(i.e. .full), don't scroll.
|
||||
interactionInProgress == false, // When interaction already in progress, don't scroll.
|
||||
interactionInProgress == false, // When interaction already in progress, don't scroll.
|
||||
0 == layoutAdapter.offsetFromMostExpandedAnchor
|
||||
else {
|
||||
return false
|
||||
@@ -591,8 +600,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
let scrollViewFrame = scrollView.convert(scrollView.bounds, to: surfaceView)
|
||||
guard
|
||||
scrollViewFrame.contains(initialLocation), // When the initial point not in scrollView, don't scroll.
|
||||
!surfaceView.grabberAreaContains(point) // When point within grabber area, don't scroll.
|
||||
scrollViewFrame.contains(initialLocation), // When the initial point not in scrollView, don't scroll.
|
||||
!surfaceView.grabberAreaContains(point) // When point within grabber area, don't scroll.
|
||||
else {
|
||||
return false
|
||||
}
|
||||
@@ -602,14 +611,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// after a panel moves from half/tip to full.
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
if offset < 0.0 {
|
||||
if offset < 0.0 {
|
||||
return true
|
||||
}
|
||||
if velocity >= 0 {
|
||||
return true
|
||||
}
|
||||
case .bottom, .right:
|
||||
if offset > 0.0 {
|
||||
if offset > 0.0 {
|
||||
return true
|
||||
}
|
||||
if velocity <= 0 {
|
||||
@@ -649,15 +658,17 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let diff = value(of: translation - initialTranslation)
|
||||
let next = pre + diff
|
||||
|
||||
layoutAdapter.updateInteractiveEdgeConstraint(diff: diff,
|
||||
scrollingContent: shouldScrollingContentInMoving(from: pre, to: next),
|
||||
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:))
|
||||
layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: diff,
|
||||
scrollingContent: shouldScrollingContentInMoving(from: pre, to: next),
|
||||
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
|
||||
let cur = value(of: layoutAdapter.surfaceLocation)
|
||||
|
||||
backdropView.alpha = getBackdropAlpha(at: cur, with: value(of: translation))
|
||||
|
||||
guard (pre != cur) else { return }
|
||||
guard pre != cur else { return }
|
||||
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelDidMove?(vc)
|
||||
@@ -672,23 +683,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if let scrollView = scrollView, scrollView.panGestureRecognizer.state == .changed {
|
||||
switch layoutAdapter.position {
|
||||
case .top:
|
||||
if pre > .zero, pre < next,
|
||||
scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
|
||||
if pre > .zero, pre < next, scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
|
||||
return true
|
||||
}
|
||||
case .left:
|
||||
if pre > .zero, pre < next,
|
||||
scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
|
||||
if pre > .zero, pre < next, scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
|
||||
return true
|
||||
}
|
||||
case .bottom:
|
||||
if pre > .zero, pre > next,
|
||||
scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
|
||||
if pre > .zero, pre > next, scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
|
||||
return true
|
||||
}
|
||||
case .right:
|
||||
if pre > .zero, pre > next,
|
||||
scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
|
||||
if pre > .zero, pre > next, scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -704,11 +711,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
stopScrollDeceleration = (0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)) // Projecting the dragging to the scroll dragging or not
|
||||
stopScrollDeceleration = (0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)) // Projecting the dragging to the scroll dragging or not
|
||||
if stopScrollDeceleration {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
|
||||
self.stopScrolling(at: self.initialScrollOffset)
|
||||
}
|
||||
}
|
||||
@@ -723,9 +730,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let distToHidden = CGFloat(abs(currentPos - layoutAdapter.position(for: .hidden)))
|
||||
switch layoutAdapter.position {
|
||||
case .top, .bottom:
|
||||
removalVector = (distToHidden != 0) ? CGVector(dx: 0.0, dy: velocity.y/distToHidden) : .zero
|
||||
removalVector = (distToHidden != 0) ? CGVector(dx: 0.0, dy: velocity.y / distToHidden) : .zero
|
||||
case .left, .right:
|
||||
removalVector = (distToHidden != 0) ? CGVector(dx: velocity.x/distToHidden, dy: 0.0) : .zero
|
||||
removalVector = (distToHidden != 0) ? CGVector(dx: velocity.x / distToHidden, dy: 0.0) : .zero
|
||||
}
|
||||
if shouldRemove(with: removalVector) {
|
||||
ownerVC?.remove()
|
||||
@@ -761,8 +768,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
startAttraction(to: targetPosition, with: velocity)
|
||||
|
||||
// Workaround: Reset `self.scrollView.isScrollEnabled`
|
||||
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState,
|
||||
let isScrollEnabled = isScrollEnabled {
|
||||
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState, let isScrollEnabled = isScrollEnabled {
|
||||
scrollView.isScrollEnabled = isScrollEnabled
|
||||
}
|
||||
}
|
||||
@@ -788,7 +794,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func startInteraction(with translation: CGPoint, at location: CGPoint) {
|
||||
/* Don't lock a scroll view to show a scroll indicator after hitting the top */
|
||||
// Don't lock a scroll view to show a scroll indicator after hitting the top
|
||||
log.debug("startInteraction -- translation = \(value(of: translation)), location = \(value(of: location))")
|
||||
guard interactionInProgress == false else { return }
|
||||
|
||||
@@ -887,21 +893,25 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
decelerationRate: behaviorAdapter.springDecelerationRate,
|
||||
responseTime: behaviorAdapter.springResponseTime,
|
||||
update: { [weak self] data in
|
||||
guard let self = self,
|
||||
let ownerVC = self.ownerVC // Ensure the owner vc is existing for `layoutAdapter.surfaceLocation`
|
||||
guard
|
||||
let self = self,
|
||||
let ownerVC = self.ownerVC // Ensure the owner vc is existing for `layoutAdapter.surfaceLocation`
|
||||
else { return }
|
||||
animationConstraint.constant = data.value
|
||||
let current = self.value(of: self.layoutAdapter.surfaceLocation)
|
||||
let translation = data.value - initialData.value
|
||||
self.backdropView.alpha = self.getBackdropAlpha(at: current, with: translation)
|
||||
ownerVC.notifyDidMove()
|
||||
},
|
||||
},
|
||||
completion: { [weak self] in
|
||||
guard let self = self,
|
||||
self.ownerVC != nil else { return }
|
||||
guard
|
||||
let self = self,
|
||||
self.ownerVC != nil
|
||||
else { return }
|
||||
self.updateLayout(to: targetPosition)
|
||||
completion()
|
||||
})
|
||||
}
|
||||
)
|
||||
moveAnimator?.startAnimation()
|
||||
state = targetPosition
|
||||
}
|
||||
@@ -920,11 +930,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
stopScrollDeceleration = false
|
||||
|
||||
log.debug("""
|
||||
log.debug(
|
||||
"""
|
||||
finishAnimation -- state = \(state) \
|
||||
surface location = \(layoutAdapter.surfaceLocation) \
|
||||
edge most position = \(layoutAdapter.surfaceLocation(for: layoutAdapter.mostExpandedState))
|
||||
""")
|
||||
"""
|
||||
)
|
||||
if finished, state == layoutAdapter.mostExpandedState, abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
unlockScrollView()
|
||||
}
|
||||
@@ -954,7 +966,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
let sortedPositions = layoutAdapter.sortedAnchorStatesByCoordinate
|
||||
|
||||
guard sortedPositions.count > 1 else {
|
||||
guard
|
||||
sortedPositions.count > 1,
|
||||
let firstPosition = sortedPositions.first,
|
||||
let lastPosition = sortedPositions.last
|
||||
else {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -972,13 +988,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
var fromPos: FloatingPanelState
|
||||
var toPos: FloatingPanelState
|
||||
|
||||
let (lowerPos, upperPos) = (segment.lower ?? sortedPositions.first!, segment.upper ?? sortedPositions.last!)
|
||||
let (lowerPos, upperPos) = (segment.lower ?? firstPosition, segment.upper ?? lastPosition)
|
||||
(fromPos, toPos) = forwardY ? (lowerPos, upperPos) : (upperPos, lowerPos)
|
||||
|
||||
if behaviorAdapter.shouldProjectMomentum(to: toPos) == false {
|
||||
log.debug("targetPosition -- negate projection: distance = \(distance)")
|
||||
let segment = layoutAdapter.segment(at: currentY, forward: forwardY)
|
||||
var (lowerPos, upperPos) = (segment.lower ?? sortedPositions.first!, segment.upper ?? sortedPositions.last!)
|
||||
var (lowerPos, upperPos) = (segment.lower ?? firstPosition, segment.upper ?? lastPosition)
|
||||
// Equate the segment out of {top,bottom} most state to the {top,bottom} most segment
|
||||
if lowerPos == upperPos {
|
||||
if forwardY {
|
||||
@@ -1117,9 +1133,11 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
}
|
||||
set {
|
||||
guard newValue is Core else {
|
||||
let exception = NSException(name: .invalidArgumentException,
|
||||
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate. Use 'delegateProxy' property.",
|
||||
userInfo: nil)
|
||||
let exception = NSException(
|
||||
name: .invalidArgumentException,
|
||||
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate. Use 'delegateProxy' property.",
|
||||
userInfo: nil
|
||||
)
|
||||
exception.raise()
|
||||
return
|
||||
}
|
||||
@@ -1132,7 +1150,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
/// If an object adopting `UIGestureRecognizerDelegate` is set, the delegate methods are proxied to it.
|
||||
public weak var delegateProxy: UIGestureRecognizerDelegate? {
|
||||
didSet {
|
||||
self.delegate = floatingPanel // Update the cached IMP
|
||||
self.delegate = floatingPanel // Update the cached IMP
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1148,13 +1166,13 @@ private class NumericSpringAnimator: NSObject {
|
||||
private class UnfairLock {
|
||||
var unfairLock = os_unfair_lock()
|
||||
func lock() {
|
||||
os_unfair_lock_lock(&unfairLock);
|
||||
os_unfair_lock_lock(&unfairLock)
|
||||
}
|
||||
func tryLock() -> Bool {
|
||||
return os_unfair_lock_trylock(&unfairLock);
|
||||
return os_unfair_lock_trylock(&unfairLock)
|
||||
}
|
||||
func unlock() {
|
||||
os_unfair_lock_unlock(&unfairLock);
|
||||
os_unfair_lock_unlock(&unfairLock)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1174,21 +1192,23 @@ private class NumericSpringAnimator: NSObject {
|
||||
private let update: ((_ data: Data) -> Void)
|
||||
private let completion: (() -> Void)
|
||||
|
||||
init(initialData: Data,
|
||||
target: CGFloat,
|
||||
displayScale: CGFloat,
|
||||
decelerationRate: CGFloat,
|
||||
responseTime: CGFloat,
|
||||
update: @escaping ((_ data: Data) -> Void),
|
||||
completion: @escaping (() -> Void)) {
|
||||
init(
|
||||
initialData: Data,
|
||||
target: CGFloat,
|
||||
displayScale: CGFloat,
|
||||
decelerationRate: CGFloat,
|
||||
responseTime: CGFloat,
|
||||
update: @escaping ((_ data: Data) -> Void),
|
||||
completion: @escaping (() -> Void)
|
||||
) {
|
||||
|
||||
self.data = initialData
|
||||
self.target = target
|
||||
self.displayScale = displayScale
|
||||
|
||||
let frequency = 1 / responseTime // oscillation frequency
|
||||
let duration: CGFloat = 0.001 // millisecond
|
||||
self.zeta = abs(initialData.velocity) > 300 ? CoreGraphics.log(decelerationRate) / (-2.0 * .pi * frequency * duration) : 1.0
|
||||
let frequency = 1 / responseTime // oscillation frequency
|
||||
let duration: CGFloat = 0.001 // millisecond
|
||||
self.zeta = abs(initialData.velocity) > 300 ? CoreGraphics.log(decelerationRate) / (-2.0 * .pi * frequency * duration) : 1.0
|
||||
self.omega = 2.0 * .pi * frequency
|
||||
|
||||
self.update = update
|
||||
@@ -1196,7 +1216,7 @@ private class NumericSpringAnimator: NSObject {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func startAnimation() -> Bool{
|
||||
func startAnimation() -> Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
@@ -1232,29 +1252,28 @@ private class NumericSpringAnimator: NSObject {
|
||||
let pre = data.value
|
||||
var cur = pre
|
||||
var velocity = data.velocity
|
||||
spring(x: &cur,
|
||||
v: &velocity,
|
||||
xt: target,
|
||||
zeta: zeta,
|
||||
omega: omega,
|
||||
h: CGFloat(displayLink.targetTimestamp - displayLink.timestamp))
|
||||
spring(
|
||||
x: &cur,
|
||||
v: &velocity,
|
||||
xt: target,
|
||||
zeta: zeta,
|
||||
omega: omega,
|
||||
h: CGFloat(displayLink.targetTimestamp - displayLink.timestamp)
|
||||
)
|
||||
data = Data(value: cur, velocity: velocity)
|
||||
update(data)
|
||||
if abs(target - data.value) <= (1 / displayScale),
|
||||
abs(pre - data.value) / (1 / displayScale) <= 1 {
|
||||
if abs(target - data.value) <= (1 / displayScale), abs(pre - data.value) / (1 / displayScale) <= 1 {
|
||||
stopAnimation(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- Parameters:
|
||||
- x: value
|
||||
- v: velocity
|
||||
- xt: target value
|
||||
- zeta: damping ratio
|
||||
- omega: angular frequency
|
||||
- h: time step
|
||||
*/
|
||||
/// - Parameters:
|
||||
/// - x: value
|
||||
/// - v: velocity
|
||||
/// - xt: target value
|
||||
/// - zeta: damping ratio
|
||||
/// - omega: angular frequency
|
||||
/// - h: time step
|
||||
private func spring(x: inout CGFloat, v: inout CGFloat, xt: CGFloat, zeta: CGFloat, omega: CGFloat, h: CGFloat) {
|
||||
let f = 1.0 + 2.0 * h * zeta * omega
|
||||
let h2 = pow(h, 2)
|
||||
|
||||
+84
-51
@@ -52,12 +52,14 @@ private class CustomLayoutGuide: LayoutGuideProvider {
|
||||
let rightAnchor: NSLayoutXAxisAnchor
|
||||
let widthAnchor: NSLayoutDimension
|
||||
let heightAnchor: NSLayoutDimension
|
||||
init(topAnchor: NSLayoutYAxisAnchor,
|
||||
leftAnchor: NSLayoutXAxisAnchor,
|
||||
bottomAnchor: NSLayoutYAxisAnchor,
|
||||
rightAnchor: NSLayoutXAxisAnchor,
|
||||
widthAnchor: NSLayoutDimension,
|
||||
heightAnchor: NSLayoutDimension) {
|
||||
init(
|
||||
topAnchor: NSLayoutYAxisAnchor,
|
||||
leftAnchor: NSLayoutXAxisAnchor,
|
||||
bottomAnchor: NSLayoutYAxisAnchor,
|
||||
rightAnchor: NSLayoutXAxisAnchor,
|
||||
widthAnchor: NSLayoutDimension,
|
||||
heightAnchor: NSLayoutDimension
|
||||
) {
|
||||
self.topAnchor = topAnchor
|
||||
self.leftAnchor = leftAnchor
|
||||
self.bottomAnchor = bottomAnchor
|
||||
@@ -72,23 +74,27 @@ extension UIViewController {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
return view?.safeAreaLayoutGuide ?? view
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
leftAnchor: view.leftAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor,
|
||||
rightAnchor: view.rightAnchor,
|
||||
widthAnchor: view.widthAnchor,
|
||||
heightAnchor: topLayoutGuide.bottomAnchor.anchorWithOffset(to: bottomLayoutGuide.topAnchor))
|
||||
return CustomLayoutGuide(
|
||||
topAnchor: topLayoutGuide.bottomAnchor,
|
||||
leftAnchor: view.leftAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor,
|
||||
rightAnchor: view.rightAnchor,
|
||||
widthAnchor: view.widthAnchor,
|
||||
heightAnchor: topLayoutGuide.bottomAnchor.anchorWithOffset(to: bottomLayoutGuide.topAnchor)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +139,14 @@ extension UIView {
|
||||
}
|
||||
|
||||
static func performWithLinear(startTime: Double = 0.0, relativeDuration: Double = 1.0, _ animations: @escaping (() -> Void)) {
|
||||
UIView.animateKeyframes(withDuration: 0.0, delay: 0.0, options: [.calculationModeCubic], animations: {
|
||||
UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: relativeDuration, animations: animations)
|
||||
}, completion: nil)
|
||||
UIView.animateKeyframes(
|
||||
withDuration: 0.0,
|
||||
delay: 0.0,
|
||||
options: [.calculationModeCubic],
|
||||
animations: {
|
||||
UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: relativeDuration, animations: animations)
|
||||
},
|
||||
completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +178,10 @@ extension UIScrollView {
|
||||
}
|
||||
}
|
||||
var fp_contentOffsetMax: CGPoint {
|
||||
return CGPoint(x: max((contentSize.width + fp_contentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + fp_contentInset.bottom) - bounds.height, 0.0))
|
||||
return CGPoint(
|
||||
x: max((contentSize.width + fp_contentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + fp_contentInset.bottom) - bounds.height, 0.0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +221,7 @@ extension UIEdgeInsets {
|
||||
|
||||
extension UIBezierPath {
|
||||
static func path(roundedRect rect: CGRect, appearance: SurfaceAppearance) -> UIBezierPath {
|
||||
let cornerRadius = appearance.cornerRadius;
|
||||
let cornerRadius = appearance.cornerRadius
|
||||
if #available(iOS 13.0, *) {
|
||||
if appearance.cornerCurve == .circular {
|
||||
let path = UIBezierPath()
|
||||
@@ -218,45 +231,61 @@ extension UIBezierPath {
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))
|
||||
if cornerRadius > 0 {
|
||||
path .addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius,
|
||||
y: rect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: -0.5 * .pi,
|
||||
endAngle: 0,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.maxX - cornerRadius,
|
||||
y: rect.minY + cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: -0.5 * .pi,
|
||||
endAngle: 0,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
|
||||
|
||||
if cornerRadius > 0 {
|
||||
path.addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius,
|
||||
y: rect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: .pi * 0.5,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.maxX - cornerRadius,
|
||||
y: rect.maxY - cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: .pi * 0.5,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY))
|
||||
|
||||
if cornerRadius > 0 {
|
||||
path.addArc(withCenter: CGPoint(x: rect.minX + cornerRadius,
|
||||
y: rect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi * 0.5,
|
||||
endAngle: .pi,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.minX + cornerRadius,
|
||||
y: rect.maxY - cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi * 0.5,
|
||||
endAngle: .pi,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
|
||||
|
||||
if cornerRadius > 0 {
|
||||
path.addArc(withCenter: CGPoint(x: rect.minX + cornerRadius,
|
||||
y: rect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi,
|
||||
endAngle: .pi * 1.5,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.minX + cornerRadius,
|
||||
y: rect.minY + cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi,
|
||||
endAngle: .pi * 1.5,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: start)
|
||||
@@ -266,9 +295,13 @@ extension UIBezierPath {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.allCorners],
|
||||
cornerRadii: CGSize(width: cornerRadius,
|
||||
height: cornerRadius))
|
||||
return UIBezierPath(
|
||||
roundedRect: rect,
|
||||
byRoundingCorners: [.allCorners],
|
||||
cornerRadii: CGSize(
|
||||
width: cornerRadius,
|
||||
height: cornerRadius
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+93
-56
@@ -30,7 +30,7 @@ open class FloatingPanelBottomLayout: NSObject, FloatingPanelLayout {
|
||||
return .half
|
||||
}
|
||||
|
||||
open var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
open var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
@@ -153,36 +153,42 @@ class LayoutAdapter {
|
||||
var adjustedContentInsets: UIEdgeInsets {
|
||||
switch position {
|
||||
case .top:
|
||||
return UIEdgeInsets(top: safeAreaInsets.top,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: safeAreaInsets.top,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: 0.0
|
||||
)
|
||||
case .left:
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: safeAreaInsets.left,
|
||||
bottom: 0.0,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: safeAreaInsets.left,
|
||||
bottom: 0.0,
|
||||
right: 0.0
|
||||
)
|
||||
case .bottom:
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: safeAreaInsets.bottom,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: safeAreaInsets.bottom,
|
||||
right: 0.0
|
||||
)
|
||||
case .right:
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: safeAreaInsets.right)
|
||||
return UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: safeAreaInsets.right
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a constraint based value in the interaction and animation.
|
||||
|
||||
So that it doesn't need to call `surfaceView.layoutIfNeeded()`
|
||||
after every interaction and animation update. It has an effect on
|
||||
the smooth interaction because the content view doesn't need to update
|
||||
its layout frequently.
|
||||
*/
|
||||
/// Returns a constraint based value in the interaction and animation.
|
||||
///
|
||||
/// So that it doesn't need to call `surfaceView.layoutIfNeeded()`
|
||||
/// after every interaction and animation update. It has an effect on
|
||||
/// the smooth interaction because the content view doesn't need to update
|
||||
/// its layout frequently.
|
||||
var surfaceLocation: CGPoint {
|
||||
get {
|
||||
var pos: CGFloat
|
||||
@@ -433,10 +439,10 @@ class LayoutAdapter {
|
||||
}
|
||||
let backdropConstraints = [
|
||||
backdropView.topAnchor.constraint(equalTo: vc.view.topAnchor, constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor,constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor, constant: 0.0),
|
||||
backdropView.rightAnchor.constraint(equalTo: vc.view.rightAnchor, constant: 0.0),
|
||||
backdropView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, constant: 0.0),
|
||||
]
|
||||
]
|
||||
|
||||
fixedConstraints = surfaceConstraints + backdropConstraints
|
||||
|
||||
@@ -471,7 +477,10 @@ class LayoutAdapter {
|
||||
for state in layout.anchors.keys {
|
||||
stateConstraints[state] = layout.anchors[state]?
|
||||
.layoutConstraints(vc, for: position)
|
||||
.map{ $0.identifier = "FloatingPanel-\(state)-constraint"; return $0 }
|
||||
.map {
|
||||
$0.identifier = "FloatingPanel-\(state)-constraint"
|
||||
return $0
|
||||
}
|
||||
}
|
||||
let hiddenAnchor = layout.anchors[.hidden] ?? self.hiddenAnchor
|
||||
offConstraints = hiddenAnchor.layoutConstraints(vc, for: position)
|
||||
@@ -549,8 +558,10 @@ class LayoutAdapter {
|
||||
case .top:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .top:
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.top
|
||||
targetY -= safeAreaInsets.top
|
||||
@@ -558,8 +569,10 @@ class LayoutAdapter {
|
||||
case .bottom:
|
||||
let baseHeight = vc.view.bounds.height
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.bottom
|
||||
targetY += safeAreaInsets.bottom
|
||||
@@ -571,16 +584,20 @@ class LayoutAdapter {
|
||||
case .left:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .left:
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.right
|
||||
targetY -= safeAreaInsets.right
|
||||
}
|
||||
case .right:
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.left
|
||||
targetY += safeAreaInsets.left
|
||||
@@ -591,16 +608,20 @@ class LayoutAdapter {
|
||||
case .bottom:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .top:
|
||||
animationConstraint = surfaceView.topAnchor.constraint(equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.topAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.top
|
||||
targetY -= safeAreaInsets.top
|
||||
}
|
||||
case .bottom:
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.topAnchor.constraint(equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.topAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.bottom
|
||||
targetY += safeAreaInsets.bottom
|
||||
@@ -612,16 +633,20 @@ class LayoutAdapter {
|
||||
case .right:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .left:
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.left
|
||||
targetY -= safeAreaInsets.left
|
||||
}
|
||||
case .right:
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.right
|
||||
targetY += safeAreaInsets.right
|
||||
@@ -647,7 +672,7 @@ class LayoutAdapter {
|
||||
// The method is separated from prepareLayout(to:) for the rotation support
|
||||
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
|
||||
func updateStaticConstraint() {
|
||||
NSLayoutConstraint.deactivate([staticConstraint, contentBoundingConstraint].compactMap{ $0 })
|
||||
NSLayoutConstraint.deactivate([staticConstraint, contentBoundingConstraint].compactMap { $0 })
|
||||
staticConstraint = nil
|
||||
contentBoundingConstraint = nil
|
||||
|
||||
@@ -656,7 +681,10 @@ class LayoutAdapter {
|
||||
return
|
||||
}
|
||||
|
||||
let anchor = layout.anchors[self.mostExpandedState]!
|
||||
guard let anchor = layout.anchors[mostExpandedState] else {
|
||||
return
|
||||
}
|
||||
|
||||
let surfaceAnchor = position.mainDimensionAnchor(surfaceView)
|
||||
switch anchor {
|
||||
case let anchor as FloatingPanelIntrinsicLayoutAnchor:
|
||||
@@ -675,11 +703,15 @@ class LayoutAdapter {
|
||||
let baseAnchor = position.mainDimensionAnchor(anchor.contentLayoutGuide)
|
||||
if let boundingLayoutGuide = anchor.contentBoundingGuide.layoutGuide(vc) {
|
||||
if anchor.isAbsolute {
|
||||
contentBoundingConstraint = baseAnchor.constraint(lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
constant: anchor.offset)
|
||||
contentBoundingConstraint = baseAnchor.constraint(
|
||||
lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
constant: anchor.offset
|
||||
)
|
||||
} else {
|
||||
contentBoundingConstraint = baseAnchor.constraint(lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
multiplier: anchor.offset)
|
||||
contentBoundingConstraint = baseAnchor.constraint(
|
||||
lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
multiplier: anchor.offset
|
||||
)
|
||||
}
|
||||
staticConstraint = surfaceAnchor.constraint(lessThanOrEqualTo: baseAnchor, constant: constant)
|
||||
} else {
|
||||
@@ -691,8 +723,10 @@ class LayoutAdapter {
|
||||
staticConstraint = surfaceAnchor.constraint(equalToConstant: position(for: self.mostCoordinateState))
|
||||
case .bottom, .right:
|
||||
let rootViewAnchor = position.mainDimensionAnchor(vc.view)
|
||||
staticConstraint = rootViewAnchor.constraint(equalTo: surfaceAnchor,
|
||||
constant: position(for: self.leastCoordinateState))
|
||||
staticConstraint = rootViewAnchor.constraint(
|
||||
equalTo: surfaceAnchor,
|
||||
constant: position(for: self.leastCoordinateState)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,7 +737,7 @@ class LayoutAdapter {
|
||||
staticConstraint?.identifier = "FloatingPanel-static-width"
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([staticConstraint, contentBoundingConstraint].compactMap{ $0 })
|
||||
NSLayoutConstraint.activate([staticConstraint, contentBoundingConstraint].compactMap { $0 })
|
||||
|
||||
surfaceView.containerOverflow = position.mainDimension(vc.view.bounds.size)
|
||||
}
|
||||
@@ -806,9 +840,12 @@ class LayoutAdapter {
|
||||
fileprivate func checkLayout() {
|
||||
// Verify layout configurations
|
||||
assert(anchorStates.count > 0)
|
||||
assert(validStates.contains(layout.initialState),
|
||||
"Does not include an initial state (\(layout.initialState)) in (\(validStates))")
|
||||
/* This assertion does not work in a device rotating
|
||||
assert(
|
||||
validStates.contains(layout.initialState),
|
||||
"Does not include an initial state (\(layout.initialState)) in (\(validStates))"
|
||||
)
|
||||
/**
|
||||
// This assertion does not work in a device rotating
|
||||
let statePosOrder = activeStates.sorted(by: { position(for: $0) < position(for: $1) })
|
||||
assert(sortedDirectionalStates == statePosOrder,
|
||||
"Check your layout anchors because the state order(\(statePosOrder)) must be (\(sortedDirectionalStates))).")
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
import UIKit
|
||||
|
||||
/// An interface for implementing custom layout anchor objects.
|
||||
@objc public protocol FloatingPanelLayoutAnchoring {
|
||||
@objc
|
||||
public protocol FloatingPanelLayoutAnchoring {
|
||||
var referenceGuide: FloatingPanelLayoutReferenceGuide { get }
|
||||
func layoutConstraints(_ fpc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint]
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with insets from an edge of a reference rectangle.
|
||||
@objc final public class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
@objc
|
||||
public final class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
|
||||
/// Returns a layout anchor with the specified inset by an absolute value, edge and reference guide for a panel.
|
||||
///
|
||||
@@ -50,8 +52,8 @@ import UIKit
|
||||
@objc let referenceEdge: FloatingPanelReferenceEdge
|
||||
}
|
||||
|
||||
public extension FloatingPanelLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
extension FloatingPanelLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
switch position {
|
||||
case .top:
|
||||
@@ -72,7 +74,7 @@ public extension FloatingPanelLayoutAnchor {
|
||||
return [edgeAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: inset)]
|
||||
}
|
||||
let offsetAnchor = layoutGuide.topAnchor.anchorWithOffset(to: edgeAnchor)
|
||||
return [offsetAnchor.constraint(equalTo:layoutGuide.heightAnchor, multiplier: inset)]
|
||||
return [offsetAnchor.constraint(equalTo: layoutGuide.heightAnchor, multiplier: inset)]
|
||||
case .bottom:
|
||||
if isAbsolute {
|
||||
return [layoutGuide.bottomAnchor.constraint(equalTo: edgeAnchor, constant: inset)]
|
||||
@@ -105,7 +107,8 @@ public extension FloatingPanelLayoutAnchor {
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with the intrinsic size for a content.
|
||||
@objc final public class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
@objc
|
||||
public final class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
|
||||
/// Returns a layout anchor with the specified offset by an absolute value and reference guide for a panel.
|
||||
///
|
||||
@@ -141,8 +144,8 @@ public extension FloatingPanelLayoutAnchor {
|
||||
@objc public let referenceGuide: FloatingPanelLayoutReferenceGuide
|
||||
}
|
||||
|
||||
public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
let surfaceIntrinsicLength = position.mainDimension(vc.surfaceView.intrinsicContentSize)
|
||||
let constant = isAbsolute ? surfaceIntrinsicLength - offset : surfaceIntrinsicLength * (1 - offset)
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
@@ -160,7 +163,8 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with a layout guide of a content view.
|
||||
@objc final public class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
@objc
|
||||
public final class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
|
||||
/// Returns a layout anchor with the specified offset by an absolute value to display a panel with its intrinsic content size.
|
||||
///
|
||||
@@ -222,8 +226,8 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
@objc public let contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide
|
||||
}
|
||||
|
||||
public extension FloatingPanelAdaptiveLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
extension FloatingPanelAdaptiveLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
|
||||
@@ -80,21 +80,21 @@ struct Logger {
|
||||
}
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.info, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.warning, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
|
||||
+123
-77
@@ -81,9 +81,11 @@ public class SurfaceView: UIView {
|
||||
public let grabberHandle = GrabberView()
|
||||
|
||||
/// Offset of the grabber handle from the interactive edge.
|
||||
public var grabberHandlePadding: CGFloat = 6.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var grabberHandlePadding: CGFloat = 6.0 {
|
||||
didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/// The offset from the move edge to prevent the content scroll
|
||||
public var grabberAreaOffset: CGFloat = 36.0
|
||||
@@ -91,9 +93,11 @@ public class SurfaceView: UIView {
|
||||
/// The grabber handle size
|
||||
///
|
||||
/// On left/right positioned panel the width dimension is used as the height of ``grabberHandle``, and vice versa.
|
||||
public var grabberHandleSize: CGSize = CGSize(width: 36.0, height: 5.0) { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var grabberHandleSize: CGSize = CGSize(width: 36.0, height: 5.0) {
|
||||
didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/// The content view to be assigned a view of the content view controller of `FloatingPanelController`
|
||||
public weak var contentView: UIView?
|
||||
@@ -108,19 +112,26 @@ public class SurfaceView: UIView {
|
||||
|
||||
public override var backgroundColor: UIColor? {
|
||||
get { return appearance.backgroundColor }
|
||||
set { appearance.backgroundColor = newValue; setNeedsLayout() }
|
||||
set {
|
||||
appearance.backgroundColor = newValue
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
/// The appearance settings for a surface view.
|
||||
public var appearance = SurfaceAppearance() { didSet {
|
||||
shadowLayers = appearance.shadows.map { _ in CAShapeLayer() }
|
||||
setNeedsLayout()
|
||||
}}
|
||||
public var appearance = SurfaceAppearance() {
|
||||
didSet {
|
||||
shadowLayers = appearance.shadows.map { _ in CAShapeLayer() }
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
/// The margins to use when laying out the container view wrapping content.
|
||||
public var containerMargins: UIEdgeInsets = .zero { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var containerMargins: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/// The view that displays an actual surface shape.
|
||||
///
|
||||
@@ -130,7 +141,7 @@ public class SurfaceView: UIView {
|
||||
/// content view.
|
||||
public let containerView: UIView = UIView()
|
||||
|
||||
var containerOverflow: CGFloat = 0.0 { // Must not call setNeedsLayout()
|
||||
var containerOverflow: CGFloat = 0.0 { // Must not call setNeedsLayout()
|
||||
didSet {
|
||||
// Calling setNeedsUpdateConstraints() is necessary to fix a layout break
|
||||
// when the contentMode is changed after laying out a panel, for instance,
|
||||
@@ -142,10 +153,12 @@ public class SurfaceView: UIView {
|
||||
var position: FloatingPanelPosition = .bottom {
|
||||
didSet {
|
||||
guard position != oldValue else { return }
|
||||
NSLayoutConstraint.deactivate([grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint])
|
||||
NSLayoutConstraint.deactivate([
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
])
|
||||
switch position {
|
||||
case .top:
|
||||
grabberHandleEdgePaddingConstraint = grabberHandle.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -grabberHandlePadding)
|
||||
@@ -168,10 +181,12 @@ public class SurfaceView: UIView {
|
||||
grabberHandleWidthConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleSize.height)
|
||||
grabberHandleHeightConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleSize.width)
|
||||
}
|
||||
NSLayoutConstraint.activate([grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint])
|
||||
NSLayoutConstraint.activate([
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
])
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
@@ -179,17 +194,25 @@ public class SurfaceView: UIView {
|
||||
var grabberAreaFrame: CGRect {
|
||||
switch position {
|
||||
case .top:
|
||||
return CGRect(origin: .init(x: bounds.minX, y: bounds.maxY - grabberAreaOffset),
|
||||
size: .init(width: bounds.width, height: grabberAreaOffset))
|
||||
return CGRect(
|
||||
origin: .init(x: bounds.minX, y: bounds.maxY - grabberAreaOffset),
|
||||
size: .init(width: bounds.width, height: grabberAreaOffset)
|
||||
)
|
||||
case .left:
|
||||
return CGRect(origin: .init(x: bounds.maxX - grabberAreaOffset, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height))
|
||||
return CGRect(
|
||||
origin: .init(x: bounds.maxX - grabberAreaOffset, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height)
|
||||
)
|
||||
case .bottom:
|
||||
return CGRect(origin: CGPoint(x: bounds.minX, y: bounds.minY),
|
||||
size: CGSize(width: bounds.width, height: grabberAreaOffset))
|
||||
return CGRect(
|
||||
origin: CGPoint(x: bounds.minX, y: bounds.minY),
|
||||
size: CGSize(width: bounds.width, height: grabberAreaOffset)
|
||||
)
|
||||
case .right:
|
||||
return CGRect(origin: .init(x: bounds.minX, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height))
|
||||
return CGRect(
|
||||
origin: .init(x: bounds.minX, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +232,7 @@ public class SurfaceView: UIView {
|
||||
|
||||
private lazy var grabberHandleWidthConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleSize.width)
|
||||
private lazy var grabberHandleHeightConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleSize.height)
|
||||
private lazy var grabberHandleCenterConstraint = grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
private lazy var grabberHandleCenterConstraint = grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
private lazy var grabberHandleEdgePaddingConstraint = grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: grabberHandlePadding)
|
||||
|
||||
private var shadowLayers: [CALayer] = [] {
|
||||
@@ -243,27 +266,31 @@ public class SurfaceView: UIView {
|
||||
|
||||
addSubview(containerView)
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
containerViewTopConstraint,
|
||||
containerViewLeftConstraint,
|
||||
containerViewBottomConstraint,
|
||||
containerViewRightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-container"
|
||||
return $0;
|
||||
})
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
containerViewTopConstraint,
|
||||
containerViewLeftConstraint,
|
||||
containerViewBottomConstraint,
|
||||
containerViewRightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-container"
|
||||
return $0
|
||||
}
|
||||
)
|
||||
|
||||
addSubview(grabberHandle)
|
||||
grabberHandle.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-grabber"
|
||||
return $0;
|
||||
})
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-grabber"
|
||||
return $0
|
||||
}
|
||||
)
|
||||
|
||||
shadowLayers = appearance.shadows.map { _ in CALayer() }
|
||||
}
|
||||
@@ -311,7 +338,7 @@ public class SurfaceView: UIView {
|
||||
case .left, .right:
|
||||
grabberHandleWidthConstraint.constant = grabberHandleSize.height
|
||||
grabberHandleHeightConstraint.constant = grabberHandleSize.width
|
||||
}
|
||||
}
|
||||
|
||||
super.updateConstraints()
|
||||
}
|
||||
@@ -332,8 +359,10 @@ public class SurfaceView: UIView {
|
||||
public override var intrinsicContentSize: CGSize {
|
||||
let fittingSize = UIView.layoutFittingCompressedSize
|
||||
let contentSize = contentView?.systemLayoutSizeFitting(fittingSize) ?? .zero
|
||||
return CGSize(width: containerMargins.horizontalInset + contentPadding.horizontalInset + contentSize.width,
|
||||
height: containerMargins.verticalInset + contentPadding.verticalInset + contentSize.height)
|
||||
return CGSize(
|
||||
width: containerMargins.horizontalInset + contentPadding.horizontalInset + contentSize.width,
|
||||
height: containerMargins.verticalInset + contentPadding.verticalInset + contentSize.height
|
||||
)
|
||||
}
|
||||
|
||||
private func updateShadow() {
|
||||
@@ -348,8 +377,10 @@ public class SurfaceView: UIView {
|
||||
|
||||
let spread = shadow.spread
|
||||
let shadowRect = containerView.frame.insetBy(dx: -spread, dy: -spread)
|
||||
let shadowPath = UIBezierPath.path(roundedRect: shadowRect,
|
||||
appearance: appearance)
|
||||
let shadowPath = UIBezierPath.path(
|
||||
roundedRect: shadowRect,
|
||||
appearance: appearance
|
||||
)
|
||||
shadowLayer.shadowPath = shadowPath.cgPath
|
||||
shadowLayer.shadowColor = shadow.color.cgColor
|
||||
shadowLayer.shadowOffset = shadow.offset
|
||||
@@ -358,11 +389,19 @@ public class SurfaceView: UIView {
|
||||
shadowLayer.shadowOpacity = shadow.opacity
|
||||
|
||||
let mask = CAShapeLayer()
|
||||
let path = UIBezierPath.path(roundedRect: containerView.frame,
|
||||
appearance: appearance)
|
||||
let path = UIBezierPath.path(
|
||||
roundedRect: containerView.frame,
|
||||
appearance: appearance
|
||||
)
|
||||
let size = window?.bounds.size ?? CGSize(width: 1000.0, height: 1000.0)
|
||||
path.append(UIBezierPath(rect: layer.bounds.insetBy(dx: -size.width,
|
||||
dy: -size.height)))
|
||||
path.append(
|
||||
UIBezierPath(
|
||||
rect: layer.bounds.insetBy(
|
||||
dx: -size.width,
|
||||
dy: -size.height
|
||||
)
|
||||
)
|
||||
)
|
||||
mask.fillRule = .evenOdd
|
||||
mask.path = path.cgPath
|
||||
if #available(iOS 13.0, *) {
|
||||
@@ -383,8 +422,10 @@ public class SurfaceView: UIView {
|
||||
containerView.layer.masksToBounds = true
|
||||
if position.inset(containerMargins) != 0 {
|
||||
if #available(iOS 11, *) {
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,
|
||||
.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
containerView.layer.maskedCorners = [
|
||||
.layerMinXMinYCorner, .layerMaxXMinYCorner,
|
||||
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
|
||||
]
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -416,28 +457,33 @@ public class SurfaceView: UIView {
|
||||
func set(contentView: UIView, mode: FloatingPanelController.ContentMode) {
|
||||
containerView.addSubview(contentView)
|
||||
self.contentView = contentView
|
||||
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
/**
|
||||
// MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.frame = bounds
|
||||
*/
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor, constant: containerMargins.top + contentPadding.top)
|
||||
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: containerMargins.left + contentPadding.left)
|
||||
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: containerMargins.right + contentPadding.right)
|
||||
let bottomConstraint = bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: containerMargins.bottom + contentPadding.bottom)
|
||||
NSLayoutConstraint.activate([
|
||||
topConstraint,
|
||||
leftConstraint,
|
||||
rightConstraint,
|
||||
bottomConstraint,
|
||||
].map {
|
||||
switch mode {
|
||||
case .static:
|
||||
$0.priority = .required
|
||||
// The reason why this priority is set to .required - 1 is #359, which fixed #294.
|
||||
case .fitToBounds:
|
||||
$0.priority = .required - 1
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
topConstraint,
|
||||
leftConstraint,
|
||||
rightConstraint,
|
||||
bottomConstraint,
|
||||
].map {
|
||||
switch mode {
|
||||
case .static:
|
||||
$0.priority = .required
|
||||
// The reason why this priority is set to .required - 1 is #359, which fixed #294.
|
||||
case .fitToBounds:
|
||||
$0.priority = .required - 1
|
||||
}
|
||||
$0.identifier = "FloatingPanel-surface-content"
|
||||
return $0
|
||||
}
|
||||
$0.identifier = "FloatingPanel-surface-content"
|
||||
return $0
|
||||
})
|
||||
)
|
||||
self.contentViewTopConstraint = topConstraint
|
||||
self.contentViewLeftConstraint = leftConstraint
|
||||
self.contentViewRightConstraint = rightConstraint
|
||||
|
||||
+16
-12
@@ -3,9 +3,11 @@
|
||||
import UIKit
|
||||
|
||||
class ModalTransition: NSObject, UIViewControllerTransitioningDelegate {
|
||||
func animationController(forPresented presented: UIViewController,
|
||||
presenting: UIViewController,
|
||||
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
func animationController(
|
||||
forPresented presented: UIViewController,
|
||||
presenting: UIViewController,
|
||||
source: UIViewController
|
||||
) -> UIViewControllerAnimatedTransitioning? {
|
||||
return ModalPresentTransition()
|
||||
}
|
||||
|
||||
@@ -52,11 +54,11 @@ class PresentationController: UIPresentationController {
|
||||
view is added unnecessarily.
|
||||
*/
|
||||
fpc.presentedViewController == nil
|
||||
else { return }
|
||||
else { return }
|
||||
|
||||
/*
|
||||
* Layout the views managed by `FloatingPanelController` here for the
|
||||
* sake of the presentation and dismissal modally from the controller.
|
||||
/**
|
||||
Layout the views managed by `FloatingPanelController` here for the
|
||||
sake of the presentation and dismissal modally from the controller.
|
||||
*/
|
||||
addFloatingPanel()
|
||||
|
||||
@@ -72,7 +74,7 @@ class PresentationController: UIPresentationController {
|
||||
guard
|
||||
let containerView = self.containerView,
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
else { fatalError() }
|
||||
|
||||
containerView.addSubview(fpc.view)
|
||||
fpc.view.frame = containerView.bounds
|
||||
@@ -84,7 +86,7 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { fatalError()}
|
||||
else { fatalError() }
|
||||
|
||||
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
|
||||
return TimeInterval(animator.duration)
|
||||
@@ -119,7 +121,7 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { fatalError()}
|
||||
else { fatalError() }
|
||||
|
||||
let animator = fpc.animatorForDismissing(with: .zero)
|
||||
return TimeInterval(animator.duration)
|
||||
@@ -139,11 +141,13 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
return fpc.transitionAnimator!
|
||||
guard let transitionAnimator = fpc.transitionAnimator else {
|
||||
fatalError("Something wrong happened in Core.move(from:to:animated:completion)")
|
||||
}
|
||||
return transitionAnimator
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-20
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ControllerTests: XCTestCase {
|
||||
@@ -9,8 +10,8 @@ class ControllerTests: XCTestCase {
|
||||
|
||||
func test_warningRetainCycle() {
|
||||
let exp = expectation(description: "Warning retain cycle")
|
||||
exp.expectedFulfillmentCount = 2 // For layout & behavior logs
|
||||
log.hook = {(log, level) in
|
||||
exp.expectedFulfillmentCount = 2 // For layout & behavior logs
|
||||
log.hook = { (log, level) in
|
||||
if log.contains("A memory leak will occur by a retain cycle because") {
|
||||
XCTAssert(level == .warning)
|
||||
exp.fulfill()
|
||||
@@ -26,7 +27,6 @@ class ControllerTests: XCTestCase {
|
||||
rootVC.loadViewIfNeeded()
|
||||
rootVC.view.bounds = .init(origin: .zero, size: .init(width: 390, height: 844))
|
||||
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.addPanel(toParent: rootVC)
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .half).y)
|
||||
@@ -46,8 +46,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
let myDelegate = MyDelegate()
|
||||
let fpc = FloatingPanelController(delegate: myDelegate)
|
||||
let traitCollection = UITraitCollection(traitsFrom: [fpc.traitCollection,
|
||||
UITraitCollection(userInterfaceStyle: .dark)])
|
||||
let traitCollection = UITraitCollection(traitsFrom: [fpc.traitCollection, UITraitCollection(userInterfaceStyle: .dark)])
|
||||
XCTAssertEqual(traitCollection.userInterfaceStyle, .dark)
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ class ControllerTests: XCTestCase {
|
||||
|
||||
fpc.hide()
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.state, .full)
|
||||
XCTAssertEqual(delegate.position, .full)
|
||||
@@ -193,18 +192,18 @@ class ControllerTests: XCTestCase {
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .hidden).y)
|
||||
}
|
||||
|
||||
|
||||
func test_moveWithNearbyPosition() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
fpc.showForTest()
|
||||
|
||||
|
||||
XCTAssertEqual(fpc.nearbyState, .half)
|
||||
|
||||
|
||||
fpc.hide()
|
||||
XCTAssertEqual(fpc.nearbyState, .tip)
|
||||
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.nearbyState, .full)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.surfaceLocation(for: .full).y)
|
||||
@@ -340,15 +339,21 @@ private class MyZombieViewController: UIViewController, FloatingPanelLayout, Flo
|
||||
return .half
|
||||
}
|
||||
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0,
|
||||
edge: .top,
|
||||
referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 60.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview),
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(
|
||||
absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0,
|
||||
edge: .top,
|
||||
referenceGuide: .superview
|
||||
),
|
||||
.half: FloatingPanelLayoutAnchor(
|
||||
absoluteInset: 250.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview
|
||||
),
|
||||
.tip: FloatingPanelLayoutAnchor(
|
||||
absoluteInset: 60.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
+345
-304
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class CoreTests: XCTestCase {
|
||||
@@ -53,7 +54,7 @@ class CoreTests: XCTestCase {
|
||||
class FloatingPanelLayout1Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .full
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] =
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] =
|
||||
[.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)]
|
||||
}
|
||||
|
||||
@@ -73,9 +74,9 @@ class CoreTests: XCTestCase {
|
||||
func test_getBackdropAlpha_1positionsWithInitialHidden() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .hidden }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .top, referenceGuide: referenceGuide),
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .top, referenceGuide: referenceGuide)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -88,7 +89,7 @@ class CoreTests: XCTestCase {
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let hiddenPos = fpc.surfaceLocation(for: .hidden).y
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos - 100.0, with: -100.0), 0.3)
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos - 100.0, with: -100.0), 0.3)
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: 0.0), 0.3)
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: hiddenPos, with: 100.0), 0.0)
|
||||
}
|
||||
@@ -97,7 +98,7 @@ class CoreTests: XCTestCase {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .half
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
@@ -127,7 +128,7 @@ class CoreTests: XCTestCase {
|
||||
func test_getBackdropAlpha_2positionsWithHidden() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .hidden }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .top, referenceGuide: referenceGuide),
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: referenceGuide),
|
||||
@@ -143,7 +144,7 @@ class CoreTests: XCTestCase {
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let hiddenPos = fpc.surfaceLocation(for: .hidden).y
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos - 100.0, with: -100.0), 0.3)
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos - 100.0, with: -100.0), 0.3)
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: 0.0), 0.3)
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: hiddenPos, with: 100.0), 0.0)
|
||||
}
|
||||
@@ -174,7 +175,6 @@ class CoreTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: -1 * distance2), 0.0)
|
||||
}
|
||||
|
||||
|
||||
func test_updateBackdropAlpha() {
|
||||
class BackdropTestLayout: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .hidden }
|
||||
@@ -246,7 +246,7 @@ class CoreTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
|
||||
fpc.willTransition(to: UITraitCollection(horizontalSizeClass: .regular), with: MockTransitionCoordinator())
|
||||
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must update the alpha by BackdropTestLayout2 in TestDelegate.
|
||||
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must update the alpha by BackdropTestLayout2 in TestDelegate.
|
||||
|
||||
// Test a view size change of FloatingPanelController.view
|
||||
|
||||
@@ -257,7 +257,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
delegate.layout = BackdropTestLayout2()
|
||||
fpc.viewWillTransition(to: CGSize.zero, with: MockTransitionCoordinator())
|
||||
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must update the alpha by BackdropTestLayout2 in TestDelegate.
|
||||
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must update the alpha by BackdropTestLayout2 in TestDelegate.
|
||||
}
|
||||
|
||||
func test_initial_surface_position() {
|
||||
@@ -265,8 +265,9 @@ class CoreTests: XCTestCase {
|
||||
class Layout: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .full
|
||||
let position: FloatingPanelPosition = .top
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
|
||||
= [.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .superview)]
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .superview)
|
||||
]
|
||||
}
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
Layout()
|
||||
@@ -288,8 +289,9 @@ class CoreTests: XCTestCase {
|
||||
class FloatingPanelLayout1Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .full
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
|
||||
= [.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)]
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)
|
||||
]
|
||||
}
|
||||
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
@@ -301,22 +303,25 @@ class CoreTests: XCTestCase {
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_targetPosition_2positions() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .half
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
@@ -332,46 +337,52 @@ class CoreTests: XCTestCase {
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .half), // redirect
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .half), // redirect
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
]
|
||||
)
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .half), // redirect
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .half), // redirect
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_targetPosition_2positionsWithHidden() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
@@ -387,38 +398,42 @@ class CoreTests: XCTestCase {
|
||||
let hiddenPos = fpc.surfaceLocation(for: .hidden).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, hiddenPos - 10.0, CGPoint(x: 0.0, y: -100.0), .hidden), // redirect
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 0.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // redirect
|
||||
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, hiddenPos - 10.0, CGPoint(x: 0.0, y: -100.0), .hidden), // redirect
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 0.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // redirect
|
||||
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
fpc.move(to: .hidden, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, hiddenPos - 10.0, CGPoint(x: 0.0, y: -100.0), .hidden), // redirect
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 0.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // redirect
|
||||
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, hiddenPos - 10.0, CGPoint(x: 0.0, y: -100.0), .hidden), // redirect
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: -100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 0.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 100.0), .hidden),
|
||||
(#line, hiddenPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // redirect
|
||||
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
}
|
||||
|
||||
@@ -434,36 +449,38 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), //project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), //project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromFull_bottomEdge() {
|
||||
@@ -478,36 +495,38 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 100.0), .tip), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .tip), //project to tip
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .full), // project to full
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from bottomMostState
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 100.0), .tip), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .tip), //project to tip
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .full), // project to full
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from bottomMostState
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromHalf() {
|
||||
@@ -522,34 +541,36 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),// project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromHalf_bottomEdge() {
|
||||
@@ -557,40 +578,42 @@ class CoreTests: XCTestCase {
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
|
||||
fpc.showForTest()
|
||||
fpc.showForTest()
|
||||
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 100.0), .tip), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .tip),// project to tip
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 100.0), .tip), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .tip), // project to tip
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .full), // project to full
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .full), // project to full
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from bottomMostState
|
||||
])
|
||||
}
|
||||
|
||||
@@ -607,34 +630,36 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -3000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
|
||||
])
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -3000.0), .full), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
|
||||
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromTip_bottomEdge() {
|
||||
@@ -644,39 +669,41 @@ class CoreTests: XCTestCase {
|
||||
|
||||
fpc.showForTest()
|
||||
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 100.0), .tip), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -3000.0), .tip), // project to full
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 100.0), .tip), // redirect
|
||||
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -3000.0), .tip), // project to full
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .full), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .full), // project to tip
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from bottomMostState
|
||||
(#line, fullPos + 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from bottomMostState
|
||||
])
|
||||
}
|
||||
|
||||
@@ -694,37 +721,43 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
])
|
||||
|
||||
// From .half
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
])
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -732,7 +765,7 @@ class CoreTests: XCTestCase {
|
||||
class FloatingPanelLayout3PositionsWithHidden: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
|
||||
@@ -746,15 +779,19 @@ class CoreTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.state, .hidden)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .half),
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .half)
|
||||
])
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .hidden),
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .hidden),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -762,7 +799,7 @@ class CoreTests: XCTestCase {
|
||||
class FloatingPanelLayout3Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 60.0, edge: .bottom, referenceGuide: .superview),
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
|
||||
@@ -782,27 +819,31 @@ class CoreTests: XCTestCase {
|
||||
//let hiddenPos = fpc.surfaceLocation(for: .hidden)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 385.0), .tip), // projection
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // projection
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirection
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirection
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), //projection
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -10.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 10.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden), //projection
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 385.0), .tip), // projection
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // projection
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirection
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirection
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), //projection
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -10.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 10.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden), //projection
|
||||
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
|
||||
])
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden),
|
||||
assertTargetPosition(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -826,7 +867,7 @@ private class FloatingPanelLayout3PositionsBottomEdge: FloatingPanelTop2BottomTe
|
||||
}
|
||||
}
|
||||
|
||||
private typealias TestParameter = (UInt, CGFloat, CGPoint, FloatingPanelState)
|
||||
private typealias TestParameter = (UInt, CGFloat, CGPoint, FloatingPanelState)
|
||||
private func assertTargetPosition(_ floatingPanel: Core, with params: [TestParameter]) {
|
||||
params.forEach { (line, pos, velocity, result) in
|
||||
floatingPanel.surfaceView.frame.origin.y = pos
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ExtensionTests: XCTestCase {
|
||||
|
||||
+399
-247
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class LayoutTests: XCTestCase {
|
||||
@@ -20,7 +21,7 @@ class LayoutTests: XCTestCase {
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview)
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -44,7 +45,7 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
func test_layoutSegment_3position() {
|
||||
class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -56,25 +57,29 @@ class LayoutTests: XCTestCase {
|
||||
let minPos = CGFloat.leastNormalMagnitude
|
||||
let maxPos = CGFloat.greatestFiniteMagnitude
|
||||
|
||||
assertLayoutSegment(fpc.floatingPanel, with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: .tip),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: tipPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: tipPos, forwardY: false, lower: .half, upper: .tip),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .tip, upper: nil),
|
||||
])
|
||||
assertLayoutSegment(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: .tip),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: tipPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: tipPos, forwardY: false, lower: .half, upper: .tip),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .tip, upper: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_layoutSegment_2positions() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
|
||||
{ super.anchors.filter { (key, _) in key != .tip } }
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
super.anchors.filter { (key, _) in key != .tip }
|
||||
}
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout2Positions()
|
||||
@@ -85,23 +90,27 @@ class LayoutTests: XCTestCase {
|
||||
let minPos = CGFloat.leastNormalMagnitude
|
||||
let maxPos = CGFloat.greatestFiniteMagnitude
|
||||
|
||||
assertLayoutSegment(fpc.floatingPanel, with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .half, upper: nil),
|
||||
])
|
||||
assertLayoutSegment(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .half, upper: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_layoutSegment_1positions() {
|
||||
class FloatingPanelLayout1Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .full }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
|
||||
{ super.anchors.filter { (key, _) in key == .full } }
|
||||
override var initialState: FloatingPanelState { .full }
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
super.anchors.filter { (key, _) in key == .full }
|
||||
}
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout1Positions()
|
||||
@@ -111,14 +120,17 @@ class LayoutTests: XCTestCase {
|
||||
let minPos = CGFloat.leastNormalMagnitude
|
||||
let maxPos = CGFloat.greatestFiniteMagnitude
|
||||
|
||||
assertLayoutSegment(fpc.floatingPanel, with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .full, upper: nil),
|
||||
])
|
||||
assertLayoutSegment(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .full, upper: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_updateInteractiveEdgeConstraint() {
|
||||
@@ -126,40 +138,50 @@ class LayoutTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state)
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100.0)
|
||||
|
||||
@@ -171,11 +193,10 @@ class LayoutTests: XCTestCase {
|
||||
fpc.showForTest()
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame, CGRect(x: 0.0, y: -667.0 + 60.0, width: 375.0, height: 667))
|
||||
XCTAssertEqual(fpc.surfaceView.containerView.frame, CGRect(x: 0.0, y: -667.0,
|
||||
width: 375.0, height: 667 * 2.0))
|
||||
XCTAssertEqual(fpc.surfaceView.containerView.frame, CGRect(x: 0.0, y: -667.0, width: 375.0, height: 667 * 2.0))
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state)
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.interactionConstraint?.constant, 60.0)
|
||||
|
||||
@@ -185,33 +206,43 @@ class LayoutTests: XCTestCase {
|
||||
var pre: CGFloat
|
||||
var next: CGFloat
|
||||
pre = fpc.surfaceLocation.y
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, pre)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: fullPos - tipPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos + 100,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: fullPos - tipPos + 100,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos + 100,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: fullPos - tipPos + 100,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
@@ -238,21 +269,27 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos - 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, hiddenPos + 100.0)
|
||||
|
||||
@@ -279,21 +316,27 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, hiddenPos + 100.0)
|
||||
|
||||
@@ -317,21 +360,27 @@ class LayoutTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
|
||||
var next: CGFloat
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos - 100)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100)
|
||||
|
||||
@@ -366,7 +415,7 @@ class LayoutTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.layout.anchors.filter({ $0.value.referenceGuide != .safeArea }).count, 0)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, myLayout.fullInset + fpc.fp_safeAreaInsets.top)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .half).y, bounds.height - myLayout.halfInset + fpc.fp_safeAreaInsets.bottom)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, bounds.height - myLayout.tipInset + fpc.fp_safeAreaInsets.bottom)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, bounds.height - myLayout.tipInset + fpc.fp_safeAreaInsets.bottom)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .hidden).y, bounds.height + 100.0)
|
||||
}
|
||||
|
||||
@@ -375,7 +424,7 @@ class LayoutTests: XCTestCase {
|
||||
fpc.loadViewIfNeeded()
|
||||
fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
|
||||
|
||||
class MyFloatingPanelFullLayout: FloatingPanelTop2BottomTestLayout { }
|
||||
class MyFloatingPanelFullLayout: FloatingPanelTop2BottomTestLayout {}
|
||||
class MyFloatingPanelSafeAreaLayout: FloatingPanelTop2BottomTestLayout {
|
||||
override var referenceGuide: FloatingPanelLayoutReferenceGuide {
|
||||
return .safeArea
|
||||
@@ -389,10 +438,9 @@ class LayoutTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.layout.anchors.filter({ $0.value.referenceGuide != .superview }).count, 0)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, bounds.height - myLayout.fullInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .half).y, myLayout.halfInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, myLayout.tipInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, myLayout.tipInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .hidden).y, -100.0)
|
||||
|
||||
|
||||
fpc.layout = MyFloatingPanelSafeAreaLayout()
|
||||
|
||||
XCTAssertEqual(fpc.layout.anchors.filter({ $0.value.referenceGuide != .safeArea }).count, 0)
|
||||
@@ -410,61 +458,93 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
|
||||
// fractional
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
}
|
||||
}
|
||||
func test_layoutAnchor_bottomPosition() {
|
||||
@@ -476,62 +556,94 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
|
||||
// fractional
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,32 +668,52 @@ class LayoutTests: XCTestCase {
|
||||
fpc.set(contentViewController: contentVC)
|
||||
|
||||
for prop in [
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,32 +738,52 @@ class LayoutTests: XCTestCase {
|
||||
fpc.set(contentViewController: contentVC)
|
||||
|
||||
for prop in [
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class StateTests: XCTestCase {
|
||||
override func setUp() { }
|
||||
override func tearDown() { }
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_nextAndPre() {
|
||||
var positions: [FloatingPanelState]
|
||||
positions = [.full, .half, .tip, .hidden]
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .half)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .half)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.pre(in: positions), .tip)
|
||||
|
||||
positions = [.full, .hidden]
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.pre(in: positions), .full)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class SurfaceViewTests: XCTestCase {
|
||||
@@ -68,12 +69,14 @@ class SurfaceViewTests: XCTestCase {
|
||||
|
||||
switch position {
|
||||
case .top:
|
||||
XCTAssertEqual(surface.containerView.frame,
|
||||
CGRect(x: 0.0, y: -height, width: 320.0, height: 480.0 * 3),
|
||||
line: line)
|
||||
XCTAssertEqual(surface.convert(surface.contentView?.frame ?? .zero, from: surface.containerView),
|
||||
surface.bounds,
|
||||
line: line)
|
||||
XCTAssertEqual(
|
||||
surface.containerView.frame,
|
||||
CGRect(x: 0.0, y: -height, width: 320.0, height: 480.0 * 3),
|
||||
line: line)
|
||||
XCTAssertEqual(
|
||||
surface.convert(surface.contentView?.frame ?? .zero, from: surface.containerView),
|
||||
surface.bounds,
|
||||
line: line)
|
||||
case .bottom:
|
||||
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds, line: line)
|
||||
default:
|
||||
@@ -82,14 +85,13 @@ class SurfaceViewTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func test_surfaceView_grabberHandle() {
|
||||
XCTContext.runActivity(named: "Bottom sheet") { _ in
|
||||
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
|
||||
XCTAssertNil(surface.contentView)
|
||||
surface.layoutIfNeeded()
|
||||
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, 6.0)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, 6.0)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.width, surface.grabberHandleSize.width)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.height, surface.grabberHandleSize.height)
|
||||
|
||||
@@ -97,7 +99,7 @@ class SurfaceViewTests: XCTestCase {
|
||||
surface.grabberHandleSize = CGSize(width: 44.0, height: 12.0)
|
||||
surface.setNeedsLayout()
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, surface.grabberHandlePadding)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, surface.grabberHandlePadding)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.width, surface.grabberHandleSize.width)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.height, surface.grabberHandleSize.height)
|
||||
}
|
||||
@@ -115,7 +117,7 @@ class SurfaceViewTests: XCTestCase {
|
||||
surface.grabberHandlePadding = 10.0
|
||||
surface.setNeedsLayout()
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssertEqual(surface.grabberHandle.frame.maxY, surface.bounds.maxY - surface.grabberHandlePadding)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.maxY, surface.bounds.maxY - surface.grabberHandlePadding)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +206,7 @@ class SurfaceViewTests: XCTestCase {
|
||||
XCTAssert(surface.containerView.layer.cornerRadius == 0.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == false)
|
||||
|
||||
surface.containerView.layer.cornerRadius = 12.0 // Don't change it directly
|
||||
surface.containerView.layer.cornerRadius = 12.0 // Don't change it directly
|
||||
XCTAssert(surface.containerView.layer.cornerRadius == 12.0)
|
||||
XCTAssertFalse(surface.containerView.layer.masksToBounds == true)
|
||||
|
||||
@@ -231,49 +233,65 @@ class SurfaceViewTests: XCTestCase {
|
||||
|
||||
func test_surfaceView_grabberArea() {
|
||||
let sv = SurfaceView()
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
sv.grabberAreaOffset = 44.0
|
||||
// Top
|
||||
do {
|
||||
sv.position = .top
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: 0,
|
||||
y: sv.bounds.height - sv.grabberAreaOffset,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: 0,
|
||||
y: sv.bounds.height - sv.grabberAreaOffset,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset
|
||||
)
|
||||
)
|
||||
}
|
||||
// Bottom
|
||||
do {
|
||||
sv.position = .bottom
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: 0,
|
||||
y: 0,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset
|
||||
)
|
||||
)
|
||||
}
|
||||
// Left
|
||||
do {
|
||||
sv.position = .left
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: sv.bounds.width - sv.grabberAreaOffset,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: sv.bounds.width - sv.grabberAreaOffset,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height
|
||||
)
|
||||
)
|
||||
}
|
||||
// Right
|
||||
do {
|
||||
sv.position = .right
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: 0,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func test_surfaceView_grabberAreaDetection() {
|
||||
let sv = SurfaceView()
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
// Top
|
||||
do {
|
||||
sv.position = .top
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
func waitRunLoop(secs: TimeInterval = 0) {
|
||||
@@ -40,7 +41,7 @@ class FloatingPanelTestLayout: FloatingPanelLayout {
|
||||
var referenceGuide: FloatingPanelLayoutReferenceGuide {
|
||||
return .superview
|
||||
}
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .top, referenceGuide: referenceGuide),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: halfInset, edge: .bottom, referenceGuide: referenceGuide),
|
||||
@@ -63,7 +64,7 @@ class FloatingPanelTop2BottomTestLayout: FloatingPanelLayout {
|
||||
var referenceGuide: FloatingPanelLayoutReferenceGuide {
|
||||
return .superview
|
||||
}
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .bottom, referenceGuide: referenceGuide),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: halfInset, edge: .top, referenceGuide: referenceGuide),
|
||||
@@ -98,4 +99,3 @@ class MockTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator
|
||||
var containerView: UIView { UIView() }
|
||||
var targetTransform: CGAffineTransform = .identity
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||
"state" : {
|
||||
"revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1",
|
||||
"version" : "1.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-format",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-format.git",
|
||||
"state" : {
|
||||
"branch" : "508.0.1",
|
||||
"revision" : "fbfe1869527923dd9f9b2edac148baccfce0dce7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-syntax",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-syntax.git",
|
||||
"state" : {
|
||||
"revision" : "cd793adf5680e138bf2bcbaacc292490175d0dcd",
|
||||
"version" : "508.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system.git",
|
||||
"state" : {
|
||||
"revision" : "836bc4557b74fe6d2660218d56e3ce96aff76574",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-tools-support-core",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-tools-support-core.git",
|
||||
"state" : {
|
||||
"revision" : "93784c59434dbca8e8a9e4b700d0d6d94551da6a",
|
||||
"version" : "0.5.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// swift-tools-version: 5.8
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "build-tools",
|
||||
platforms: [.macOS(.v12)],
|
||||
products: [
|
||||
.plugin(name: "SwiftFormatCommand", targets: ["SwiftFormatCommand"]),
|
||||
.plugin(name: "SwiftFormatBuildTool", targets: ["SwiftFormatBuildTool"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-format.git", branch: "508.0.1")
|
||||
],
|
||||
targets: [
|
||||
.plugin(
|
||||
name: "SwiftFormatCommand",
|
||||
capability: .command(
|
||||
intent: .sourceCodeFormatting(),
|
||||
permissions: [
|
||||
.writeToPackageDirectory(reason: "")
|
||||
]
|
||||
),
|
||||
dependencies: [
|
||||
.product(name: "swift-format", package: "swift-format")
|
||||
]
|
||||
),
|
||||
.plugin(
|
||||
name: "SwiftFormatBuildTool",
|
||||
capability: .buildTool(),
|
||||
dependencies: [
|
||||
.product(name: "swift-format", package: "swift-format")
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
import PackagePlugin
|
||||
|
||||
@main
|
||||
struct SwiftFormatBuildTool: BuildToolPlugin {
|
||||
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
|
||||
debugPrint("BuildToolPlugin -> \(context.package.directory)")
|
||||
let configFile = context.package.directory.appending(".swift-format")
|
||||
return [
|
||||
.prebuildCommand(
|
||||
displayName: "Run swift-format",
|
||||
executable: try context.tool(named: "swift-format").path,
|
||||
arguments: [
|
||||
"lint",
|
||||
"--configuration",
|
||||
configFile.string,
|
||||
"-r",
|
||||
"\(context.pluginWorkDirectory.string)",
|
||||
],
|
||||
outputFilesDirectory: context.package.directory
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(XcodeProjectPlugin)
|
||||
import XcodeProjectPlugin
|
||||
extension SwiftFormatBuildTool: XcodeBuildToolPlugin {
|
||||
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
|
||||
debugPrint("XcodeBuildToolPlugin -> \(context.xcodeProject.directory.string)")
|
||||
let configFile = context.xcodeProject.directory.appending(".swift-format")
|
||||
let sourceFiles = context.xcodeProject.directory.appending("Sources")
|
||||
let testFiles = context.xcodeProject.directory.appending("Tests")
|
||||
return [
|
||||
.buildCommand(
|
||||
displayName: "Run swift-format(xcode)",
|
||||
executable: try context.tool(named: "swift-format").path,
|
||||
arguments: [
|
||||
"lint",
|
||||
"--configuration",
|
||||
configFile.string,
|
||||
"-r",
|
||||
"\(sourceFiles.string)",
|
||||
"\(testFiles.string)",
|
||||
],
|
||||
inputFiles: [],
|
||||
outputFiles: []
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
import Foundation
|
||||
import PackagePlugin
|
||||
|
||||
@main
|
||||
struct SwiftFormatCommand: CommandPlugin {
|
||||
func performCommand(context: PluginContext, arguments: [String]) async throws {
|
||||
debugPrint("CommandPlugin start: \(context)")
|
||||
|
||||
let swiftFormatTool = try context.tool(named: "swift-format")
|
||||
debugPrint("XcodeCommandPlugin: swift-format: \(swiftFormatTool.path)")
|
||||
|
||||
let configFile = context.package.directory.appending(".swift-format")
|
||||
debugPrint("CommandPlugin using config: \(configFile)")
|
||||
|
||||
var argExtractor = ArgumentExtractor(arguments)
|
||||
let targetNames = argExtractor.extractOption(named: "target")
|
||||
let targetsToFormat = try context.package.targets(named: targetNames)
|
||||
|
||||
let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget }
|
||||
|
||||
try runCommand(swiftFormatTool, configFile: configFile, filePaths: sourceCodeTargets.map(\.directory))
|
||||
}
|
||||
|
||||
func runCommand(_ swiftFormatTool: PackagePlugin.PluginContext.Tool, configFile: Path, filePaths: [Path]) throws {
|
||||
// Invoke `swift-format` on the target directory, passing a configuration
|
||||
// file from the package directory.
|
||||
let swiftFormatExec = URL(fileURLWithPath: swiftFormatTool.path.string)
|
||||
let swiftFormatArgs =
|
||||
[
|
||||
"--configuration",
|
||||
"\(configFile.string)",
|
||||
"--in-place",
|
||||
"--recursive",
|
||||
] + filePaths.map(\.string)
|
||||
let process = try Process.run(swiftFormatExec, arguments: swiftFormatArgs)
|
||||
process.waitUntilExit()
|
||||
|
||||
debugPrint("result: \(process.terminationStatus)")
|
||||
|
||||
if process.terminationReason == .exit && process.terminationStatus == 0 {
|
||||
print("success.")
|
||||
} else {
|
||||
let problem = "\(process.terminationReason):\(process.terminationStatus)"
|
||||
Diagnostics.error("failed: \(problem)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(XcodeProjectPlugin)
|
||||
import XcodeProjectPlugin
|
||||
extension SwiftFormatCommand: XcodeCommandPlugin {
|
||||
func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws {
|
||||
debugPrint("start")
|
||||
|
||||
let swiftFormatTool = try context.tool(named: "swift-format")
|
||||
debugPrint("swift-format executable = \(swiftFormatTool.path)")
|
||||
|
||||
let configFile = context.xcodeProject.directory.appending(".swift-format")
|
||||
debugPrint("config file = \(swiftFormatTool.path)")
|
||||
|
||||
var argExtractor = ArgumentExtractor(arguments)
|
||||
let targetNames = argExtractor.extractOption(named: "target")
|
||||
let xcodeTargets = context.xcodeProject.targets.filter { targetNames.contains($0.product?.name ?? "") }
|
||||
|
||||
let filePaths = xcodeTargets.flatMap(\.inputFiles).filter { $0.type == .source }.map(\.path)
|
||||
|
||||
debugPrint("files to format = \(filePaths.map(\.lastComponent))")
|
||||
|
||||
try runCommand(swiftFormatTool, configFile: configFile, filePaths: filePaths)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user