Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
version: 2.1

jobs:
build-swift_5_7:
macos:
xcode: 13.4.1
steps:
- checkout
- run: xcodebuild -scheme FloatingPanel -workspace FloatingPanel.xcworkspace SWIFT_VERSION=5.7 clean build

build-swiftpm_ios15_7:
macos:
xcode: 13.4.1
steps:
- checkout
- run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios15.7-simulator"
- run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "arm64-apple-ios15.7-simulator"

test-ios15_5-iPhone_13_Pro:
macos:
xcode: 13.4.1
steps:
- checkout
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=15.5,name=iPhone 13 Pro'
test-ios14_5-iPhone_12_Pro:
macos:
xcode: 13.4.1
steps:
- checkout
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'


workflows:
test:
jobs:
- test-ios14_5-iPhone_12_Pro
- build-swift_5_7:
name: build (5.7, 13.4.1)
- build-swiftpm_ios15_7:
name: swiftpm ({x86_64,arm64}-apple-ios15.5-simulator, 13.4.1)
- test-ios14_5-iPhone_12_Pro:
name: test (15.5, 13.4.1, iPhone 12 Pro)
- test-ios15_5-iPhone_13_Pro:
name: test (14.5, 13.4.1, iPhone 13 Pro)
21 changes: 0 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ jobs:
- swift: "5.8"
xcode: "14.3.1"
runs-on: macos-13
- swift: "5.7"
xcode: "14.1"
runs-on: macos-12
- swift: "5.6"
xcode: "13.4.1"
runs-on: macos-12
- swift: "5.5"
xcode: "13.2.1"
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: Building in Swift ${{ matrix.swift }}
Expand All @@ -59,11 +50,6 @@ jobs:
sim: "iPhone 14 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-13
- os: "15.5"
xcode: "13.4.1"
sim: "iPhone 13 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: Testing in iOS ${{ matrix.os }}
Expand Down Expand Up @@ -137,13 +123,6 @@ jobs:
- 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"
xcode: "14.1"
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: "Swift Package Manager build"
Expand Down
49 changes: 30 additions & 19 deletions Sources/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
var removalVector: CGVector = .zero

// Scroll handling
private var initialScrollOffset: CGPoint = .zero
private var initialScrollOffset: CGPoint?
private var scrollBounce = false
private var scrollIndictorVisible = false
private var scrollBounceThreshold: CGFloat = -30.0
Expand Down Expand Up @@ -411,7 +411,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {

if insideMostExpandedAnchor {
// Prevent scrolling if needed
if isScrollable(state: state) {
if isScrollable(state: state), let initialScrollOffset = initialScrollOffset {
if interactionInProgress {
os_log(msg, log: devLog, type: .debug, "settle offset -- \(value(of: initialScrollOffset))")
// Return content offset to initial offset to prevent scrolling
Expand All @@ -429,7 +429,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
stopScrolling(at: initialScrollOffset)
}
}
} else {
} else if let initialScrollOffset = initialScrollOffset {
// Return content offset to initial offset to prevent scrolling
stopScrolling(at: initialScrollOffset)
}
Expand Down Expand Up @@ -471,7 +471,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
if isScrollable(state: state) {
// Adjust a small gap of the scroll offset just after swiping down starts in the grabber area.
if surfaceView.grabberAreaContains(location), surfaceView.grabberAreaContains(initialLocation) {
if surfaceView.grabberAreaContains(location), surfaceView.grabberAreaContains(initialLocation),
let initialScrollOffset = initialScrollOffset {
stopScrolling(at: initialScrollOffset)
}
}
Expand Down Expand Up @@ -499,7 +500,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
}
// Adjust a small gap of the scroll offset just before swiping down starts in the grabber area,
if surfaceView.grabberAreaContains(location), surfaceView.grabberAreaContains(initialLocation) {
if surfaceView.grabberAreaContains(location), surfaceView.grabberAreaContains(initialLocation),
let initialScrollOffset = initialScrollOffset {
stopScrolling(at: initialScrollOffset)
}
}
Expand Down Expand Up @@ -847,7 +849,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
} else {
initialScrollOffset = scrollView.contentOffset
}
os_log(msg, log: devLog, type: .debug, "initial scroll offset -- \(initialScrollOffset)")
os_log(msg, log: devLog, type: .debug, "initial scroll offset -- \(optional: initialScrollOffset)")
}

initialTranslation = translation
Expand Down Expand Up @@ -894,17 +896,6 @@ 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 }
Expand Down Expand Up @@ -934,8 +925,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
self.backdropView.alpha = self.getBackdropAlpha(at: current, with: translation)

// Pin the offset of the tracking scroll view while moving by this animator
if let scrollView = self.scrollView {
self.stopScrolling(at: self.initialScrollOffset)
if let scrollView = self.scrollView, let initialScrollOffset = self.initialScrollOffset {
self.stopScrolling(at: initialScrollOffset)
os_log(msg, log: devLog, type: .debug, "move -- pinning scroll offset = \(scrollView.contentOffset)")
}

Expand All @@ -959,6 +950,12 @@ class Core: NSObject, UIGestureRecognizerDelegate {
self.isAttracting = false
self.moveAnimator = nil

// We need to reset `initialScrollOffset` because the scroll offset can become unexpected
// under the following circumstances:
// 1. The scroll offset changes while the panel does not move.
// 2. The panel is then moved using `move(to:animate:completion:)`.
self.initialScrollOffset = nil

if let vc = ownerVC {
vc.delegate?.floatingPanelDidEndAttracting?(vc)
}
Expand All @@ -981,6 +978,20 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
}

func endWithoutAttraction(_ target: FloatingPanelState) {
// See comments in `endAttraction`
self.initialScrollOffset = nil

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)
}
}

func value(of point: CGPoint) -> CGFloat {
return layoutAdapter.position.mainLocation(point)
}
Expand Down
11 changes: 11 additions & 0 deletions Sources/Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@ struct Logging {
static let category = "FloatingPanel"
private init() {}
}

extension String.StringInterpolation {
mutating func appendInterpolation<T>(optional: T?, defaultValue: String = "nil") {
switch optional {
case let value?:
appendLiteral(String(describing: value))
case nil:
appendLiteral(defaultValue)
}
}
}
28 changes: 28 additions & 0 deletions Tests/CoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,34 @@ class CoreTests: XCTestCase {
}
}

func test_initial_scroll_offset_reset() {
let fpc = FloatingPanelController()
let scrollView = UIScrollView()
fpc.layout = FloatingPanelBottomLayout()
fpc.track(scrollView: scrollView)
fpc.showForTest()

fpc.move(to: .full, animated: false)

fpc.panGestureRecognizer.state = .began
fpc.floatingPanel.handle(panGesture: fpc.panGestureRecognizer)

fpc.panGestureRecognizer.state = .cancelled
fpc.floatingPanel.handle(panGesture: fpc.panGestureRecognizer)

waitRunLoop(secs: 1.0)

let expect = CGPoint(x: 0, y: 100)

scrollView.setContentOffset(expect, animated: false)

fpc.move(to: .half, animated: true)

waitRunLoop(secs: 1.0)

XCTAssertEqual(expect, scrollView.contentOffset)
}

func test_handleGesture_endWithoutAttraction() throws {
class Delegate: FloatingPanelControllerDelegate {
var willAttract: Bool?
Expand Down