Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 639e4221e8 | |||
| 1ff3c28de8 | |||
| 89092676e3 | |||
| bacc871dd2 | |||
| 0bc9812c23 | |||
| a4d98971b3 | |||
| 1b0ec64489 | |||
| 345e894007 | |||
| 1fb1708df9 | |||
| b9edbf766e | |||
| fe30e60235 | |||
| 6e7c31110f | |||
| e39f634d34 | |||
| 5851fd77c4 |
+90
-69
@@ -10,94 +10,115 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macOS-11
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- swift: "5.6"
|
||||
xcode: "13.3.1"
|
||||
runsOn: macos-12
|
||||
- swift: "5.5"
|
||||
xcode: "13.2.1"
|
||||
runsOn: macos-11
|
||||
- swift: "5.4"
|
||||
xcode: "12.5.1"
|
||||
runsOn: macos-11
|
||||
- swift: "5.3"
|
||||
xcode: "12.4"
|
||||
runsOn: macos-10.15
|
||||
- swift: "5.2"
|
||||
xcode: "11.7"
|
||||
runsOn: macos-10.15
|
||||
- swift: "5.1"
|
||||
xcode: "11.3.1"
|
||||
runsOn: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "Swift 5.4"
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.4 clean build
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.5.1.app/Contents/Developer
|
||||
- name: "Swift 5.5"
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.5 clean build
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
|
||||
- uses: actions/checkout@v2
|
||||
- name: Building in Swift ${{ matrix.swift }}
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
|
||||
|
||||
build_compat:
|
||||
runs-on: macOS-10.15
|
||||
test:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: "15.4"
|
||||
xcode: "13.3.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-12
|
||||
- os: "15.2"
|
||||
xcode: "13.2.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "15.0"
|
||||
xcode: "13.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "14.5"
|
||||
xcode: "12.5.1"
|
||||
sim: "iPhone 12 Pro"
|
||||
runsOn: macos-11
|
||||
- os: "14.4"
|
||||
xcode: "12.4"
|
||||
sim: "iPhone 12 Pro"
|
||||
runsOn: macos-10.15
|
||||
- os: "13.7"
|
||||
xcode: "11.7"
|
||||
sim: "iPhone 11 Pro"
|
||||
runsOn: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "Swift 5.1"
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.1 clean build
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.3.1.app/Contents/Developer
|
||||
- name: "Swift 5.2"
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.2 clean build
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer
|
||||
- name: "Swift 5.3"
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.3 clean build
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
|
||||
|
||||
testing:
|
||||
runs-on: macOS-11
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "Testing in iOS 14.5"
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.5.1.app/Contents/Developer
|
||||
- name: "Testing in iOS 15.0"
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=15.0,name=iPhone 13 Pro'
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
|
||||
|
||||
testing_compat:
|
||||
runs-on: macOS-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "Testing in iOS 13.7"
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.7,name=iPhone 11 Pro'
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer
|
||||
- name: "Testing in iOS 14.4"
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.4,name=iPhone 12 Pro'
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
|
||||
- uses: actions/checkout@v2
|
||||
- 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 }}'
|
||||
|
||||
example:
|
||||
runs-on: macOS-11
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.3.1.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- example: "Maps"
|
||||
- example: "Stocks"
|
||||
- example: "Samples"
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "Build Maps"
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Maps -sdk iphonesimulator clean build
|
||||
- name: "Build Stocks"
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Stocks -sdk iphonesimulator clean build
|
||||
- name: "Build Samples"
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Samples -sdk iphonesimulator clean build
|
||||
- uses: actions/checkout@v2
|
||||
- name: Building ${{ matrix.example }}
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
|
||||
|
||||
swiftpm:
|
||||
runs-on: macOS-11
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.3.1.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: "arm64-apple-ios15.4-simulator"
|
||||
- target: "x86_64-apple-ios15.4-simulator"
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "Swift Package build"
|
||||
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios14.3-simulator"
|
||||
- uses: actions/checkout@v2
|
||||
- name: "Swift Package Manager build"
|
||||
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
|
||||
|
||||
carthage:
|
||||
runs-on: macOS-11
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: "Carthage build"
|
||||
run: carthage build --use-xcframeworks --no-skip-current
|
||||
|
||||
cocoapods:
|
||||
runs-on: macOS-11
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: "CocoaPods: pod lib lint"
|
||||
run: pod lib lint --allow-warnings
|
||||
- name: "CocoaPods: pod spec lint"
|
||||
|
||||
@@ -96,8 +96,20 @@ struct FloatingPanelView<Content: View, FloatingPanelContent: View>: UIViewContr
|
||||
ignoresKeyboard: true
|
||||
)
|
||||
hostingViewController.view.backgroundColor = nil
|
||||
fpc.set(contentViewController: hostingViewController)
|
||||
let contentViewController = UIViewController()
|
||||
contentViewController.view.addSubview(hostingViewController.view)
|
||||
fpc.set(contentViewController: contentViewController)
|
||||
fpc.addPanel(toParent: parentViewController, at: 1, animated: false)
|
||||
|
||||
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
let bottomConstraint = hostingViewController.view.bottomAnchor.constraint(equalTo: contentViewController.view.bottomAnchor)
|
||||
bottomConstraint.priority = .defaultHigh
|
||||
NSLayoutConstraint.activate([
|
||||
hostingViewController.view.topAnchor.constraint(equalTo: contentViewController.view.topAnchor),
|
||||
hostingViewController.view.leadingAnchor.constraint(equalTo: contentViewController.view.leadingAnchor),
|
||||
hostingViewController.view.trailingAnchor.constraint(equalTo: contentViewController.view.trailingAnchor),
|
||||
bottomConstraint
|
||||
])
|
||||
}
|
||||
|
||||
func updateIfNeeded() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import FloatingPanel
|
||||
import UIKit
|
||||
|
||||
final class SearchPanelPhoneDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
|
||||
|
||||
@@ -29,7 +29,6 @@ static FloatingPanelState *_lastQuart;
|
||||
|
||||
FloatingPanelController *fpc = [[FloatingPanelController alloc] init];
|
||||
[fpc setContentViewController:nil];
|
||||
[fpc trackScrollView:nil];
|
||||
[fpc setDelegate:self];
|
||||
|
||||
[fpc setLayout: [MyFloatingPanelLayout new]];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.5.1"
|
||||
s.version = "2.5.3"
|
||||
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.
|
||||
|
||||
+39
-26
@@ -17,6 +17,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
didSet {
|
||||
oldValue?.panGestureRecognizer.removeTarget(self, action: nil)
|
||||
scrollView?.panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +66,6 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
private var stopScrollDeceleration: Bool = false
|
||||
private var scrollBounce = false
|
||||
private var scrollIndictorVisible = false
|
||||
private var grabberAreaFrame: CGRect {
|
||||
return surfaceView.grabberAreaFrame
|
||||
}
|
||||
|
||||
// MARK: - Interface
|
||||
|
||||
@@ -264,7 +274,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
if grabberAreaFrame.contains(gestureRecognizer.location(in: gestureRecognizer.view)) {
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
return true
|
||||
}
|
||||
// all gestures of the tracking scroll view should be recognized in parallel
|
||||
@@ -295,7 +305,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return true
|
||||
}
|
||||
|
||||
if grabberAreaFrame.contains(gestureRecognizer.location(in: gestureRecognizer.view)) {
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -323,7 +333,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
scrollGestureRecognizers.contains(otherGestureRecognizer) {
|
||||
switch otherGestureRecognizer {
|
||||
case scrollView.panGestureRecognizer:
|
||||
if grabberAreaFrame.contains(gestureRecognizer.location(in: gestureRecognizer.view)) {
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
return false
|
||||
}
|
||||
return allowScrollPanGesture(for: scrollView)
|
||||
@@ -351,7 +361,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// Should begin the pan gesture without waiting the dismiss gesture of a sheet modal.
|
||||
return false
|
||||
}
|
||||
if grabberAreaFrame.contains(gestureRecognizer.location(in: gestureRecognizer.view)) {
|
||||
if surfaceView.grabberAreaContains(gestureRecognizer.location(in: surfaceView)) {
|
||||
return false
|
||||
}
|
||||
// Do not begin the pan gesture until these gestures fail
|
||||
@@ -396,7 +406,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
log.debug("settle offset --", value(of: initialScrollOffset))
|
||||
stopScrolling(at: initialScrollOffset)
|
||||
} else {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
if surfaceView.grabberAreaContains(location) {
|
||||
// Preserve the current content offset in moving from full.
|
||||
stopScrolling(at: initialScrollOffset)
|
||||
}
|
||||
@@ -439,7 +449,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
// Adjust a small gap of the scroll offset just after swiping down starts in the grabber area.
|
||||
if grabberAreaFrame.contains(location), grabberAreaFrame.contains(initialLocation) {
|
||||
if surfaceView.grabberAreaContains(location), surfaceView.grabberAreaContains(initialLocation) {
|
||||
stopScrolling(at: initialScrollOffset)
|
||||
}
|
||||
}
|
||||
@@ -464,7 +474,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
// Adjust a small gap of the scroll offset just before swiping down starts in the grabber area,
|
||||
if grabberAreaFrame.contains(location), grabberAreaFrame.contains(initialLocation) {
|
||||
if surfaceView.grabberAreaContains(location), surfaceView.grabberAreaContains(initialLocation) {
|
||||
stopScrolling(at: initialScrollOffset)
|
||||
}
|
||||
}
|
||||
@@ -511,7 +521,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// doesn't pass through .changed state after an interruptible animator is interrupted.
|
||||
let diff = translation - .leastNonzeroMagnitude
|
||||
layoutAdapter.updateInteractiveEdgeConstraint(diff: value(of: diff),
|
||||
overflow: true,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:))
|
||||
}
|
||||
panningEnd(with: translation, velocity: velocity)
|
||||
@@ -572,19 +582,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
// When the current point is within grabber area but the initial point is not, do scroll.
|
||||
if grabberAreaFrame.contains(point), !grabberAreaFrame.contains(initialLocation) {
|
||||
if surfaceView.grabberAreaContains(point), !surfaceView.grabberAreaContains(initialLocation) {
|
||||
return true
|
||||
}
|
||||
|
||||
// When the initial point is within grabber area and the current point is out of surface, don't scroll.
|
||||
if grabberAreaFrame.contains(initialLocation), !surfaceView.frame.contains(point) {
|
||||
if surfaceView.grabberAreaContains(initialLocation), !surfaceView.frame.contains(point) {
|
||||
return false
|
||||
}
|
||||
|
||||
let scrollViewFrame = scrollView.convert(scrollView.bounds, to: surfaceView)
|
||||
guard
|
||||
scrollViewFrame.contains(initialLocation), // When the initial point not in scrollView, don't scroll.
|
||||
!grabberAreaFrame.contains(point) // When point within grabber area, don't scroll.
|
||||
!surfaceView.grabberAreaContains(point) // When point within grabber area, don't scroll.
|
||||
else {
|
||||
return false
|
||||
}
|
||||
@@ -627,7 +637,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
guard let scrollView = scrollView else { return }
|
||||
if state == layoutAdapter.mostExpandedState {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
if surfaceView.grabberAreaContains(location) {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
}
|
||||
} else {
|
||||
@@ -640,10 +650,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let pre = value(of: layoutAdapter.surfaceLocation)
|
||||
let diff = value(of: translation - initialTranslation)
|
||||
let next = pre + diff
|
||||
let overflow = shouldOverflow(from: pre, to: next)
|
||||
|
||||
layoutAdapter.updateInteractiveEdgeConstraint(diff: diff,
|
||||
overflow: overflow,
|
||||
scrollingContent: shouldScrollingContentInMoving(from: pre, to: next),
|
||||
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:))
|
||||
|
||||
let cur = value(of: layoutAdapter.surfaceLocation)
|
||||
@@ -657,32 +666,36 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldOverflow(from pre: CGFloat, to next: CGFloat) -> Bool {
|
||||
private func shouldScrollingContentInMoving(from pre: CGFloat, to next: CGFloat) -> Bool {
|
||||
// Don't allow scrolling if the initial panning location is in the grabber area.
|
||||
if surfaceView.grabberAreaContains(initialLocation) {
|
||||
return false
|
||||
}
|
||||
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 {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
case .left:
|
||||
if pre > .zero, pre < next,
|
||||
scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
case .bottom:
|
||||
if pre > .zero, pre > next,
|
||||
scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
case .right:
|
||||
if pre > .zero, pre > next,
|
||||
scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
|
||||
@@ -697,7 +710,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if stopScrollDeceleration {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
|
||||
self.stopScrolling(at: self.initialScrollOffset)
|
||||
}
|
||||
}
|
||||
@@ -785,10 +798,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
initialSurfaceLocation = layoutAdapter.surfaceLocation
|
||||
if state == layoutAdapter.mostExpandedState, let scrollView = scrollView {
|
||||
if grabberAreaFrame.contains(location) {
|
||||
if surfaceView.grabberAreaContains(location) {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
} else {
|
||||
initialScrollOffset = contentOffsetForPinning(of: scrollView)
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
let offsetDiff = scrollView.contentOffset - contentOffsetForPinning(of: scrollView)
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.5.1</string>
|
||||
<string>2.5.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -678,7 +678,7 @@ class LayoutAdapter {
|
||||
surfaceView.containerOverflow = position.mainDimension(vc.view.bounds.size)
|
||||
}
|
||||
|
||||
func updateInteractiveEdgeConstraint(diff: CGFloat, overflow: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
|
||||
func updateInteractiveEdgeConstraint(diff: CGFloat, scrollingContent: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
|
||||
defer {
|
||||
log.debug("update surface location = \(surfaceLocation)")
|
||||
}
|
||||
@@ -701,7 +701,7 @@ class LayoutAdapter {
|
||||
const = maxConst + rubberBandEffect(for: buffer, base: base)
|
||||
}
|
||||
|
||||
if overflow == false {
|
||||
if scrollingContent {
|
||||
const = min(max(const, minConst), maxConst)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,12 @@ public class SurfaceAppearance: NSObject {
|
||||
///
|
||||
/// Defaults to `.circular`.
|
||||
@available(iOS 13.0, *)
|
||||
public lazy var cornerCurve: CALayerCornerCurve = .circular
|
||||
public var cornerCurve: CALayerCornerCurve {
|
||||
get { _cornerCurve ?? .circular }
|
||||
set { _cornerCurve = newValue }
|
||||
}
|
||||
|
||||
private var _cornerCurve: CALayerCornerCurve?
|
||||
|
||||
/// An array of shadows used to create drop shadows underneath a surface view.
|
||||
public var shadows: [Shadow] = [Shadow()]
|
||||
@@ -442,4 +447,22 @@ public class SurfaceView: UIView {
|
||||
func hasStackView() -> Bool {
|
||||
return contentView?.subviews.reduce(false) { $0 || ($1 is UIStackView) } ?? false
|
||||
}
|
||||
|
||||
func grabberAreaContains(_ location: CGPoint) -> Bool {
|
||||
// Sometimes a dragging finger's location is out of surface frame.
|
||||
let cappedLocation: CGPoint
|
||||
// Because the maximum width / height is out of bounds in CGRect.contains(_:)
|
||||
let adjustment = 1 / fp_displayScale
|
||||
switch position {
|
||||
case .top:
|
||||
cappedLocation = CGPoint(x: location.x, y: min(location.y, bounds.height - adjustment))
|
||||
case .left:
|
||||
cappedLocation = CGPoint(x: min(location.x, bounds.width - adjustment), y: location.y)
|
||||
case .bottom:
|
||||
cappedLocation = CGPoint(x: location.x, y: max(location.y, 0))
|
||||
case .right:
|
||||
cappedLocation = CGPoint(x: max(location.x, 0), y: location.y)
|
||||
}
|
||||
return grabberAreaFrame.contains(cappedLocation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,9 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
return animator
|
||||
}
|
||||
|
||||
// Ensure the current(initial) state is hidden. Because `fpc.transitionAnimator` can be nil if not.
|
||||
fpc.move(to: .hidden, animated: false)
|
||||
|
||||
fpc.suspendTransitionAnimator(true)
|
||||
fpc.show(animated: true) { [weak fpc] in
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
|
||||
+19
-19
@@ -138,31 +138,31 @@ class LayoutTests: XCTestCase {
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100.0)
|
||||
@@ -190,31 +190,31 @@ class LayoutTests: XCTestCase {
|
||||
var next: CGFloat
|
||||
pre = fpc.surfaceLocation.y
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, pre)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos + 100,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos + 100,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
@@ -245,19 +245,19 @@ class LayoutTests: XCTestCase {
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
overflow: true,
|
||||
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,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, hiddenPos + 100.0)
|
||||
@@ -288,19 +288,19 @@ class LayoutTests: XCTestCase {
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
overflow: true,
|
||||
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,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, hiddenPos + 100.0)
|
||||
@@ -328,19 +328,19 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
var next: CGFloat
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
overflow: false,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
overflow: true,
|
||||
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,
|
||||
overflow: true,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100)
|
||||
|
||||
@@ -228,4 +228,88 @@ class SurfaceViewTests: XCTestCase {
|
||||
XCTAssert(surface.containerView.layer.borderColor == UIColor.red.cgColor)
|
||||
XCTAssert(surface.containerView.layer.borderWidth == 3.0)
|
||||
}
|
||||
|
||||
func test_surfaceView_grabberArea() {
|
||||
let sv = SurfaceView()
|
||||
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))
|
||||
}
|
||||
// Bottom
|
||||
do {
|
||||
sv.position = .bottom
|
||||
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))
|
||||
}
|
||||
// Right
|
||||
do {
|
||||
sv.position = .right
|
||||
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)
|
||||
// Top
|
||||
do {
|
||||
sv.position = .top
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: 0, y: sv.bounds.height)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width / 2, y: sv.bounds.height)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width / 2, y: sv.bounds.height + 2)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: -2, y: sv.bounds.height)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: -2, y: -2)))
|
||||
}
|
||||
// Bottom
|
||||
do {
|
||||
sv.position = .bottom
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: 0, y: 0)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width / 2, y: 0)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width / 2, y: -2)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: -2, y: 0)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: -2, y: -2)))
|
||||
}
|
||||
// Left
|
||||
do {
|
||||
sv.position = .left
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width, y: 0)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width, y: sv.bounds.height / 2)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: sv.bounds.width + 2, y: sv.bounds.height / 2)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: sv.bounds.width, y: -2)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: -2, y: -2)))
|
||||
}
|
||||
// Right
|
||||
do {
|
||||
sv.position = .right
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: 0, y: 0)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: 0, y: sv.bounds.height / 2)))
|
||||
XCTAssertTrue(sv.grabberAreaContains(.init(x: -2, y: sv.bounds.height / 2)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: 0, y: -2)))
|
||||
XCTAssertFalse(sv.grabberAreaContains(.init(x: -2, y: -2)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user