Compare commits

..

26 Commits

Author SHA1 Message Date
Shin Yamamoto dc1e2668cc Replace OTHER_SWIFT_FLAGS with SWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY 2024-08-09 14:26:44 +09:00
Keita Watanabe da31639068 Fix existential-any for Swift6 (#639)
* Fixed existential any
* Added upcoming feature flag
2024-08-05 21:42:56 +09:00
Shin Yamamoto 5fa37b4371 Fix a concurrency warning in Samples app 2024-07-19 12:41:14 +09:00
Shin Yamamoto 0de380b79e Fix doc comment errors in ObjC APIs 2024-07-19 12:41:14 +09:00
Shin Yamamoto 8a194d70e8 Fix concurrency checks 2024-07-19 12:41:14 +09:00
Shin Yamamoto 874d0c68f8 Update the deployment target to iOS 13 2024-07-19 12:41:14 +09:00
Shin Yamamoto d0c1e00388 Set strict concurrency to complete 2024-07-19 12:41:14 +09:00
Shin Yamamoto 93168c217a Set 'Default internal imports' to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto 28e6b74714 Set 'Disable Outward Actor Isolation Inference' to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto f9bf9af179 Set 'Forward Trailing Closures' to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto d89223819c Set 'Implicity Opened Existentials' to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto 7b7e8a8365 Set 'Import ObjC Forward Declaration' to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto 476f45deac Set 'Deprecate Application Main' to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto d1f769dd7e Set "Concise Magic File” to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto a629cf0789 Set "Infer Sendable for Methods and Key Path Literals” to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto 66c6aab497 Set "Region Based Isolation” to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto ca22dd3283 Set "Isolated Default Values” to Yes 2024-07-11 20:39:54 +09:00
Shin Yamamoto e666333b0f Set "Isolated Global Variables” to Yes 2024-07-11 20:39:54 +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
20 changed files with 166 additions and 136 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"
@@ -3,6 +3,7 @@
import UIKit
import FloatingPanel
@MainActor
final class UseCaseController: NSObject {
unowned let mainVC: MainViewController
private(set) var useCase: UseCase
+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.4"
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.
+42 -3
View File
@@ -462,7 +462,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -475,6 +475,19 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
SWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;
SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;
SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;
SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -494,7 +507,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -505,6 +518,19 @@
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
SWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;
SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;
SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;
SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -631,7 +657,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -644,6 +670,19 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG FP_LOG";
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
SWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;
SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;
SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;
SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
+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.4/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).
+3 -3
View File
@@ -86,9 +86,9 @@ open class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
class BehaviorAdapter {
unowned let vc: FloatingPanelController
fileprivate var behavior: FloatingPanelBehavior
fileprivate var behavior: any FloatingPanelBehavior
init(vc: FloatingPanelController, behavior: FloatingPanelBehavior) {
init(vc: FloatingPanelController, behavior: any FloatingPanelBehavior) {
self.vc = vc
self.behavior = behavior
}
@@ -123,7 +123,7 @@ class BehaviorAdapter {
}
extension FloatingPanelController {
var _behavior: FloatingPanelBehavior {
var _behavior: any FloatingPanelBehavior {
get { floatingPanel.behaviorAdapter.behavior }
set { floatingPanel.behaviorAdapter.behavior = newValue}
}
+21 -18
View File
@@ -6,14 +6,15 @@ 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.
@MainActor
@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
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> any 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
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> any FloatingPanelLayout
/// Returns a UIViewPropertyAnimator object to add/present the panel to a position.
///
@@ -150,7 +151,7 @@ open class FloatingPanelController: UIViewController {
/// The delegate of a panel controller object.
@objc
public weak var delegate: FloatingPanelControllerDelegate?{
public weak var delegate: (any FloatingPanelControllerDelegate)?{
didSet{
didUpdateDelegate()
}
@@ -198,7 +199,7 @@ open class FloatingPanelController: UIViewController {
/// You need to call ``invalidateLayout()`` if you want to apply a new layout object into the panel
/// immediately.
@objc
public var layout: FloatingPanelLayout {
public var layout: any FloatingPanelLayout {
get { _layout }
set {
_layout = newValue
@@ -211,7 +212,7 @@ open class FloatingPanelController: UIViewController {
/// The behavior object that the controller manages
@objc
public var behavior: FloatingPanelBehavior {
public var behavior: any FloatingPanelBehavior {
get { _behavior }
set {
_behavior = newValue
@@ -282,7 +283,7 @@ open class FloatingPanelController: UIViewController {
/// Initialize a newly created panel controller.
@objc
public init(delegate: FloatingPanelControllerDelegate? = nil) {
public init(delegate: (any FloatingPanelControllerDelegate)? = nil) {
super.init(nibName: nil, bundle: nil)
self.delegate = delegate
setUp()
@@ -294,7 +295,7 @@ open class FloatingPanelController: UIViewController {
modalPresentationStyle = .custom
transitioningDelegate = modalTransition
let initialLayout: FloatingPanelLayout
let initialLayout: any FloatingPanelLayout
if let layout = delegate?.floatingPanel?(self, layoutFor: traitCollection) {
initialLayout = layout
} else {
@@ -344,7 +345,7 @@ open class FloatingPanelController: UIViewController {
floatingPanel.adjustScrollContentInsetIfNeeded()
}
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
open override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if self.view.bounds.size == size {
@@ -363,7 +364,7 @@ open class FloatingPanelController: UIViewController {
}
}
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
open override func willTransition(to newCollection: UITraitCollection, with coordinator: any UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
if shouldUpdateLayout(from: traitCollection, to: newCollection) == false {
@@ -483,16 +484,18 @@ open class FloatingPanelController: UIViewController {
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
// That's why it needs the observation to keep `adjustedContentInsets` correct.
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
MainActor.assumeIsolated {
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
// an unsatisfied constraints error. So this method should not be called with those bounds.
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
// an unsatisfied constraints error. So this method should not be called with those bounds.
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
self.update(safeAreaInsets: self.view.safeAreaInsets)
self.update(safeAreaInsets: self.view.safeAreaInsets)
}
}
move(to: floatingPanel.layoutAdapter.initialState,
@@ -729,7 +732,7 @@ extension FloatingPanelController {
// MARK: - Swizzling
private var originalDismissImp: IMP?
@MainActor private var originalDismissImp: IMP?
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
extension FloatingPanelController {
private static let dismissSwizzling: Void = {
+30 -22
View File
@@ -3,7 +3,6 @@
import UIKit
import os.log
///
/// The presentation model of FloatingPanel
///
class Core: NSObject, UIGestureRecognizerDelegate {
@@ -79,7 +78,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
// MARK: - Interface
init(_ vc: FloatingPanelController, layout: FloatingPanelLayout, behavior: FloatingPanelBehavior) {
init(_ vc: FloatingPanelController, layout: any FloatingPanelLayout, behavior: any FloatingPanelBehavior) {
ownerVC = vc
surfaceView = SurfaceView()
@@ -114,8 +113,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
deinit {
// Release `NumericSpringAnimator.displayLink` from the run loop.
self.moveAnimator?.stopAnimation(false)
moveAnimator?.stopAnimation(false)
}
func move(to: FloatingPanelState, animated: Bool, completion: (() -> Void)? = nil) {
@@ -656,14 +654,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 +679,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
}
}
@@ -1237,7 +1245,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
///
/// - Note: The delegate is used by FloatingPanel itself. If you set your own delegate object, an
/// exception is raised. If you want to handle the methods of UIGestureRecognizerDelegate, you can use `delegateProxy`.
public override weak var delegate: UIGestureRecognizerDelegate? {
public override weak var delegate: (any UIGestureRecognizerDelegate)? {
get {
return super.delegate
}
@@ -1258,7 +1266,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
/// The default object implementing a set methods of the delegate of the gesture recognizer.
///
/// Use this property with ``delegateProxy`` when you need to use the default gesture behaviors in a proxy implementation.
public var delegateOrigin: UIGestureRecognizerDelegate {
public var delegateOrigin: any UIGestureRecognizerDelegate {
return floatingPanel
}
@@ -1266,7 +1274,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
///
/// `UIGestureRecognizerDelegate` methods implementing by this object are called instead of the default delegate,
/// ``delegateOrigin``.
public weak var delegateProxy: UIGestureRecognizerDelegate? {
public weak var delegateProxy: (any UIGestureRecognizerDelegate)? {
didSet {
self.delegate = floatingPanel?.panGestureDelegateRouter // Update the cached IMP
}
@@ -1299,7 +1307,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
// MARK: - Animator
private class NumericSpringAnimator: NSObject {
private final class NumericSpringAnimator: NSObject, @unchecked Sendable {
struct Data {
let value: CGFloat
let velocity: CGFloat
+1
View File
@@ -36,6 +36,7 @@ extension CGPoint {
// MARK: - UIKit
@MainActor
protocol LayoutGuideProvider {
var topAnchor: NSLayoutYAxisAnchor { get }
var leftAnchor: NSLayoutXAxisAnchor { get }
+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.4</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+10 -8
View File
@@ -4,6 +4,7 @@ import UIKit
import os.log
/// An interface for generating layout information for a panel.
@MainActor
@objc public protocol FloatingPanelLayout {
/// Returns the position of a panel in a `FloatingPanelController` view .
@objc var position: FloatingPanelPosition { get }
@@ -12,7 +13,7 @@ import os.log
@objc var initialState: FloatingPanelState { get }
/// Returns the layout anchors to specify the snapping locations for each state.
@objc var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { get }
@objc var anchors: [FloatingPanelState: any FloatingPanelLayoutAnchoring] { get }
/// Returns layout constraints to determine the cross dimension of a panel.
@objc optional func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint]
@@ -31,7 +32,7 @@ open class FloatingPanelBottomLayout: NSObject, FloatingPanelLayout {
return .half
}
open var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
open var anchors: [FloatingPanelState: any FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
@@ -60,11 +61,12 @@ struct LayoutSegment {
let upper: FloatingPanelState?
}
@MainActor
class LayoutAdapter {
private unowned var vc: FloatingPanelController
private let defaultLayout = FloatingPanelBottomLayout()
fileprivate var layout: FloatingPanelLayout {
fileprivate var layout: any FloatingPanelLayout {
didSet {
surfaceView.position = position
}
@@ -287,7 +289,7 @@ class LayoutAdapter {
return offset.rounded(by: surfaceView.fp_displayScale)
}
private var hiddenAnchor: FloatingPanelLayoutAnchoring {
private var hiddenAnchor: any FloatingPanelLayoutAnchoring {
switch position {
case .top:
return FloatingPanelLayoutAnchor(absoluteInset: -100, edge: .top, referenceGuide: .superview)
@@ -300,7 +302,7 @@ class LayoutAdapter {
}
}
init(vc: FloatingPanelController, layout: FloatingPanelLayout) {
init(vc: FloatingPanelController, layout: any FloatingPanelLayout) {
self.vc = vc
self.layout = layout
}
@@ -404,7 +406,7 @@ class LayoutAdapter {
}
}
private func referenceEdge(of anchor: FloatingPanelLayoutAnchoring) -> FloatingPanelReferenceEdge {
private func referenceEdge(of anchor: any FloatingPanelLayoutAnchoring) -> FloatingPanelReferenceEdge {
switch anchor {
case is FloatingPanelIntrinsicLayoutAnchor,
is FloatingPanelAdaptiveLayoutAnchor:
@@ -546,7 +548,7 @@ class LayoutAdapter {
NSLayoutConstraint.deactivate(constraint: interactionConstraint)
interactionConstraint = nil
let layoutGuideProvider: LayoutGuideProvider
let layoutGuideProvider: any LayoutGuideProvider
switch anchor.referenceGuide {
case .safeArea:
layoutGuideProvider = vc.view.safeAreaLayoutGuide
@@ -854,7 +856,7 @@ extension LayoutAdapter {
}
extension FloatingPanelController {
var _layout: FloatingPanelLayout {
var _layout: any FloatingPanelLayout {
get {
floatingPanel.layoutAdapter.layout
}
+13 -12
View File
@@ -3,6 +3,7 @@
import UIKit
/// An interface for implementing custom layout anchor objects.
@MainActor
@objc public protocol FloatingPanelLayoutAnchoring {
var referenceGuide: FloatingPanelLayoutReferenceGuide { get }
func layoutConstraints(_ fpc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint]
@@ -17,7 +18,7 @@ import UIKit
/// positioning.
///
/// - Parameters:
/// - absoluteOffset: An absolute offset to attach the panel from the edge.
/// - absoluteInset: An absolute offset to attach the panel from the edge.
/// - edge: Specify the edge of ``FloatingPanelController``'s view. This is the staring point of the offset.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(absoluteInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
@@ -34,7 +35,7 @@ import UIKit
/// 1.0 represents a distance to the opposite edge.
///
/// - Parameters:
/// - fractionalOffset: A fractional value of the size of ``FloatingPanelController``'s view to attach the panel from the edge.
/// - fractionalInset: A fractional value of the size of ``FloatingPanelController``'s view to attach the panel from the edge.
/// - edge: Specify the edge of ``FloatingPanelController``'s view. This is the staring point of the offset.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(fractionalInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
@@ -65,7 +66,7 @@ public extension FloatingPanelLayoutAnchor {
}
}
private func layoutConstraints(_ layoutGuide: LayoutGuideProvider, for edgeAnchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
private func layoutConstraints(_ layoutGuide: any LayoutGuideProvider, for edgeAnchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
switch referenceEdge {
case .top:
if isAbsolute {
@@ -84,7 +85,7 @@ public extension FloatingPanelLayoutAnchor {
}
}
private func layoutConstraints(_ layoutGuide: LayoutGuideProvider, for edgeAnchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
private func layoutConstraints(_ layoutGuide: any LayoutGuideProvider, for edgeAnchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
switch referenceEdge {
case .left:
if isAbsolute {
@@ -115,8 +116,8 @@ public extension FloatingPanelLayoutAnchor {
/// - Parameters:
/// - absoluteOffset: An absolute offset from the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(absoluteOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
@objc public init(absoluteOffset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = absoluteOffset
self.referenceGuide = referenceGuide
self.isAbsolute = true
}
@@ -129,8 +130,8 @@ public extension FloatingPanelLayoutAnchor {
/// - Parameters:
/// - fractionalOffset: A fractional offset of the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(fractionalOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
@objc public init(fractionalOffset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = fractionalOffset
self.referenceGuide = referenceGuide
self.isAbsolute = false
}
@@ -177,12 +178,12 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
///
/// - Warning: If ``contentBoundingGuide`` is set to none, the panel may expand out of the screen size, depending on the intrinsic size of its content.
@objc public init(
absoluteOffset offset: CGFloat,
absoluteOffset: CGFloat,
contentLayout: UILayoutGuide,
referenceGuide: FloatingPanelLayoutReferenceGuide,
contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide = .none
) {
self.offset = offset
self.offset = absoluteOffset
self.contentLayoutGuide = contentLayout
self.referenceGuide = referenceGuide
self.contentBoundingGuide = contentBoundingGuide
@@ -204,12 +205,12 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
///
/// - Warning: If ``contentBoundingGuide`` is set to none, the panel may expand out of the screen size, depending on the intrinsic size of its content.
@objc public init(
fractionalOffset offset: CGFloat,
fractionalOffset: CGFloat,
contentLayout: UILayoutGuide,
referenceGuide: FloatingPanelLayoutReferenceGuide,
contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide = .none
) {
self.offset = offset
self.offset = fractionalOffset
self.contentLayoutGuide = contentLayout
self.referenceGuide = referenceGuide
self.contentBoundingGuide = contentBoundingGuide
+5 -2
View File
@@ -3,6 +3,7 @@
import UIKit
/// Constants that specify the edge of the container of a panel.
@MainActor
@objc public enum FloatingPanelReferenceEdge: Int {
case top
case left
@@ -28,13 +29,14 @@ extension FloatingPanelReferenceEdge {
}
/// A representation to specify a rectangular area to lay out a panel.
@MainActor
@objc public enum FloatingPanelLayoutReferenceGuide: Int {
case superview = 0
case safeArea = 1
}
extension FloatingPanelLayoutReferenceGuide {
func layoutGuide(vc: UIViewController) -> LayoutGuideProvider {
func layoutGuide(vc: UIViewController) -> any LayoutGuideProvider {
switch self {
case .safeArea:
return vc.view.safeAreaLayoutGuide
@@ -45,6 +47,7 @@ extension FloatingPanelLayoutReferenceGuide {
}
/// A representation to specify a bounding box which limit the content size of a panel.
@MainActor
@objc public enum FloatingPanelLayoutContentBoundingGuide: Int {
case none = 0
case superview = 1
@@ -52,7 +55,7 @@ extension FloatingPanelLayoutReferenceGuide {
}
extension FloatingPanelLayoutContentBoundingGuide {
func layoutGuide(_ fpc: FloatingPanelController) -> LayoutGuideProvider? {
func layoutGuide(_ fpc: FloatingPanelController) -> (any LayoutGuideProvider)? {
switch self {
case .superview:
return fpc.view
+2 -2
View File
@@ -3,11 +3,11 @@
import os.log
let msg = StaticString("%{public}@")
let sysLog = OSLog(subsystem: Logging.subsystem, category: Logging.category)
nonisolated(unsafe) 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
nonisolated(unsafe) let devLog = OSLog.disabled
#endif
struct Logging {
+2 -1
View File
@@ -3,6 +3,7 @@
import UIKit
/// Constants describing the position of a panel in a screen
@MainActor
@objc public enum FloatingPanelPosition: Int {
case top
case left
@@ -25,7 +26,7 @@ extension FloatingPanelPosition {
}
}
func mainDimensionAnchor(_ layoutGuide: LayoutGuideProvider) -> NSLayoutDimension {
func mainDimensionAnchor(_ layoutGuide: any LayoutGuideProvider) -> NSLayoutDimension {
switch self {
case .top, .bottom: return layoutGuide.heightAnchor
case .left, .right: return layoutGuide.widthAnchor
+1 -1
View File
@@ -4,7 +4,7 @@ import Foundation
/// An object that represents the display state of a panel in a screen.
@objc
open class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
public final class FloatingPanelState: NSObject, NSCopying, RawRepresentable, Sendable {
public typealias RawValue = String
required public init?(rawValue: RawValue) {
+8 -8
View File
@@ -5,11 +5,11 @@ import UIKit
class ModalTransition: NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
return ModalPresentTransition()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
func animationController(forDismissed dismissed: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
return ModalDismissTransition()
}
@@ -81,7 +81,7 @@ class PresentationController: UIPresentationController {
}
class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
func transitionDuration(using transitionContext: (any UIViewControllerContextTransitioning)?) -> TimeInterval {
guard
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
else { fatalError()}
@@ -90,7 +90,7 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
return TimeInterval(animator.duration)
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
func interruptibleAnimator(using transitionContext: any UIViewControllerContextTransitioning) -> any UIViewImplicitlyAnimating {
guard
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
else { fatalError() }
@@ -110,13 +110,13 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
return transitionAnimator
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func animateTransition(using transitionContext: any UIViewControllerContextTransitioning) {
self.interruptibleAnimator(using: transitionContext).startAnimation()
}
}
class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
func transitionDuration(using transitionContext: (any UIViewControllerContextTransitioning)?) -> TimeInterval {
guard
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
else { fatalError()}
@@ -125,7 +125,7 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
return TimeInterval(animator.duration)
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
func interruptibleAnimator(using transitionContext: any UIViewControllerContextTransitioning) -> any UIViewImplicitlyAnimating {
guard
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
else { fatalError() }
@@ -142,7 +142,7 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
return fpc.transitionAnimator!
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func animateTransition(using transitionContext: any UIViewControllerContextTransitioning) {
self.interruptibleAnimator(using: transitionContext).startAnimation()
}
}
+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