Compare commits

...

10 Commits

Author SHA1 Message Date
Shin Yamamoto 71f419a3cd Version 2.8.5 2024-08-05 22:07:48 +09:00
Ivan Levin 0e27410460 Replace fatal errors in transitionDuration delegate methods (#642)
This PR modifies the transitionDuration(using:) method in FloatingPanelController to return 0.0 instead of calling fatalError when the FloatingPanelController instance is not found. This change is crucial for several reasons:

1. Avoiding Crashes in Production: Using fatalError in production can lead to unexpected crashes, which are detrimental to user experience. It's safer to return a default value like 0.0 and handle the scenario gracefully.

2. Improved Stability: By returning 0.0, the application can continue running, allowing for better stability and user satisfaction. This approach also aligns with the principle of fail-safety, where the system continues operating under error conditions.

3. Real-World Case: In our large-scale project, we encountered a crash at this specific point due to the fatalError. Given the size and complexity of our application, it has been challenging to pinpoint the exact cause. Switching to a return value of 0.0 would significantly help us mitigate the issue and maintain app stability while we investigate further.

4. Maintainability: Returning a default value makes the codebase more maintainable and easier to debug, as it avoids abrupt termination and allows for logging or other error handling mechanisms.
2024-08-05 22:06:43 +09:00
Shin Yamamoto 29185a47bd Version 2.8.4 2024-07-06 15:59:52 +09:00
Shin Yamamoto 6821b26706 Fix an inappropriate condition to determine scrolling content or not (#633)
Removed the `pre > .zero` condition from `FloatingPanelLayoutAnchor` as it was not appropriate for zero or negative `absoluteInset` values. 

Added documentation for `shouldScrollingContentInMoving(from:to:)` to prevent similar mistakes in the future.
2024-07-04 10:08:17 +09:00
Shin Yamamoto 5bdbe0f0ea test: increase timeout durations 2024-07-03 22:55:32 +09:00
Shin Yamamoto afe2a9bced ci: remove macos-11 runner
The macOS 11 runner image was removed on 6/28/2024.
https://github.blog/changelog/2024-05-20-actions-upcoming-changes-to-github-hosted-macos-runners/

Therefore Carthage support was removed from README since the 'carthage' job can
no longer be run and then 'carthage' build can not be checked.
2024-07-03 22:13:37 +09:00
Shin Yamamoto c0d88af234 ci: update actions/checkout to v4 2024-07-02 21:09:56 +09:00
Shin Yamamoto 3b4c1bd51c test: increase timeout durations 2024-07-02 20:48:44 +09:00
Shin Yamamoto 3c9f556533 ci: remove test-ios13_7-iPhone_11_Pro job
Because all Intel-based macOS resource were deprecated on June 28, 2024.
2024-07-02 15:31:26 +09:00
Shin Yamamoto 7f1a74825d Remove an unused variable 2024-07-02 00:01:01 +09:00
9 changed files with 53 additions and 72 deletions
-7
View File
@@ -7,15 +7,8 @@ jobs:
steps:
- checkout
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'
test-ios13_7-iPhone_11_Pro:
macos:
xcode: 12.5.1
steps:
- checkout
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.7,name=iPhone 11 Pro'
workflows:
test:
jobs:
- test-ios14_5-iPhone_12_Pro
- test-ios13_7-iPhone_11_Pro
+6 -22
View File
@@ -36,17 +36,8 @@ jobs:
- swift: "5.5"
xcode: "13.2.1"
runs-on: macos-12
- swift: "5.4"
xcode: "12.5.1"
runs-on: macos-11
- swift: "5.3"
xcode: "12.4"
runs-on: macos-11
- swift: "5.2"
xcode: "11.7"
runs-on: macos-11
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Building in Swift ${{ matrix.swift }}
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
@@ -74,7 +65,7 @@ jobs:
parallel: NO # Stop random test job failures
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Testing in iOS ${{ matrix.os }}
run: |
xcodebuild clean test \
@@ -97,7 +88,7 @@ jobs:
- example: "Stocks"
- example: "Samples"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Building ${{ matrix.example }}
run: |
xcodebuild clean build \
@@ -124,7 +115,7 @@ jobs:
- platform: iphonesimulator
sys: "ios17.2-simulator"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "Swift Package Manager build"
run: |
xcrun swift build \
@@ -154,26 +145,19 @@ jobs:
xcode: "14.1"
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- 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
steps:
- uses: actions/checkout@v3
- name: "Carthage build"
run: carthage build --use-xcframeworks --no-skip-current
cocoapods:
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: "CocoaPods: pod lib lint"
run: pod lib lint --allow-warnings
- name: "CocoaPods: pod spec lint"
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "2.8.3"
s.version = "2.8.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.
+1 -11
View File
@@ -2,14 +2,13 @@
[![Platform](https://img.shields.io/cocoapods/p/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
[![Version](https://img.shields.io/cocoapods/v/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/scenee/FloatingPanel/ci.yml?branch=master)
[![Carthage compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
# FloatingPanel
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@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.3/documentation/floatingpanel) for more details.
Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.5/documentation/floatingpanel) for more details.
![Maps](https://github.com/SCENEE/FloatingPanel/blob/master/assets/maps.gif)
![Stocks](https://github.com/SCENEE/FloatingPanel/blob/master/assets/stocks.gif)
@@ -22,7 +21,6 @@ Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/Flo
- [Requirements](#requirements)
- [Installation](#installation)
- [CocoaPods](#cocoapods)
- [Carthage](#carthage)
- [Swift Package Manager](#swift-package-manager)
- [Getting Started](#getting-started)
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
@@ -104,14 +102,6 @@ it, simply add the following line to your Podfile:
pod 'FloatingPanel'
```
### Carthage
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
```ogdl
github "scenee/FloatingPanel"
```
### Swift Package Manager
Follow [this doc](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).
+24 -14
View File
@@ -656,14 +656,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
private func panningChange(with translation: CGPoint) {
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:))
os_log(msg, log: devLog, type: .debug, """
panningChange -- translation = \(value(of: translation)), diff = \(diff), pre = \(pre), next = \(next)
""")
layoutAdapter.updateInteractiveEdgeConstraint(
diff: diff,
scrollingContent: shouldScrollingContentInMoving(from: pre, to: next),
allowsRubberBanding: behaviorAdapter.allowsRubberBanding(for:)
)
let cur = value(of: layoutAdapter.surfaceLocation)
@@ -676,31 +681,36 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
}
private func shouldScrollingContentInMoving(from pre: CGFloat, to next: CGFloat) -> Bool {
/// Determines if the content should scroll while the surface is moving from `cur` to `target`.
///
/// - Note: `cur` argument starts from an anchor location of surface view in a state. For example,
/// it starts from zero if the state is full whose FloatingPanelLayoutAnchor.absoluteInset is zero
/// and there is no additional safe area insets like a navigation bar. Therefore, `cur` argument
/// can be minus if the absoluteInset is minus with such a condition.
private func shouldScrollingContentInMoving(from cur: CGFloat, to target: 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 {
if let sv = scrollView, sv.panGestureRecognizer.state == .changed {
let (contentSize, bounds, alwaysBounceHorizontal, alwaysBounceVertical)
= (sv.contentSize, sv.bounds, sv.alwaysBounceHorizontal, sv.alwaysBounceVertical)
switch layoutAdapter.position {
case .top:
if pre > .zero, pre < next,
scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
if cur < target, contentSize.height > bounds.height || alwaysBounceVertical {
return true
}
case .left:
if pre > .zero, pre < next,
scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
if cur < target, contentSize.width > bounds.width || alwaysBounceHorizontal {
return true
}
case .bottom:
if pre > .zero, pre > next,
scrollView.contentSize.height > scrollView.bounds.height || scrollView.alwaysBounceVertical {
if cur > target, contentSize.height > bounds.height || alwaysBounceVertical {
return true
}
case .right:
if pre > .zero, pre > next,
scrollView.contentSize.width > scrollView.bounds.width || scrollView.alwaysBounceHorizontal {
if cur > target, contentSize.width > bounds.width || alwaysBounceHorizontal {
return true
}
}
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.8.3</string>
<string>2.8.5</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+2 -2
View File
@@ -84,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 { return 0.0 }
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
return TimeInterval(animator.duration)
@@ -119,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 { return 0.0 }
let animator = fpc.animatorForDismissing(with: .zero)
return TimeInterval(animator.duration)
+13 -10
View File
@@ -64,6 +64,7 @@ class ControllerTests: XCTestCase {
}
func test_moveTo() {
let timeout = 3.0
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(delegate.position, .hidden)
@@ -102,7 +103,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(delegate.position, .full)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to half(animated)") { act in
@@ -113,7 +114,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .half)
XCTAssertEqual(delegate.position, .half)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to tip(animated)") { act in
@@ -124,7 +125,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .tip)
XCTAssertEqual(delegate.position, .tip)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
fpc.move(to: .hidden, animated: true)
@@ -137,6 +138,7 @@ class ControllerTests: XCTestCase {
class MyFloatingPanelTop2BottomLayout: FloatingPanelTop2BottomTestLayout {
override var initialState: FloatingPanelState { return .half }
}
let timeout = 3.0
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = MyFloatingPanelTop2BottomLayout()
@@ -175,7 +177,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(delegate.position, .full)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to half(animated)") { act in
@@ -186,7 +188,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .half)
XCTAssertEqual(delegate.position, .half)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to tip(animated)") { act in
@@ -197,7 +199,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .tip)
XCTAssertEqual(delegate.position, .tip)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
fpc.move(to: .hidden, animated: true)
@@ -223,6 +225,7 @@ class ControllerTests: XCTestCase {
}
func test_moveTo_didMoveDelegate() {
let timeout = 3.0
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(delegate.position, .hidden)
@@ -237,7 +240,7 @@ class ControllerTests: XCTestCase {
exp.fulfill()
}
fpc.move(to: .full, animated: false)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertEqual(count, 1)
}
@@ -253,7 +256,7 @@ class ControllerTests: XCTestCase {
fpc.move(to: .half, animated: true) {
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertGreaterThan(count, 1)
}
@@ -270,7 +273,7 @@ class ControllerTests: XCTestCase {
exp.fulfill()
}
}
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertEqual(count, 1)
}
@@ -288,7 +291,7 @@ class ControllerTests: XCTestCase {
exp.fulfill()
}
}
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertGreaterThan(count, 1)
}
+5 -4
View File
@@ -209,6 +209,8 @@ class CoreTests: XCTestCase {
return floor(fpc.backdropView.alpha * 1e+06) / 1e+06
}
let timeout = 3.0
let delegate = TestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = BackdropTestLayout()
@@ -228,14 +230,14 @@ class CoreTests: XCTestCase {
fpc.move(to: .full, animated: true) {
exp1.fulfill()
}
wait(for: [exp1], timeout: 1.0)
wait(for: [exp1], timeout: timeout)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
let exp2 = expectation(description: "move to half with animation")
fpc.move(to: .half, animated: true) {
exp2.fulfill()
}
wait(for: [exp2], timeout: 1.0)
wait(for: [exp2], timeout: timeout)
XCTAssertEqual(fpc.backdropView.alpha, 0.0)
// Test a content mode change of FloatingPanelController
@@ -246,7 +248,7 @@ class CoreTests: XCTestCase {
}
fpc.contentMode = .fitToBounds
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must not affect the backdrop alpha by changing the content mode
wait(for: [exp3], timeout: 1.0)
wait(for: [exp3], timeout: timeout)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
// Test a size class change of FloatingPanelController.view
@@ -922,7 +924,6 @@ class CoreTests: XCTestCase {
}
}
let fpc = FloatingPanelController()
let scrollView = UIScrollView()
let delegate = Delegate()
fpc.showForTest()
fpc.delegate = delegate