Compare commits

..

1 Commits

Author SHA1 Message Date
Shin Yamamoto ccabb1914a Opt-in the dismissSwizzling call as needed 2023-12-02 09:46:51 +09:00
16 changed files with 105 additions and 202 deletions
+27 -75
View File
@@ -11,7 +11,7 @@ on:
jobs:
build:
runs-on: ${{ matrix.runs-on }}
runs-on: ${{ matrix.runsOn }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
@@ -19,72 +19,67 @@ jobs:
matrix:
include:
- swift: "5.9"
xcode: "15.2"
runs-on: macos-13
xcode: "15.0.1"
runsOn: macos-13
- swift: "5.8"
xcode: "14.3.1"
runs-on: macos-13
runsOn: macos-13
- swift: "5.7"
xcode: "14.1"
runs-on: macos-12
runsOn: macos-12
- swift: "5.6"
xcode: "13.4.1"
runs-on: macos-12
runsOn: macos-12
- swift: "5.5"
xcode: "13.2.1"
runs-on: macos-12
runsOn: macos-11
- swift: "5.4"
xcode: "12.5.1"
runs-on: macos-11
runsOn: macos-11
- swift: "5.3"
xcode: "12.4"
runs-on: macos-11
runsOn: macos-11
- swift: "5.2"
xcode: "11.7"
runs-on: macos-11
runsOn: macos-11
steps:
- uses: actions/checkout@v3
- name: Building in Swift ${{ matrix.swift }}
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
test:
runs-on: ${{ matrix.runs-on }}
runs-on: ${{ matrix.runsOn }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- os: "17.2"
xcode: "15.2"
- os: "17.0.1"
xcode: "15.0.1"
sim: "iPhone 15 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-13
runsOn: macos-13
- os: "16.4"
xcode: "14.3.1"
sim: "iPhone 14 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-13
runsOn: macos-13
- os: "15.5"
xcode: "13.4.1"
sim: "iPhone 13 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-12
runsOn: macos-12
steps:
- uses: actions/checkout@v3
- name: Testing in iOS ${{ matrix.os }}
run: |
xcodebuild clean test \
-workspace FloatingPanel.xcworkspace \
-scheme FloatingPanel \
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
-parallel-testing-enabled '${{ matrix.parallel }}'
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' -parallel-testing-enabled '${{ matrix.parallel }}'
timeout-minutes: 20
example:
runs-on: macos-13
runs-on: macos-12
env:
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
strategy:
fail-fast: false
matrix:
@@ -96,67 +91,26 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Building ${{ matrix.example }}
run: |
xcodebuild clean build \
-workspace FloatingPanel.xcworkspace \
-scheme ${{ matrix.example }} \
-sdk iphonesimulator
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
swiftpm:
runs-on: macos-13
runs-on: macos-12
env:
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
strategy:
fail-fast: false
matrix:
platform: [iphoneos, iphonesimulator]
arch: [x86_64, arm64]
exclude:
- platform: iphoneos
arch: x86_64
include:
# 17.2
- platform: iphoneos
sys: "ios17.2"
- platform: iphonesimulator
sys: "ios17.2-simulator"
steps:
- uses: actions/checkout@v3
- name: "Swift Package Manager build"
run: |
xcrun swift build \
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
swiftpm_old:
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
# 16.4
- target: "x86_64-apple-ios16.4-simulator"
xcode: "14.3.1"
runs-on: macos-13
- target: "arm64-apple-ios16.4-simulator"
xcode: "14.3.1"
runs-on: macos-13
# 15.7
- target: "x86_64-apple-ios15.7-simulator"
xcode: "14.1"
runs-on: macos-12
- target: "arm64-apple-ios15.7-simulator"
xcode: "14.1"
runs-on: macos-12
# 16.1
- target: "x86_64-apple-ios16.1-simulator"
- target: "arm64-apple-ios16.1-simulator"
steps:
- uses: actions/checkout@v3
- name: "Swift Package Manager build"
run: |
swift build \
-Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" \
-Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
carthage:
runs-on: macos-11
@@ -166,9 +120,7 @@ jobs:
run: carthage build --use-xcframeworks --no-skip-current
cocoapods:
runs-on: macos-13
env:
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- name: "CocoaPods: pod lib lint"
@@ -82,7 +82,10 @@ struct FloatingPanelView<Content: View, FloatingPanelContent: View>: UIViewContr
/// Responsible to setup the view hierarchy and floating panel.
final class Coordinator {
private let parent: FloatingPanelView<Content, FloatingPanelContent>
private lazy var fpc = FloatingPanelController()
private lazy var fpc = {
FloatingPanelController.enableDismissToRemove()
return FloatingPanelController()
}()
init(parent: FloatingPanelView<Content, FloatingPanelContent>) {
self.parent = parent
+6 -1
View File
@@ -1,9 +1,14 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FloatingPanelController.enableDismissToRemove()
return true
}
}
+1 -1
View File
@@ -124,7 +124,7 @@ extension MainViewController: UISearchBarDelegate {
searchBar.showsCancelButton = true
searchVC.showHeader(animated: true)
searchVC.tableView.alpha = 1.0
detailVC.dismiss(animated: true, completion: nil)
detailFpc.removePanelFromParent(animated: true)
}
func deactivate(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
@@ -1,8 +1,13 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FloatingPanelController.enableDismissToRemove()
return true
}
}
@@ -24,7 +24,6 @@ enum UseCase: Int, CaseIterable {
case showAdaptivePanel
case showAdaptivePanelWithCustomGuide
case showCustomStatePanel
case showCustomBackdrop
}
extension UseCase {
@@ -51,7 +50,6 @@ extension UseCase {
case .showAdaptivePanel: return "Show Adaptive Panel"
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
case .showCustomStatePanel: return "Show Panel with Custom state"
case .showCustomBackdrop: return "Show Panel with Custom Backdrop"
}
}
}
@@ -85,7 +83,6 @@ extension UseCase {
case .showAdaptivePanel: return .storyboard(String(describing: ImageViewController.self))
case .showAdaptivePanelWithCustomGuide: return .storyboard(String(describing: AdaptiveLayoutTestViewController.self))
case .showCustomStatePanel: return .viewController(DebugTableViewController())
case .showCustomBackdrop: return .viewController(UIViewController())
}
}
@@ -273,52 +273,6 @@ extension UseCaseController {
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showCustomBackdrop:
class BlurBackdropView: BackdropView {
var effectView: UIVisualEffectView!
override var alpha: CGFloat {
set {
effectView.alpha = newValue
}
get {
effectView.alpha
}
}
override init() {
super.init()
let effect = UIBlurEffect(style: .prominent)
let effectView = UIVisualEffectView(effect: effect)
addSubview(effectView)
effectView.frame = bounds
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.effectView = effectView
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CustomBottomLayout: FloatingPanelBottomLayout {
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(fractionalInset: 0.1, edge: .bottom, referenceGuide: .safeArea),
]
}
override func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return state == .full ? 0.8 : 0.0
}
}
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.set(contentViewController: contentVC)
fpc.backdropView = BlurBackdropView()
fpc.layout = CustomBottomLayout()
addMain(panel: fpc)
}
}
@@ -1,9 +1,15 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import "AppDelegate.h"
@import FloatingPanel;
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions
{
[FloatingPanelController enableDismissToRemove];
return YES;
}
@end
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "2.8.2"
s.version = "2.8.1"
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 -1
View File
@@ -9,7 +9,7 @@
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app.
The user interface displays related content and utilities alongside the main content.
Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.2/documentation/floatingpanel) for more details.
Please see also [the API reference](https://floatingpanel.github.io/2.8.1/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)
+3 -3
View File
@@ -4,7 +4,7 @@ import UIKit
/// A view that presents a backdrop interface behind a panel.
@objc(FloatingPanelBackdropView)
open class BackdropView: UIView {
public class BackdropView: UIView {
/// The gesture recognizer for tap gestures to dismiss a panel.
///
@@ -12,14 +12,14 @@ open class BackdropView: UIView {
/// To dismiss a panel by tap gestures on the backdrop, `dismissalTapGestureRecognizer.isEnabled` is set to true.
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer
public init() {
init() {
dismissalTapGestureRecognizer = UITapGestureRecognizer()
dismissalTapGestureRecognizer.isEnabled = false
super.init(frame: .zero)
addGestureRecognizer(dismissalTapGestureRecognizer)
}
required public init?(coder: NSCoder) {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+24 -14
View File
@@ -158,15 +158,14 @@ open class FloatingPanelController: UIViewController {
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
@objc
public var surfaceView: SurfaceView {
public var surfaceView: SurfaceView! {
return floatingPanel.surfaceView
}
/// Returns the backdrop view managed by the controller object.
@objc
public var backdropView: BackdropView {
set { floatingPanel.backdropView = newValue }
get { return floatingPanel.backdropView }
public var backdropView: BackdropView! {
return floatingPanel.backdropView
}
/// Returns the scroll view that the controller tracks.
@@ -289,8 +288,6 @@ open class FloatingPanelController: UIViewController {
}
private func setUp() {
_ = FloatingPanelController.dismissSwizzling
modalPresentationStyle = .custom
transitioningDelegate = modalTransition
@@ -311,7 +308,7 @@ open class FloatingPanelController: UIViewController {
}
}
// MARK:- Overrides
// MARK: - Overrides
/// Creates the view that the controller manages.
open override func loadView() {
@@ -382,7 +379,8 @@ open class FloatingPanelController: UIViewController {
safeAreaInsetsObservation = nil
}
// MARK:- Child view controller to consult
// MARK: - Child view controller to consult
open override var childForStatusBarStyle: UIViewController? {
return contentViewController
}
@@ -399,19 +397,19 @@ open class FloatingPanelController: UIViewController {
return contentViewController
}
// MARK:- Privates
// MARK: - Privates
private func shouldUpdateLayout(from previous: UITraitCollection, to new: UITraitCollection) -> Bool {
return previous.horizontalSizeClass != new.horizontalSizeClass
|| previous.verticalSizeClass != new.verticalSizeClass
|| previous.preferredContentSizeCategory != new.preferredContentSizeCategory
|| previous.layoutDirection != new.layoutDirection
|| previous.verticalSizeClass != new.verticalSizeClass
|| previous.preferredContentSizeCategory != new.preferredContentSizeCategory
|| previous.layoutDirection != new.layoutDirection
}
private func update(safeAreaInsets: UIEdgeInsets) {
guard
preSafeAreaInsets != safeAreaInsets
else { return }
else { return }
os_log(msg, log: devLog, type: .debug, "Update safeAreaInsets = \(safeAreaInsets)")
@@ -541,7 +539,7 @@ open class FloatingPanelController: UIViewController {
self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0),
self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0),
self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
])
])
show(animated: animated) { [weak self] in
guard let self = self else { return }
@@ -695,6 +693,18 @@ open class FloatingPanelController: UIViewController {
get { floatingPanel.layoutAdapter.surfaceLocation }
set { floatingPanel.layoutAdapter.surfaceLocation = newValue }
}
/// Calling this will allow to invoke `removePanelFromParent(animated:completion:)` as needed by
/// calling UIViewController's `dismiss` method
///
/// Previously, until v2.8, this was the default behavior. However, from v2.9 onwards, due to
/// identified issues when used in conjunction with other libraries, it has been made an opt-in
/// feature.
@objc
public static func enableDismissToRemove() {
_ = FloatingPanelController.dismissSwizzling
}
}
extension FloatingPanelController {
+20 -34
View File
@@ -10,13 +10,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
private weak var ownerVC: FloatingPanelController?
let surfaceView: SurfaceView
var backdropView: BackdropView {
didSet {
backdropView.dismissalTapGestureRecognizer
.addTarget(self, action: #selector(handleBackdrop(tapGesture:)))
}
}
let backdropView: BackdropView
let layoutAdapter: LayoutAdapter
let behaviorAdapter: BehaviorAdapter
@@ -30,7 +24,6 @@ class Core: NSObject, UIGestureRecognizerDelegate {
scrollBounce = cur.bounces
scrollIndictorVisible = cur.showsVerticalScrollIndicator
}
scrollLocked = false
} else {
if let pre = oldValue {
pre.isDirectionalLockEnabled = false
@@ -75,7 +68,6 @@ class Core: NSObject, UIGestureRecognizerDelegate {
private var scrollBounce = false
private var scrollIndictorVisible = false
private var scrollBounceThreshold: CGFloat = -30.0
private var scrollLocked = false
// MARK: - Interface
@@ -736,7 +728,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
guard shouldAttract(to: target) else {
self.endWithoutAttraction(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)
}
return
}
@@ -879,17 +877,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 }
@@ -1064,25 +1051,29 @@ class Core: NSObject, UIGestureRecognizerDelegate {
private func lockScrollView(strict: Bool = false) {
guard let scrollView = scrollView else { return }
if scrollLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll locked")
return
}
scrollBounce = scrollView.bounces
if !strict, shouldLooselyLockScrollView {
if scrollView.isLooselyLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll locked loosely.")
return
}
// Don't change its `bounces` property. If it's changed, it will cause its scroll content offset jump at
// the most expanded anchor position while seamlessly scrolling content. This problem only occurs where its
// content mode is `.fitToBounds` and the tracking scroll content is smaller than the content view size.
// The reason why is because `bounces` prop change leads to the "content frame" change on `.fitToBounds`.
// See also https://github.com/scenee/FloatingPanel/issues/524.
} else {
if scrollView.isLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll locked.")
return
}
scrollBounce = scrollView.bounces
scrollView.bounces = false
}
os_log(msg, log: devLog, type: .debug, "lock scroll view")
scrollLocked = true
scrollView.isDirectionalLockEnabled = true
switch layoutAdapter.position {
case .top, .bottom:
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
@@ -1094,14 +1085,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
private func unlockScrollView() {
guard let scrollView = scrollView else { return }
if !scrollLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll unlocked.")
return
}
guard let scrollView = scrollView, scrollView.isLocked else { return }
os_log(msg, log: devLog, type: .debug, "unlock scroll view")
scrollLocked = false
scrollView.bounces = scrollBounce
scrollView.isDirectionalLockEnabled = false
switch layoutAdapter.position {
+6
View File
@@ -109,6 +109,12 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
#endif
extension UIScrollView {
var isLocked: Bool {
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
}
var isLooselyLocked: Bool {
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
}
var fp_contentOffsetMax: CGPoint {
return CGPoint(x: max((contentSize.width + adjustedContentInset.right) - bounds.width, 0.0),
y: max((contentSize.height + adjustedContentInset.bottom) - bounds.height, 0.0))
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.8.2</string>
<string>2.8.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
-21
View File
@@ -913,27 +913,6 @@ class CoreTests: XCTestCase {
)
}
}
func test_handleGesture_endWithoutAttraction() throws {
class Delegate: FloatingPanelControllerDelegate {
var willAttract: Bool?
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool) {
willAttract = attract
}
}
let fpc = FloatingPanelController()
let scrollView = UIScrollView()
let delegate = Delegate()
fpc.showForTest()
fpc.delegate = delegate
XCTAssertEqual(fpc.state, .half)
fpc.floatingPanel.endWithoutAttraction(.full)
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, fpc.surfaceLocation.y)
XCTAssertEqual(delegate.willAttract, false)
}
}
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {