Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f2be39bf4 | |||
| 92c10830ff | |||
| dcb89f58c3 | |||
| d39c4b54d1 | |||
| 504182ceae | |||
| bc1cfe444b | |||
| 0e0f773df7 | |||
| be2be99537 | |||
| afcf1ced36 | |||
| 5b33d3d5ff | |||
| dbef6a691a |
+79
-23
@@ -11,67 +11,80 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- swift: "5.9"
|
||||
xcode: "15.2"
|
||||
runs-on: macos-13
|
||||
- swift: "5.8"
|
||||
xcode: "14.3"
|
||||
runsOn: macos-13
|
||||
xcode: "14.3.1"
|
||||
runs-on: macos-13
|
||||
- swift: "5.7"
|
||||
xcode: "14.1"
|
||||
runsOn: macos-12
|
||||
runs-on: macos-12
|
||||
- swift: "5.6"
|
||||
xcode: "13.4.1"
|
||||
runsOn: macos-12
|
||||
runs-on: macos-12
|
||||
- swift: "5.5"
|
||||
xcode: "13.2.1"
|
||||
runsOn: macos-11
|
||||
runs-on: macos-12
|
||||
- swift: "5.4"
|
||||
xcode: "12.5.1"
|
||||
runsOn: macos-11
|
||||
runs-on: macos-11
|
||||
- swift: "5.3"
|
||||
xcode: "12.4"
|
||||
runsOn: macos-11
|
||||
runs-on: macos-11
|
||||
- swift: "5.2"
|
||||
xcode: "11.7"
|
||||
runsOn: macos-11
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Building in Swift ${{ matrix.swift }}
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: "17.2"
|
||||
xcode: "15.2"
|
||||
sim: "iPhone 15 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runs-on: macos-13
|
||||
- os: "16.4"
|
||||
xcode: "14.3.1"
|
||||
sim: "iPhone 14 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runsOn: macos-13
|
||||
runs-on: macos-13
|
||||
- os: "15.5"
|
||||
xcode: "13.4.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runsOn: macos-12
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Testing in iOS ${{ matrix.os }}
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' -parallel-testing-enabled '${{ matrix.parallel }}'
|
||||
run: |
|
||||
xcodebuild clean test \
|
||||
-workspace FloatingPanel.xcworkspace \
|
||||
-scheme FloatingPanel \
|
||||
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
|
||||
-parallel-testing-enabled '${{ matrix.parallel }}'
|
||||
timeout-minutes: 20
|
||||
|
||||
example:
|
||||
runs-on: macos-12
|
||||
runs-on: macos-13
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -83,26 +96,67 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Building ${{ matrix.example }}
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
|
||||
run: |
|
||||
xcodebuild clean build \
|
||||
-workspace FloatingPanel.xcworkspace \
|
||||
-scheme ${{ matrix.example }} \
|
||||
-sdk iphonesimulator
|
||||
|
||||
swiftpm:
|
||||
runs-on: macos-12
|
||||
runs-on: macos-13
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [iphoneos, iphonesimulator]
|
||||
arch: [x86_64, arm64]
|
||||
exclude:
|
||||
- platform: iphoneos
|
||||
arch: x86_64
|
||||
include:
|
||||
# 17.2
|
||||
- platform: iphoneos
|
||||
sys: "ios17.2"
|
||||
- platform: iphonesimulator
|
||||
sys: "ios17.2-simulator"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Swift Package Manager build"
|
||||
run: |
|
||||
xcrun swift build \
|
||||
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
|
||||
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
|
||||
|
||||
swiftpm_old:
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# 16.4
|
||||
- target: "x86_64-apple-ios16.4-simulator"
|
||||
xcode: "14.3.1"
|
||||
runs-on: macos-13
|
||||
- target: "arm64-apple-ios16.4-simulator"
|
||||
xcode: "14.3.1"
|
||||
runs-on: macos-13
|
||||
# 15.7
|
||||
- target: "x86_64-apple-ios15.7-simulator"
|
||||
xcode: "14.1"
|
||||
runs-on: macos-12
|
||||
- target: "arm64-apple-ios15.7-simulator"
|
||||
# 16.1
|
||||
- target: "x86_64-apple-ios16.1-simulator"
|
||||
- target: "arm64-apple-ios16.1-simulator"
|
||||
xcode: "14.1"
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Swift Package Manager build"
|
||||
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
|
||||
run: |
|
||||
swift build \
|
||||
-Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" \
|
||||
-Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
|
||||
|
||||
carthage:
|
||||
runs-on: macos-11
|
||||
@@ -112,7 +166,9 @@ jobs:
|
||||
run: carthage build --use-xcframeworks --no-skip-current
|
||||
|
||||
cocoapods:
|
||||
runs-on: macos-12
|
||||
runs-on: macos-13
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "CocoaPods: pod lib lint"
|
||||
|
||||
@@ -24,6 +24,7 @@ enum UseCase: Int, CaseIterable {
|
||||
case showAdaptivePanel
|
||||
case showAdaptivePanelWithCustomGuide
|
||||
case showCustomStatePanel
|
||||
case showCustomBackdrop
|
||||
}
|
||||
|
||||
extension UseCase {
|
||||
@@ -50,6 +51,7 @@ extension UseCase {
|
||||
case .showAdaptivePanel: return "Show Adaptive Panel"
|
||||
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
|
||||
case .showCustomStatePanel: return "Show Panel with Custom state"
|
||||
case .showCustomBackdrop: return "Show Panel with Custom Backdrop"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,6 +85,7 @@ extension UseCase {
|
||||
case .showAdaptivePanel: return .storyboard(String(describing: ImageViewController.self))
|
||||
case .showAdaptivePanelWithCustomGuide: return .storyboard(String(describing: AdaptiveLayoutTestViewController.self))
|
||||
case .showCustomStatePanel: return .viewController(DebugTableViewController())
|
||||
case .showCustomBackdrop: return .viewController(UIViewController())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,6 +273,52 @@ extension UseCaseController {
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showCustomBackdrop:
|
||||
class BlurBackdropView: BackdropView {
|
||||
var effectView: UIVisualEffectView!
|
||||
override var alpha: CGFloat {
|
||||
set {
|
||||
effectView.alpha = newValue
|
||||
}
|
||||
get {
|
||||
effectView.alpha
|
||||
}
|
||||
}
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
let effect = UIBlurEffect(style: .prominent)
|
||||
let effectView = UIVisualEffectView(effect: effect)
|
||||
addSubview(effectView)
|
||||
effectView.frame = bounds
|
||||
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
self.effectView = effectView
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
class CustomBottomLayout: FloatingPanelBottomLayout {
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(fractionalInset: 0.1, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
}
|
||||
override func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return state == .full ? 0.8 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = self
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.backdropView = BlurBackdropView()
|
||||
fpc.layout = CustomBottomLayout()
|
||||
addMain(panel: fpc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ class MainViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
}
|
||||
|
||||
private func hideStockTickerBanner() {
|
||||
// Dimiss top bar with dissolve animation
|
||||
// Dismiss top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 0.0
|
||||
self.labelStackView.alpha = 1.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.8.0"
|
||||
s.version = "2.8.2"
|
||||
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.
|
||||
|
||||
@@ -468,6 +468,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -499,6 +500,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -521,6 +523,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -541,6 +544,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -633,6 +637,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -657,6 +662,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -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.8.0/documentation/floatingpanel/) for more details.
|
||||
Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.2/documentation/floatingpanel) for more details.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -4,7 +4,7 @@ import UIKit
|
||||
|
||||
/// A view that presents a backdrop interface behind a panel.
|
||||
@objc(FloatingPanelBackdropView)
|
||||
public class BackdropView: UIView {
|
||||
open class BackdropView: UIView {
|
||||
|
||||
/// The gesture recognizer for tap gestures to dismiss a panel.
|
||||
///
|
||||
@@ -12,14 +12,14 @@ public class BackdropView: UIView {
|
||||
/// To dismiss a panel by tap gestures on the backdrop, `dismissalTapGestureRecognizer.isEnabled` is set to true.
|
||||
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer
|
||||
|
||||
init() {
|
||||
public init() {
|
||||
dismissalTapGestureRecognizer = UITapGestureRecognizer()
|
||||
dismissalTapGestureRecognizer.isEnabled = false
|
||||
super.init(frame: .zero)
|
||||
addGestureRecognizer(dismissalTapGestureRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,14 +158,15 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
|
||||
@objc
|
||||
public var surfaceView: SurfaceView! {
|
||||
public var surfaceView: SurfaceView {
|
||||
return floatingPanel.surfaceView
|
||||
}
|
||||
|
||||
/// Returns the backdrop view managed by the controller object.
|
||||
@objc
|
||||
public var backdropView: BackdropView! {
|
||||
return floatingPanel.backdropView
|
||||
public var backdropView: BackdropView {
|
||||
set { floatingPanel.backdropView = newValue }
|
||||
get { return floatingPanel.backdropView }
|
||||
}
|
||||
|
||||
/// Returns the scroll view that the controller tracks.
|
||||
@@ -732,6 +733,7 @@ private var originalDismissImp: IMP?
|
||||
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Void = {
|
||||
guard originalDismissImp == nil else { return }
|
||||
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:))) {
|
||||
|
||||
+37
-21
@@ -10,7 +10,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
private weak var ownerVC: FloatingPanelController?
|
||||
|
||||
let surfaceView: SurfaceView
|
||||
let backdropView: BackdropView
|
||||
var backdropView: BackdropView {
|
||||
didSet {
|
||||
backdropView.dismissalTapGestureRecognizer
|
||||
.addTarget(self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
}
|
||||
}
|
||||
|
||||
let layoutAdapter: LayoutAdapter
|
||||
let behaviorAdapter: BehaviorAdapter
|
||||
|
||||
@@ -24,6 +30,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
scrollBounce = cur.bounces
|
||||
scrollIndictorVisible = cur.showsVerticalScrollIndicator
|
||||
}
|
||||
scrollLocked = false
|
||||
} else {
|
||||
if let pre = oldValue {
|
||||
pre.isDirectionalLockEnabled = false
|
||||
@@ -68,6 +75,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
private var scrollBounce = false
|
||||
private var scrollIndictorVisible = false
|
||||
private var scrollBounceThreshold: CGFloat = -30.0
|
||||
private var scrollLocked = false
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
@@ -209,6 +217,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
contentOffset = scrollView?.contentOffset
|
||||
}
|
||||
|
||||
if layoutAdapter.validStates.contains(state) == false {
|
||||
state = layoutAdapter.initialState
|
||||
}
|
||||
layoutAdapter.updateStaticConstraint()
|
||||
layoutAdapter.activateLayout(for: state, forceLayout: forceLayout)
|
||||
|
||||
@@ -725,14 +736,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
guard shouldAttract(to: target) else {
|
||||
self.state = target
|
||||
self.updateLayout(to: target)
|
||||
self.unlockScrollView()
|
||||
// The `floatingPanelDidEndDragging(_:willAttract:)` must be called after the state property changes.
|
||||
// This allows library users to get the correct state in the delegate method.
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelDidEndDragging?(vc, willAttract: false)
|
||||
}
|
||||
self.endWithoutAttraction(target)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -875,6 +879,17 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return true
|
||||
}
|
||||
|
||||
func endWithoutAttraction(_ target: FloatingPanelState) {
|
||||
self.state = target
|
||||
self.updateLayout(to: target)
|
||||
self.unlockScrollView()
|
||||
// The `floatingPanelDidEndDragging(_:willAttract:)` must be called after the state property changes.
|
||||
// This allows library users to get the correct state in the delegate method.
|
||||
if let vc = ownerVC {
|
||||
vc.delegate?.floatingPanelDidEndDragging?(vc, willAttract: false)
|
||||
}
|
||||
}
|
||||
|
||||
private func startAttraction(to state: FloatingPanelState, with velocity: CGPoint, completion: @escaping (() -> Void)) {
|
||||
os_log(msg, log: devLog, type: .debug, "startAnimation to \(state) -- velocity = \(value(of: velocity))")
|
||||
guard let vc = ownerVC else { return }
|
||||
@@ -1049,29 +1064,25 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
private func lockScrollView(strict: Bool = false) {
|
||||
guard let scrollView = scrollView else { return }
|
||||
|
||||
if scrollLocked {
|
||||
os_log(msg, log: devLog, type: .debug, "Already scroll locked")
|
||||
return
|
||||
}
|
||||
scrollBounce = scrollView.bounces
|
||||
if !strict, shouldLooselyLockScrollView {
|
||||
if scrollView.isLooselyLocked {
|
||||
os_log(msg, log: devLog, type: .debug, "Already scroll locked loosely.")
|
||||
return
|
||||
}
|
||||
// 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 {
|
||||
if scrollView.isLocked {
|
||||
os_log(msg, log: devLog, type: .debug, "Already scroll locked.")
|
||||
return
|
||||
}
|
||||
|
||||
scrollBounce = scrollView.bounces
|
||||
scrollView.bounces = false
|
||||
}
|
||||
os_log(msg, log: devLog, type: .debug, "lock scroll view")
|
||||
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
|
||||
scrollLocked = true
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
switch layoutAdapter.position {
|
||||
case .top, .bottom:
|
||||
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
|
||||
@@ -1083,9 +1094,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func unlockScrollView() {
|
||||
guard let scrollView = scrollView, scrollView.isLocked else { return }
|
||||
guard let scrollView = scrollView else { return }
|
||||
if !scrollLocked {
|
||||
os_log(msg, log: devLog, type: .debug, "Already scroll unlocked.")
|
||||
return
|
||||
}
|
||||
os_log(msg, log: devLog, type: .debug, "unlock scroll view")
|
||||
|
||||
scrollLocked = false
|
||||
scrollView.bounces = scrollBounce
|
||||
scrollView.isDirectionalLockEnabled = false
|
||||
switch layoutAdapter.position {
|
||||
|
||||
@@ -109,12 +109,6 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
#endif
|
||||
|
||||
extension UIScrollView {
|
||||
var isLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
|
||||
}
|
||||
var isLooselyLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
|
||||
}
|
||||
var fp_contentOffsetMax: CGPoint {
|
||||
return CGPoint(x: max((contentSize.width + adjustedContentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + adjustedContentInset.bottom) - bounds.height, 0.0))
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.8.0</string>
|
||||
<string>2.8.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -782,12 +782,6 @@ class LayoutAdapter {
|
||||
NSLayoutConstraint.activate(constraint: self.fitToBoundsConstraint)
|
||||
}
|
||||
|
||||
var state = state
|
||||
|
||||
if validStates.contains(state) == false {
|
||||
state = layout.initialState
|
||||
}
|
||||
|
||||
// Recalculate the intrinsic size of a content view. This is because
|
||||
// UIView.systemLayoutSizeFitting() returns a different size between an
|
||||
// on-screen and off-screen view which includes
|
||||
|
||||
@@ -334,6 +334,36 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
func test_switching_layout() {
|
||||
final class FirstLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
final class SecondLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FirstLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .tip, animated: false)
|
||||
|
||||
// Switch to another layout
|
||||
fpc.layout = SecondLayout()
|
||||
fpc.invalidateLayout()
|
||||
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
}
|
||||
}
|
||||
|
||||
private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
|
||||
|
||||
@@ -913,6 +913,27 @@ class CoreTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func test_handleGesture_endWithoutAttraction() throws {
|
||||
class Delegate: FloatingPanelControllerDelegate {
|
||||
var willAttract: Bool?
|
||||
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool) {
|
||||
willAttract = attract
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
let scrollView = UIScrollView()
|
||||
let delegate = Delegate()
|
||||
fpc.showForTest()
|
||||
fpc.delegate = delegate
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
|
||||
fpc.floatingPanel.endWithoutAttraction(.full)
|
||||
|
||||
XCTAssertEqual(fpc.state, .full)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, fpc.surfaceLocation.y)
|
||||
XCTAssertEqual(delegate.willAttract, false)
|
||||
}
|
||||
}
|
||||
|
||||
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
|
||||
|
||||
Reference in New Issue
Block a user