Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b34f1093de | |||
| de1dbe70de | |||
| c593c646ca | |||
| 0cb5307a61 | |||
| 8ab3b7986c | |||
| bbdf3e7c6f | |||
| bdb756b665 | |||
| 6611ec83a2 | |||
| a917d6a626 | |||
| 7511ce577d | |||
| e7d0a72440 | |||
| 44923ef66e | |||
| 2760bc7298 | |||
| d428e96b03 | |||
| 67495961e5 |
@@ -9,34 +9,6 @@ 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:
|
||||
@@ -79,10 +51,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: "16.1"
|
||||
xcode: "14.1"
|
||||
- os: "16.4"
|
||||
xcode: "14.3.1"
|
||||
sim: "iPhone 14 Pro"
|
||||
runsOn: macos-12
|
||||
runsOn: macos-13
|
||||
- os: "15.5"
|
||||
xcode: "13.4.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [FloatingPanel]
|
||||
platform: ios
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -10,6 +10,7 @@ enum UseCase: Int, CaseIterable {
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showOnWindow
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
@@ -34,6 +35,7 @@ extension UseCase {
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showOnWindow: return "Show Panel over Window"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
@@ -65,6 +67,7 @@ extension UseCase {
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showMultiPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showOnWindow: return .viewController(DebugTableViewController())
|
||||
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
|
||||
|
||||
@@ -11,6 +11,7 @@ final class UseCaseController: NSObject {
|
||||
private var detailPanelVC: FloatingPanelController!
|
||||
private var settingsPanelVC: FloatingPanelController!
|
||||
private lazy var pagePanelController = PagePanelController()
|
||||
private lazy var overWindowPanelVC = FloatingPanelController()
|
||||
|
||||
init(mainVC: MainViewController) {
|
||||
self.mainVC = mainVC
|
||||
@@ -157,6 +158,19 @@ extension UseCaseController {
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showOnWindow:
|
||||
let fpc = overWindowPanelVC
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
|
||||
guard let window = UIApplication.shared.windows.first else { fatalError("Any window not found") }
|
||||
|
||||
window.addSubview(fpc.view)
|
||||
fpc.view.frame = window.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
fpc.show(animated: true)
|
||||
case .showPanelInSheetModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = UIViewController()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.6.2"
|
||||
s.version = "2.6.5"
|
||||
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
|
||||
s.description = <<-DESC
|
||||
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
549E944522CF295D0050AECF /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* StateTests.swift */; };
|
||||
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* CoreTests.swift */; };
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logging.swift */; };
|
||||
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* SurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* BackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* Layout.swift */; };
|
||||
@@ -67,7 +67,7 @@
|
||||
549E944422CF295D0050AECF /* StateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceViewTests.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
|
||||
@@ -133,7 +133,7 @@
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
|
||||
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
|
||||
@@ -190,7 +190,6 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
54B58FC929EB95880009567E /* PBXTargetDependency */,
|
||||
);
|
||||
name = FloatingPanel;
|
||||
productName = FloatingModalController;
|
||||
@@ -243,8 +242,6 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 545DB9B72151169500CA77B8;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 545DB9C22151169500CA77B8 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -285,7 +282,7 @@
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
|
||||
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
|
||||
@@ -318,10 +315,6 @@
|
||||
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
|
||||
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
54B58FC929EB95880009567E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 54B58FC829EB95880009567E /* SwiftFormatBuildTool */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -701,13 +694,6 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
54B58FC829EB95880009567E /* SwiftFormatBuildTool */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "plugin:SwiftFormatBuildTool";
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app.
|
||||
The user interface displays related content and utilities alongside the main content.
|
||||
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.6.2/documentation/floatingpanel/) for more details, powered by [DocC](https://developer.apple.com/documentation/docc).
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.6.5/documentation/floatingpanel/) for more details.
|
||||
|
||||

|
||||

|
||||
|
||||
+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}
|
||||
}
|
||||
}
|
||||
|
||||
+76
-75
@@ -1,82 +1,85 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
|
||||
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
|
||||
/// 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
|
||||
///
|
||||
@@ -86,7 +89,8 @@ 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
|
||||
}
|
||||
|
||||
///
|
||||
@@ -111,9 +115,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()
|
||||
}
|
||||
}
|
||||
@@ -164,7 +168,8 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_layout = newValue
|
||||
if let parent = parent, let layout = newValue as? UIViewController, layout == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.")
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its layout object. Don't allow the parent to adopt FloatingPanelLayout."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +181,8 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_behavior = newValue
|
||||
if let parent = parent, let behavior = newValue as? UIViewController, behavior == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.")
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its behavior object. Don't allow the parent to adopt FloatingPanelBehavior."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +196,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.
|
||||
@@ -225,7 +231,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()
|
||||
|
||||
@@ -264,7 +270,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
|
||||
}
|
||||
@@ -370,9 +376,9 @@ open class FloatingPanelController: UIViewController {
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
guard
|
||||
preSafeAreaInsets != safeAreaInsets
|
||||
else { return }
|
||||
else { return }
|
||||
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
os_log(msg, log: devLog, type: .debug, "Update safeAreaInsets = \(safeAreaInsets)")
|
||||
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
@@ -411,8 +417,14 @@ open class FloatingPanelController: UIViewController {
|
||||
guard let self = self else { return }
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
} else {
|
||||
} else if parent != nil {
|
||||
removePanelFromParent(animated: true)
|
||||
} else {
|
||||
hide(animated: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.view.removeFromSuperview()
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,21 +463,17 @@ 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.
|
||||
@@ -477,7 +485,7 @@ open class FloatingPanelController: UIViewController {
|
||||
@objc(addPanelToParent:at:animated:completion:)
|
||||
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
guard self.parent == nil else {
|
||||
log.warning("Already added to a parent(\(parent))")
|
||||
os_log(msg, log: sysLog, type: .error, "Warning: already added to a parent(\(parent))")
|
||||
return
|
||||
}
|
||||
assert((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
|
||||
@@ -494,14 +502,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 }
|
||||
@@ -595,7 +603,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(_:)`,
|
||||
@@ -675,41 +683,34 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Swizzling
|
||||
|
||||
private var originalDismissImp: IMP?
|
||||
private typealias __dismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -717,7 +718,7 @@ extension FloatingPanelController {
|
||||
extension UIViewController {
|
||||
@objc
|
||||
fileprivate func __swizzled_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: __dismissFunction.self)
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: DismissFunction.self)
|
||||
let sel = #selector(UIViewController.dismiss(animated:completion:))
|
||||
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
|
||||
+188
-164
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
///
|
||||
/// The presentation model of FloatingPanel
|
||||
@@ -20,11 +21,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if let cur = scrollView {
|
||||
if oldValue == nil {
|
||||
initialScrollOffset = cur.contentOffset
|
||||
scrollBounce = cur.bounces
|
||||
scrollIndictorVisible = cur.showsVerticalScrollIndicator
|
||||
}
|
||||
} else {
|
||||
if let pre = oldValue {
|
||||
pre.isDirectionalLockEnabled = false
|
||||
pre.bounces = scrollBounce
|
||||
pre.showsVerticalScrollIndicator = scrollIndictorVisible
|
||||
}
|
||||
}
|
||||
@@ -33,7 +36,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
private(set) var state: FloatingPanelState = .hidden {
|
||||
didSet {
|
||||
log.debug("state changed: \(oldValue) -> \(state)")
|
||||
os_log(msg, log: devLog, type: .debug, "state changed: \(oldValue) -> \(state)")
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelDidChangeState?(vc)
|
||||
}
|
||||
@@ -43,7 +46,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?
|
||||
|
||||
@@ -62,7 +65,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// Scroll handling
|
||||
private var initialScrollOffset: CGPoint = .zero
|
||||
private var stopScrollDeceleration: Bool = false
|
||||
private var scrollBounce = false
|
||||
private var scrollIndictorVisible = false
|
||||
private var scrollBounceThreshold: CGFloat = -30.0
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
@@ -123,7 +128,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if animated {
|
||||
let updateScrollView: () -> Void = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.state == self.layoutAdapter.mostExpandedState, abs(self.layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
if self.state == self.layoutAdapter.mostExpandedState, 0 == self.layoutAdapter.offsetFromMostExpandedAnchor {
|
||||
self.unlockScrollView()
|
||||
} else {
|
||||
self.lockScrollView()
|
||||
@@ -148,8 +153,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let shouldDoubleLayout =
|
||||
from == .hidden
|
||||
let shouldDoubleLayout = from == .hidden
|
||||
&& surfaceView.hasStackView()
|
||||
&& layoutAdapter.isIntrinsicAnchor(state: to)
|
||||
|
||||
@@ -160,7 +164,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
self.updateLayout(to: to)
|
||||
|
||||
if shouldDoubleLayout {
|
||||
log.info("Lay out the surface again to modify an intrinsic size error according to UIStackView")
|
||||
os_log(msg, log: sysLog, type: .info, "Lay out the surface again to modify an intrinsic size error according to UIStackView")
|
||||
self.updateLayout(to: to)
|
||||
}
|
||||
}
|
||||
@@ -229,7 +233,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func getBackdropAlpha(at cur: CGFloat, with translation: CGFloat) -> CGFloat {
|
||||
// log.debug("currentY: \(currentY) translation: \(translation)")
|
||||
/* os_log(msg, log: devLog, type: .debug, "currentY: \(currentY) translation: \(translation)") */
|
||||
let forwardY = (translation >= 0)
|
||||
|
||||
let segment = layoutAdapter.segment(at: cur, forward: forwardY)
|
||||
@@ -249,32 +253,30 @@ 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)
|
||||
/* os_log(msg, log: devLog, type: .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
|
||||
}
|
||||
@@ -300,7 +302,8 @@ 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
|
||||
}
|
||||
@@ -330,14 +333,16 @@ 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)) {
|
||||
return false
|
||||
}
|
||||
return allowScrollPanGesture(for: scrollView)
|
||||
guard state == layoutAdapter.mostExpandedState else { return false }
|
||||
// The condition where offset > 0 must not be included here. Because it will stop recognizing
|
||||
// the panel pan gesture if a user starts scrolling content from an offset greater than 0.
|
||||
return allowScrollPanGesture(of: scrollView) { offset in offset <= scrollBounceThreshold }
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -353,11 +358,12 @@ 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,10 +393,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let velocity = value(of: panGesture.velocity(in: panGesture.view))
|
||||
let location = panGesture.location(in: surfaceView)
|
||||
|
||||
let insideMostExpandedAnchor = 0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)
|
||||
let insideMostExpandedAnchor = 0 > layoutAdapter.offsetFromMostExpandedAnchor
|
||||
|
||||
log.debug(
|
||||
"""
|
||||
os_log(msg, log: devLog, type: .debug, """
|
||||
scroll gesture(\(state):\(panGesture.state)) -- \
|
||||
inside expanded anchor = \(insideMostExpandedAnchor), \
|
||||
interactionInProgress = \(interactionInProgress), \
|
||||
@@ -405,7 +410,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// Scroll offset pinning
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
if interactionInProgress {
|
||||
log.debug("settle offset --", value(of: initialScrollOffset))
|
||||
os_log(msg, log: devLog, type: .debug, "settle offset -- \(value(of: initialScrollOffset))")
|
||||
stopScrolling(at: initialScrollOffset)
|
||||
} else {
|
||||
if surfaceView.grabberAreaContains(location) {
|
||||
@@ -457,21 +462,24 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
} else {
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
let allowScroll = allowScrollPanGesture(of: scrollView) { offset in
|
||||
offset <= scrollBounceThreshold || 0 < offset
|
||||
}
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
if velocity < 0, !allowScrollPanGesture(for: scrollView) {
|
||||
lockScrollView()
|
||||
if velocity < 0, !allowScroll {
|
||||
lockScrollView(strict: true)
|
||||
}
|
||||
if velocity > 0, allowScrollPanGesture(for: scrollView) {
|
||||
if velocity > 0, allowScroll {
|
||||
unlockScrollView()
|
||||
}
|
||||
case .bottom, .right:
|
||||
// Hide a scroll indicator just before starting an interaction by swiping a panel down.
|
||||
if velocity > 0, !allowScrollPanGesture(for: scrollView) {
|
||||
lockScrollView()
|
||||
if velocity > 0, !allowScroll {
|
||||
lockScrollView(strict: true)
|
||||
}
|
||||
// Show a scroll indicator when an animation is interrupted at the top and content is scrolled up
|
||||
if velocity < 0, allowScrollPanGesture(for: scrollView) {
|
||||
if velocity < 0, allowScroll {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
@@ -483,22 +491,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
case panGestureRecognizer:
|
||||
let translation = panGesture.translation(in: panGesture.view?.superview)
|
||||
let translation = panGesture.translation(in: panGestureRecognizer.view!.superview)
|
||||
let velocity = panGesture.velocity(in: panGesture.view)
|
||||
let location = panGesture.location(in: panGesture.view)
|
||||
|
||||
log.debug(
|
||||
"""
|
||||
os_log(msg, log: devLog, type: .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
|
||||
}
|
||||
|
||||
@@ -525,11 +530,9 @@ 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:
|
||||
@@ -542,19 +545,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
private func interruptAnimationIfNeeded() {
|
||||
if let animator = self.moveAnimator, animator.isRunning {
|
||||
log.debug("the attraction animator interrupted!!!")
|
||||
os_log(msg, log: devLog, type: .debug, "the attraction animator interrupted!!!")
|
||||
animator.stopAnimation(true)
|
||||
endAttraction(false)
|
||||
}
|
||||
if let animator = self.transitionAnimator {
|
||||
guard 0 >= layoutAdapter.offsetFromMostExpandedAnchor else { return }
|
||||
log.debug("a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
|
||||
os_log(msg, log: devLog, type: .debug, "a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(false)
|
||||
// A user can stop a panel at the nearest Y of a target position so this fine-tunes
|
||||
// the a small gap between the presentation layer frame and model layer frame
|
||||
// to unlock scroll view properly at finishAnimation(at:)
|
||||
if abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
if 0 == layoutAdapter.offsetFromMostExpandedAnchor {
|
||||
layoutAdapter.surfaceLocation = layoutAdapter.surfaceLocation(for: layoutAdapter.mostExpandedState)
|
||||
}
|
||||
animator.finishAnimation(at: .current)
|
||||
@@ -582,7 +585,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
|
||||
@@ -600,8 +603,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
|
||||
}
|
||||
@@ -611,14 +614,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 {
|
||||
@@ -640,7 +643,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// A user interaction does not always start from Began state of the pan gesture
|
||||
// because it can be recognized in scrolling a content in a content view controller.
|
||||
// So here just preserve the current state if needed.
|
||||
log.debug("panningBegan -- location = \(value(of: location))")
|
||||
os_log(msg, log: devLog, type: .debug, "panningBegan -- location = \(value(of: location))")
|
||||
|
||||
guard let scrollView = scrollView else { return }
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
@@ -653,22 +656,20 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func panningChange(with translation: CGPoint) {
|
||||
log.debug("panningChange -- translation = \(value(of: translation))")
|
||||
os_log(msg, log: devLog, type: .debug, "panningChange -- translation = \(value(of: translation))")
|
||||
let pre = value(of: layoutAdapter.surfaceLocation)
|
||||
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)
|
||||
@@ -683,19 +684,23 @@ 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,18 +709,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
|
||||
log.debug("panningEnd -- translation = \(value(of: translation)), velocity = \(value(of: velocity))")
|
||||
os_log(msg, log: devLog, type: .debug, "panningEnd -- translation = \(value(of: translation)), velocity = \(value(of: velocity))")
|
||||
|
||||
if state == .hidden {
|
||||
log.debug("Already hidden")
|
||||
os_log(msg, log: devLog, type: .debug, "Already hidden")
|
||||
return
|
||||
}
|
||||
|
||||
stopScrollDeceleration = (0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)) // Projecting the dragging to the scroll dragging or not
|
||||
// Determine whether the panel's dragging should be projected onto the scroll content scrolling
|
||||
stopScrollDeceleration = 0 > layoutAdapter.offsetFromMostExpandedAnchor
|
||||
if stopScrollDeceleration {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
|
||||
self.stopScrolling(at: self.initialScrollOffset)
|
||||
}
|
||||
}
|
||||
@@ -730,9 +736,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()
|
||||
@@ -768,7 +774,8 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -794,8 +801,8 @@ 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
|
||||
log.debug("startInteraction -- translation = \(value(of: translation)), location = \(value(of: location))")
|
||||
/* Don't lock a scroll view to show a scroll indicator after hitting the top */
|
||||
os_log(msg, log: devLog, type: .debug, "startInteraction -- translation = \(value(of: translation)), location = \(value(of: location))")
|
||||
guard interactionInProgress == false else { return }
|
||||
|
||||
var offset: CGPoint = .zero
|
||||
@@ -806,13 +813,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
} else {
|
||||
let pinningOffset = contentOffsetForPinning(of: scrollView)
|
||||
|
||||
// `scrollView.contentOffset` can be a value in [-30, 0) at this time by `allowScrollPanGesture(for:)`.
|
||||
// Therefore the initial scroll offset must be reset to the pinning offset. Otherwise, the following
|
||||
// `Fit the surface bounds` logic don't working and also the scroll content offset can be invalid.
|
||||
|
||||
// `initialScrollOffset` must be reset to the pinning offset because the value of `scrollView.contentOffset`,
|
||||
// for instance, is a value in [-30, 0) on a bottom positioned panel with `allowScrollPanGesture(of:condition:)`.
|
||||
// If it's not reset, the following logic to shift the surface frame will not work and then the scroll
|
||||
// content offset will become an unexpected value.
|
||||
initialScrollOffset = pinningOffset
|
||||
|
||||
// Fit the surface bounds to a scroll offset content by startInteraction(at:offset:)
|
||||
// Shift the surface frame to negate the scroll content offset at startInteraction(at:offset:)
|
||||
let offsetDiff = scrollView.contentOffset - pinningOffset
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
@@ -825,7 +833,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("initial scroll offset --", initialScrollOffset)
|
||||
os_log(msg, log: devLog, type: .debug, "initial scroll offset -- \(initialScrollOffset)")
|
||||
}
|
||||
|
||||
initialTranslation = translation
|
||||
@@ -842,10 +850,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func endInteraction(for targetPosition: FloatingPanelState) {
|
||||
log.debug("endInteraction to \(targetPosition)")
|
||||
os_log(msg, log: devLog, type: .debug, "endInteraction to \(targetPosition)")
|
||||
|
||||
if let scrollView = scrollView {
|
||||
log.debug("endInteraction -- scroll offset = \(scrollView.contentOffset)")
|
||||
os_log(msg, log: devLog, type: .debug, "endInteraction -- scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
interactionInProgress = false
|
||||
@@ -873,7 +881,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func startAttraction(to targetPosition: FloatingPanelState, with velocity: CGPoint) {
|
||||
log.debug("startAnimation to \(targetPosition) -- velocity = \(value(of: velocity))")
|
||||
os_log(msg, log: devLog, type: .debug, "startAnimation to \(targetPosition) -- velocity = \(value(of: velocity))")
|
||||
guard let vc = ownerVC else { return }
|
||||
|
||||
isAttracting = true
|
||||
@@ -893,25 +901,21 @@ 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
|
||||
}
|
||||
@@ -925,19 +929,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
if let scrollView = scrollView {
|
||||
log.debug("finishAnimation -- scroll offset = \(scrollView.contentOffset)")
|
||||
os_log(msg, log: devLog, type: .debug, "finishAnimation -- scroll offset = \(scrollView.contentOffset)")
|
||||
}
|
||||
|
||||
stopScrollDeceleration = false
|
||||
|
||||
log.debug(
|
||||
"""
|
||||
os_log(msg, log: devLog, type: .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 {
|
||||
""")
|
||||
if finished, state == layoutAdapter.mostExpandedState, 0 == layoutAdapter.offsetFromMostExpandedAnchor {
|
||||
unlockScrollView()
|
||||
} else if finished, shouldLooselyLockScrollView {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
@@ -946,6 +950,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return layoutAdapter.position.mainLocation(point)
|
||||
}
|
||||
|
||||
func value(of size: CGSize) -> CGFloat {
|
||||
return layoutAdapter.position.mainDimension(size)
|
||||
}
|
||||
|
||||
func setValue(_ newValue: CGPoint, to point: inout CGPoint) {
|
||||
switch layoutAdapter.position {
|
||||
case .top, .bottom:
|
||||
@@ -962,15 +970,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func targetPosition(from currentY: CGFloat, with velocity: CGFloat) -> (FloatingPanelState) {
|
||||
log.debug("targetPosition -- currentY = \(currentY), velocity = \(velocity)")
|
||||
os_log(msg, log: devLog, type: .debug, "targetPosition -- currentY = \(currentY), velocity = \(velocity)")
|
||||
|
||||
let sortedPositions = layoutAdapter.sortedAnchorStatesByCoordinate
|
||||
|
||||
guard
|
||||
sortedPositions.count > 1,
|
||||
let firstPosition = sortedPositions.first,
|
||||
let lastPosition = sortedPositions.last
|
||||
else {
|
||||
guard sortedPositions.count > 1 else {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -988,13 +992,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
var fromPos: FloatingPanelState
|
||||
var toPos: FloatingPanelState
|
||||
|
||||
let (lowerPos, upperPos) = (segment.lower ?? firstPosition, segment.upper ?? lastPosition)
|
||||
let (lowerPos, upperPos) = (segment.lower ?? sortedPositions.first!, segment.upper ?? sortedPositions.last!)
|
||||
(fromPos, toPos) = forwardY ? (lowerPos, upperPos) : (upperPos, lowerPos)
|
||||
|
||||
if behaviorAdapter.shouldProjectMomentum(to: toPos) == false {
|
||||
log.debug("targetPosition -- negate projection: distance = \(distance)")
|
||||
os_log(msg, log: devLog, type: .debug, "targetPosition -- negate projection: distance = \(distance)")
|
||||
let segment = layoutAdapter.segment(at: currentY, forward: forwardY)
|
||||
var (lowerPos, upperPos) = (segment.lower ?? firstPosition, segment.upper ?? lastPosition)
|
||||
var (lowerPos, upperPos) = (segment.lower ?? sortedPositions.first!, segment.upper ?? sortedPositions.last!)
|
||||
// Equate the segment out of {top,bottom} most state to the {top,bottom} most segment
|
||||
if lowerPos == upperPos {
|
||||
if forwardY {
|
||||
@@ -1037,36 +1041,60 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
scrollView.transform = CGAffineTransform(translationX: 0, y: contentOffset)
|
||||
}
|
||||
|
||||
private func lockScrollView() {
|
||||
private func lockScrollView(strict: Bool = false) {
|
||||
guard let scrollView = scrollView else { return }
|
||||
|
||||
if scrollView.isLocked {
|
||||
log.debug("Already scroll locked.")
|
||||
os_log(msg, log: devLog, type: .debug, "Already scroll locked.")
|
||||
return
|
||||
}
|
||||
log.debug("lock scroll view")
|
||||
os_log(msg, log: devLog, type: .debug, "lock scroll view")
|
||||
|
||||
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
|
||||
|
||||
// Must not modify the UIScrollView.bounces property here. If you reset it to unlock the tracking scroll view,
|
||||
// UIScrollView may unexpectedly alter the scroll offset when dealing with small scrollable content.
|
||||
if !strict, shouldLooselyLockScrollView {
|
||||
// Don't change its `bounces` property. If it's changed, it will cause its scroll content offset jump at
|
||||
// the most expanded anchor position while seamlessly scrolling content. This problem only occurs where its
|
||||
// content mode is `.fitToBounds` and the tracking scroll content is smaller than the content view size.
|
||||
// The reason why is because `bounces` prop change leads to the "content frame" change on `.fitToBounds`.
|
||||
// See also https://github.com/scenee/FloatingPanel/issues/524.
|
||||
} else {
|
||||
scrollBounce = scrollView.bounces
|
||||
scrollView.bounces = false
|
||||
}
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
}
|
||||
|
||||
private func unlockScrollView() {
|
||||
guard let scrollView = scrollView, scrollView.isLocked else { return }
|
||||
log.debug("unlock scroll view")
|
||||
os_log(msg, log: devLog, type: .debug, "unlock scroll view")
|
||||
|
||||
scrollView.bounces = scrollBounce
|
||||
scrollView.isDirectionalLockEnabled = false
|
||||
scrollView.showsVerticalScrollIndicator = scrollIndictorVisible
|
||||
}
|
||||
|
||||
private var shouldLooselyLockScrollView: Bool {
|
||||
var isSmallScrollContentAndFitToBoundsMode: Bool {
|
||||
if ownerVC?.contentMode == .fitToBounds, let scrollView = scrollView,
|
||||
value(of: scrollView.contentSize) < value(of: scrollView.bounds.size) - min(layoutAdapter.offsetFromMostExpandedAnchor, 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return isSmallScrollContentAndFitToBoundsMode
|
||||
}
|
||||
|
||||
private func stopScrolling(at contentOffset: CGPoint) {
|
||||
// Must use setContentOffset(_:animated) to force-stop deceleration
|
||||
guard let scrollView = scrollView else { return }
|
||||
var offset = scrollView.contentOffset
|
||||
setValue(contentOffset, to: &offset)
|
||||
if contentOffset.y >= 0 {
|
||||
setValue(contentOffset, to: &offset)
|
||||
} else {
|
||||
offset = CGPoint(x: 0, y: 0)
|
||||
}
|
||||
scrollView.setContentOffset(offset, animated: false)
|
||||
}
|
||||
|
||||
@@ -1086,16 +1114,15 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func allowScrollPanGesture(for scrollView: UIScrollView) -> Bool {
|
||||
guard state == layoutAdapter.mostExpandedState else { return false }
|
||||
var offsetY: CGFloat = 0
|
||||
private func allowScrollPanGesture(of scrollView: UIScrollView, condition: (_ offset: CGFloat) -> Bool) -> Bool {
|
||||
var offset: CGFloat = 0
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
offsetY = value(of: scrollView.fp_contentOffsetMax - scrollView.contentOffset)
|
||||
offset = value(of: scrollView.fp_contentOffsetMax - scrollView.contentOffset)
|
||||
case .bottom, .right:
|
||||
offsetY = value(of: scrollView.contentOffset - contentOffsetForPinning(of: scrollView))
|
||||
offset = value(of: scrollView.contentOffset - contentOffsetForPinning(of: scrollView))
|
||||
}
|
||||
return offsetY <= -30.0 || offsetY > 0
|
||||
return condition(offset)
|
||||
}
|
||||
|
||||
// MARK: - UIPanGestureRecognizer Intermediation
|
||||
@@ -1133,11 +1160,9 @@ 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
|
||||
}
|
||||
@@ -1150,7 +1175,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1166,13 +1191,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1192,23 +1217,21 @@ 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
|
||||
@@ -1216,14 +1239,14 @@ private class NumericSpringAnimator: NSObject {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func startAnimation() -> Bool {
|
||||
func startAnimation() -> Bool{
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
if isRunning {
|
||||
return false
|
||||
}
|
||||
log.debug("startAnimation --", displayLink)
|
||||
os_log(msg, log: devLog, type: .debug, "startAnimation --", displayLink)
|
||||
isRunning = true
|
||||
displayLink.add(to: RunLoop.main, forMode: .common)
|
||||
return true
|
||||
@@ -1235,7 +1258,7 @@ private class NumericSpringAnimator: NSObject {
|
||||
if locked { lock.unlock() }
|
||||
}
|
||||
|
||||
log.debug("stopAnimation --", displayLink)
|
||||
os_log(msg, log: devLog, type: .debug, "stopAnimation --", displayLink)
|
||||
isRunning = false
|
||||
displayLink.invalidate()
|
||||
if withoutFinishing {
|
||||
@@ -1252,28 +1275,29 @@ 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)
|
||||
|
||||
+53
-86
@@ -10,7 +10,7 @@ extension CGFloat {
|
||||
return (self * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
|
||||
}
|
||||
func isEqual(to: CGFloat, on displayScale: CGFloat) -> Bool {
|
||||
return self.rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
return rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,12 @@ 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
|
||||
@@ -74,27 +72,23 @@ 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 ?? view
|
||||
return view!.safeAreaLayoutGuide
|
||||
} 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,14 +133,9 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +157,7 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
|
||||
extension UIScrollView {
|
||||
var isLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
|
||||
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
|
||||
}
|
||||
var fp_contentInset: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
@@ -178,10 +167,8 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +208,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()
|
||||
@@ -231,61 +218,45 @@ 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)
|
||||
@@ -295,13 +266,9 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.6.2</string>
|
||||
<string>2.6.5</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
+65
-99
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An interface for generating layout information for a panel.
|
||||
@objc public protocol FloatingPanelLayout {
|
||||
@@ -30,7 +31,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,42 +154,36 @@ 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
|
||||
@@ -271,12 +266,14 @@ class LayoutAdapter {
|
||||
}
|
||||
|
||||
var offsetFromMostExpandedAnchor: CGFloat {
|
||||
let offset: CGFloat
|
||||
switch position {
|
||||
case .top, .left:
|
||||
return edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
|
||||
offset = edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
|
||||
case .bottom, .right:
|
||||
return position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
|
||||
offset = position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
|
||||
}
|
||||
return offset.rounded(by: surfaceView.fp_displayScale)
|
||||
}
|
||||
|
||||
private var hiddenAnchor: FloatingPanelLayoutAnchoring {
|
||||
@@ -439,10 +436,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
|
||||
|
||||
@@ -477,10 +474,7 @@ 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)
|
||||
@@ -558,10 +552,8 @@ 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
|
||||
@@ -569,10 +561,8 @@ 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
|
||||
@@ -584,20 +574,16 @@ 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
|
||||
@@ -608,20 +594,16 @@ 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
|
||||
@@ -633,20 +615,16 @@ 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
|
||||
@@ -672,7 +650,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
|
||||
|
||||
@@ -681,10 +659,7 @@ class LayoutAdapter {
|
||||
return
|
||||
}
|
||||
|
||||
guard let anchor = layout.anchors[mostExpandedState] else {
|
||||
return
|
||||
}
|
||||
|
||||
let anchor = layout.anchors[self.mostExpandedState]!
|
||||
let surfaceAnchor = position.mainDimensionAnchor(surfaceView)
|
||||
switch anchor {
|
||||
case let anchor as FloatingPanelIntrinsicLayoutAnchor:
|
||||
@@ -703,15 +678,11 @@ 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 {
|
||||
@@ -723,10 +694,8 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,14 +706,14 @@ 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)
|
||||
}
|
||||
|
||||
func updateInteractiveEdgeConstraint(diff: CGFloat, scrollingContent: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
|
||||
defer {
|
||||
log.debug("update surface location = \(surfaceLocation)")
|
||||
os_log(msg, log: devLog, type: .debug, "update surface location = \(surfaceLocation)")
|
||||
}
|
||||
|
||||
let minConst: CGFloat = position(for: leastCoordinateState)
|
||||
@@ -784,9 +753,9 @@ class LayoutAdapter {
|
||||
defer {
|
||||
if forceLayout {
|
||||
layoutSurfaceIfNeeded()
|
||||
log.debug("activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
} else {
|
||||
log.debug("activateLayout for \(state)")
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,7 +790,7 @@ class LayoutAdapter {
|
||||
if let constraints = stateConstraints[state] {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
} else {
|
||||
log.error("Couldn't find any constraints for \(state)")
|
||||
os_log(msg, log: sysLog, type: .fault, "Error: can not find any constraints for \(state)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -840,12 +809,9 @@ 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,15 +3,13 @@
|
||||
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
|
||||
public final class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
@objc final public class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
|
||||
/// Returns a layout anchor with the specified inset by an absolute value, edge and reference guide for a panel.
|
||||
///
|
||||
@@ -52,8 +50,8 @@ public final class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAncho
|
||||
@objc let referenceEdge: FloatingPanelReferenceEdge
|
||||
}
|
||||
|
||||
extension FloatingPanelLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
public extension FloatingPanelLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
switch position {
|
||||
case .top:
|
||||
@@ -74,7 +72,7 @@ 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)]
|
||||
@@ -107,8 +105,7 @@ extension FloatingPanelLayoutAnchor {
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with the intrinsic size for a content.
|
||||
@objc
|
||||
public final class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
@objc final public class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
|
||||
/// Returns a layout anchor with the specified offset by an absolute value and reference guide for a panel.
|
||||
///
|
||||
@@ -144,8 +141,8 @@ public final class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLa
|
||||
@objc public let referenceGuide: FloatingPanelLayoutReferenceGuide
|
||||
}
|
||||
|
||||
extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
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)
|
||||
@@ -163,8 +160,7 @@ extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with a layout guide of a content view.
|
||||
@objc
|
||||
public final class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
@objc final public class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
|
||||
/// Returns a layout anchor with the specified offset by an absolute value to display a panel with its intrinsic content size.
|
||||
///
|
||||
@@ -226,8 +222,8 @@ public final class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLay
|
||||
@objc public let contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide
|
||||
}
|
||||
|
||||
extension FloatingPanelAdaptiveLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
public extension FloatingPanelAdaptiveLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
// Must be a variable to use `hook` property in testing
|
||||
var log = {
|
||||
return Logger()
|
||||
}()
|
||||
|
||||
struct Logger {
|
||||
private let osLog: OSLog
|
||||
private let s = DispatchSemaphore(value: 1)
|
||||
|
||||
enum Level: Int, Comparable {
|
||||
case debug = 0
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "Debug:"
|
||||
case .info:
|
||||
return "Info:"
|
||||
case .warning:
|
||||
return "Warning:"
|
||||
case .error:
|
||||
return "Error:"
|
||||
}
|
||||
}
|
||||
@available(iOS 10.0, *)
|
||||
var osLogType: OSLogType {
|
||||
switch self {
|
||||
case .debug: return .debug
|
||||
case .info: return .info
|
||||
case .warning: return .default
|
||||
case .error: return .error
|
||||
}
|
||||
}
|
||||
|
||||
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
typealias Hook = ((String, Level) -> Void)
|
||||
var hook: Hook?
|
||||
|
||||
fileprivate init() {
|
||||
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
|
||||
}
|
||||
|
||||
private func log(_ level: Level, _ message: Any, _ arguments: [Any], tag: String, function: String, line: UInt) {
|
||||
_ = s.wait(timeout: .now() + 0.033)
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let _tag = tag.isEmpty ? "" : "\(tag):"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
hook?(log, level)
|
||||
|
||||
os_log("%{public}@", log: osLog, type: level.osLogType, log)
|
||||
}
|
||||
|
||||
private func getPrettyFunction(_ function: String, _ file: String) -> String {
|
||||
if let filename = file.split(separator: "/").last {
|
||||
return filename + ":" + function
|
||||
} else {
|
||||
return file + ":" + function
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import os.log
|
||||
|
||||
let msg = StaticString("%{public}@")
|
||||
let sysLog = OSLog(subsystem: Logging.subsystem, category: Logging.category)
|
||||
#if FP_LOG
|
||||
let devLog = OSLog(subsystem: Logging.subsystem, category: "\(Logging.category):dev")
|
||||
#else
|
||||
let devLog = OSLog.disabled
|
||||
#endif
|
||||
|
||||
struct Logging {
|
||||
static let subsystem = "com.scenee.FloatingPanel"
|
||||
static let category = "FloatingPanel"
|
||||
private init() {}
|
||||
}
|
||||
+79
-124
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An object for customizing the appearance of a surface view
|
||||
@objc(FloatingPanelSurfaceAppearance)
|
||||
@@ -81,11 +82,9 @@ 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
|
||||
@@ -93,11 +92,9 @@ 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?
|
||||
@@ -112,26 +109,19 @@ 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.
|
||||
///
|
||||
@@ -141,7 +131,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,
|
||||
@@ -153,12 +143,10 @@ 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)
|
||||
@@ -181,12 +169,10 @@ 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()
|
||||
}
|
||||
}
|
||||
@@ -194,25 +180,17 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +210,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] = [] {
|
||||
@@ -266,31 +244,27 @@ 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() }
|
||||
}
|
||||
@@ -338,14 +312,14 @@ public class SurfaceView: UIView {
|
||||
case .left, .right:
|
||||
grabberHandleWidthConstraint.constant = grabberHandleSize.height
|
||||
grabberHandleHeightConstraint.constant = grabberHandleSize.width
|
||||
}
|
||||
}
|
||||
|
||||
super.updateConstraints()
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
log.debug("surface view frame = \(frame)")
|
||||
os_log(msg, log: devLog, type: .debug, "surface view frame = \(frame)")
|
||||
|
||||
containerView.backgroundColor = appearance.backgroundColor
|
||||
|
||||
@@ -359,10 +333,8 @@ 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() {
|
||||
@@ -377,10 +349,8 @@ 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
|
||||
@@ -389,19 +359,11 @@ 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, *) {
|
||||
@@ -422,10 +384,8 @@ 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
|
||||
}
|
||||
@@ -457,33 +417,28 @@ public class SurfaceView: UIView {
|
||||
func set(contentView: UIView, mode: FloatingPanelController.ContentMode) {
|
||||
containerView.addSubview(contentView)
|
||||
self.contentView = contentView
|
||||
/**
|
||||
// MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.frame = bounds
|
||||
*/
|
||||
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
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
|
||||
}
|
||||
$0.identifier = "FloatingPanel-surface-content"
|
||||
return $0
|
||||
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
|
||||
})
|
||||
self.contentViewTopConstraint = topConstraint
|
||||
self.contentViewLeftConstraint = leftConstraint
|
||||
self.contentViewRightConstraint = rightConstraint
|
||||
|
||||
+12
-16
@@ -3,11 +3,9 @@
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -54,11 +52,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()
|
||||
|
||||
@@ -74,7 +72,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
|
||||
@@ -86,7 +84,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)
|
||||
@@ -121,7 +119,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)
|
||||
@@ -141,13 +139,11 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
guard let transitionAnimator = fpc.transitionAnimator else {
|
||||
fatalError("Something wrong happened in Core.move(from:to:animated:completion)")
|
||||
}
|
||||
return transitionAnimator
|
||||
return fpc.transitionAnimator!
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-35
@@ -1,32 +1,39 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import OSLog
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ControllerTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_warningRetainCycle() {
|
||||
let exp = expectation(description: "Warning retain cycle")
|
||||
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()
|
||||
}
|
||||
#if swift(>=5.5) // Avoid the 'No exact matches in call to initializer' build failure for OSLogStore when running this test case on iOS 13.7 using Xcode 12.5.1
|
||||
func test_warningRetainCycle() throws {
|
||||
guard #available(iOS 15.0, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 15 or later")
|
||||
}
|
||||
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
|
||||
myVC.loadViewIfNeeded()
|
||||
wait(for: [exp], timeout: 10)
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let found = try store
|
||||
.getEntries(
|
||||
at: store.position(timeIntervalSinceLatestBoot: 0),
|
||||
matching: .init(format: "subsystem == '\(Logging.subsystem)'")
|
||||
)
|
||||
.contains {
|
||||
$0.composedMessage.contains("A memory leak occurs due to a retain cycle, as")
|
||||
}
|
||||
XCTAssertTrue(found)
|
||||
}
|
||||
#endif
|
||||
|
||||
func test_addPanel() {
|
||||
let rootVC = UIViewController()
|
||||
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)
|
||||
@@ -34,8 +41,13 @@ class ControllerTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
func test_updateLayout_willTransition() {
|
||||
func test_updateLayout_willTransition() throws {
|
||||
guard #available(iOS 12, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 12 or later")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
throw XCTSkip("Unsupported iOS version: this test doesn't support iOS 17 or later")
|
||||
}
|
||||
class MyDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if newCollection.userInterfaceStyle == .dark {
|
||||
@@ -46,7 +58,8 @@ 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)
|
||||
}
|
||||
|
||||
@@ -59,7 +72,7 @@ class ControllerTests: XCTestCase {
|
||||
|
||||
fpc.hide()
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.state, .full)
|
||||
XCTAssertEqual(delegate.position, .full)
|
||||
@@ -192,18 +205,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)
|
||||
@@ -317,7 +330,6 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .full).y)
|
||||
fpc.move(to: .half, animated: false)
|
||||
print(1 / fpc.surfaceView.fp_displayScale)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .half).y)
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
|
||||
@@ -339,21 +351,15 @@ 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),
|
||||
]
|
||||
}
|
||||
|
||||
+313
-346
@@ -1,34 +1,38 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class CoreTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_scrolllock() {
|
||||
func test_scrollLock() {
|
||||
let fpc = FloatingPanelController()
|
||||
|
||||
let contentVC1 = UITableViewController(nibName: nil, bundle: nil)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
fpc.set(contentViewController: contentVC1)
|
||||
fpc.track(scrollView: contentVC1.tableView)
|
||||
fpc.showForTest()
|
||||
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
|
||||
let exp1 = expectation(description: "move to full with animation")
|
||||
fpc.move(to: .full, animated: true) {
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
exp1.fulfill()
|
||||
}
|
||||
wait(for: [exp1], timeout: 1.0)
|
||||
@@ -36,6 +40,7 @@ class CoreTests: XCTestCase {
|
||||
let exp2 = expectation(description: "move to tip with animation")
|
||||
fpc.move(to: .tip, animated: false) {
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
exp2.fulfill()
|
||||
}
|
||||
wait(for: [exp2], timeout: 1.0)
|
||||
@@ -43,18 +48,20 @@ class CoreTests: XCTestCase {
|
||||
// Reset the content vc
|
||||
let contentVC2 = UITableViewController(nibName: nil, bundle: nil)
|
||||
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC2.tableView.bounces, true)
|
||||
fpc.set(contentViewController: contentVC2)
|
||||
fpc.track(scrollView: contentVC2.tableView)
|
||||
fpc.show(animated: false, completion: nil)
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC2.tableView.bounces, false)
|
||||
}
|
||||
|
||||
func test_getBackdropAlpha_1positions() {
|
||||
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)]
|
||||
}
|
||||
|
||||
@@ -74,9 +81,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),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -89,7 +96,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)
|
||||
}
|
||||
@@ -98,7 +105,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),
|
||||
]
|
||||
@@ -128,7 +135,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),
|
||||
@@ -144,7 +151,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)
|
||||
}
|
||||
@@ -175,6 +182,7 @@ 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 +254,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 +265,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,9 +273,8 @@ 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()
|
||||
@@ -289,9 +296,8 @@ 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()
|
||||
@@ -303,25 +309,22 @@ 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),
|
||||
]
|
||||
@@ -337,52 +340,46 @@ 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),
|
||||
]
|
||||
@@ -398,42 +395,38 @@ 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
|
||||
])
|
||||
}
|
||||
|
||||
@@ -449,38 +442,36 @@ 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() {
|
||||
@@ -495,38 +486,36 @@ 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() {
|
||||
@@ -541,36 +530,34 @@ 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() {
|
||||
@@ -578,42 +565,40 @@ 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
|
||||
])
|
||||
}
|
||||
|
||||
@@ -630,36 +615,34 @@ 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() {
|
||||
@@ -669,41 +652,39 @@ 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
|
||||
])
|
||||
}
|
||||
|
||||
@@ -721,43 +702,37 @@ 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),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -765,7 +740,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),
|
||||
@@ -779,19 +754,15 @@ 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),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -799,7 +770,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),
|
||||
@@ -819,31 +790,27 @@ 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),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -867,7 +834,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,7 +1,6 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ExtensionTests: XCTestCase {
|
||||
|
||||
+246
-399
@@ -1,7 +1,6 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class LayoutTests: XCTestCase {
|
||||
@@ -21,7 +20,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
|
||||
@@ -45,7 +44,7 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
func test_layoutSegment_3position() {
|
||||
class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -57,29 +56,25 @@ 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()
|
||||
@@ -90,27 +85,23 @@ 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()
|
||||
@@ -120,17 +111,14 @@ 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() {
|
||||
@@ -138,50 +126,40 @@ 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)
|
||||
|
||||
@@ -193,10 +171,11 @@ 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)
|
||||
|
||||
@@ -206,43 +185,33 @@ 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)
|
||||
|
||||
@@ -269,27 +238,21 @@ 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)
|
||||
|
||||
@@ -316,27 +279,21 @@ 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)
|
||||
|
||||
@@ -360,27 +317,21 @@ 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)
|
||||
|
||||
@@ -415,7 +366,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)
|
||||
}
|
||||
|
||||
@@ -424,7 +375,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
|
||||
@@ -438,9 +389,10 @@ 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)
|
||||
@@ -458,93 +410,60 @@ 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
func test_layoutAnchor_bottomPosition() {
|
||||
@@ -556,94 +475,62 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,52 +555,32 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,52 +605,32 @@ 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,24 +1,23 @@
|
||||
// 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,7 +1,6 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class SurfaceViewTests: XCTestCase {
|
||||
@@ -69,14 +68,12 @@ 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:
|
||||
@@ -85,13 +82,14 @@ 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)
|
||||
|
||||
@@ -99,7 +97,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)
|
||||
}
|
||||
@@ -117,7 +115,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +204,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)
|
||||
|
||||
@@ -233,65 +231,49 @@ 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,7 +1,6 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
func waitRunLoop(secs: TimeInterval = 0) {
|
||||
@@ -41,7 +40,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),
|
||||
@@ -64,7 +63,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),
|
||||
@@ -99,3 +98,4 @@ class MockTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator
|
||||
var containerView: UIView { UIView() }
|
||||
var targetTransform: CGAffineTransform = .identity
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// 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")
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -1,52 +0,0 @@
|
||||
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
|
||||
@@ -1,72 +0,0 @@
|
||||
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