Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a917d6a626 | |||
| 7511ce577d | |||
| e7d0a72440 | |||
| 44923ef66e | |||
| 2760bc7298 | |||
| d428e96b03 | |||
| 67495961e5 |
@@ -0,0 +1,5 @@
|
||||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [FloatingPanel]
|
||||
platform: ios
|
||||
@@ -10,6 +10,7 @@ enum UseCase: Int, CaseIterable {
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showOnWindow
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
@@ -34,6 +35,7 @@ extension UseCase {
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showOnWindow: return "Show Panel over Window"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
@@ -65,6 +67,7 @@ extension UseCase {
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showMultiPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showOnWindow: return .viewController(DebugTableViewController())
|
||||
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
|
||||
|
||||
@@ -11,6 +11,7 @@ final class UseCaseController: NSObject {
|
||||
private var detailPanelVC: FloatingPanelController!
|
||||
private var settingsPanelVC: FloatingPanelController!
|
||||
private lazy var pagePanelController = PagePanelController()
|
||||
private lazy var overWindowPanelVC = FloatingPanelController()
|
||||
|
||||
init(mainVC: MainViewController) {
|
||||
self.mainVC = mainVC
|
||||
@@ -157,6 +158,19 @@ extension UseCaseController {
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showOnWindow:
|
||||
let fpc = overWindowPanelVC
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
|
||||
guard let window = UIApplication.shared.windows.first else { fatalError("Any window not found") }
|
||||
|
||||
window.addSubview(fpc.view)
|
||||
fpc.view.frame = window.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
fpc.show(animated: true)
|
||||
case .showPanelInSheetModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = UIViewController()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.6.2"
|
||||
s.version = "2.6.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.
|
||||
|
||||
@@ -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](https://floatingpanel.github.io/2.6.2/documentation/floatingpanel/) for more details, powered by [DocC](https://developer.apple.com/documentation/docc).
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.6.3/documentation/floatingpanel/) for more details.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -414,8 +414,14 @@ open class FloatingPanelController: UIViewController {
|
||||
guard let self = self else { return }
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
} else {
|
||||
} else if parent != nil {
|
||||
removePanelFromParent(animated: true)
|
||||
} else {
|
||||
hide(animated: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.view.removeFromSuperview()
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +701,7 @@ extension FloatingPanelController {
|
||||
// MARK: - Swizzling
|
||||
|
||||
private var originalDismissImp: IMP?
|
||||
private typealias __dismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Void = {
|
||||
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
|
||||
@@ -709,7 +715,7 @@ extension FloatingPanelController {
|
||||
extension UIViewController {
|
||||
@objc
|
||||
fileprivate func __swizzled_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: __dismissFunction.self)
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: DismissFunction.self)
|
||||
let sel = #selector(UIViewController.dismiss(animated:completion:))
|
||||
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
|
||||
+39
-6
@@ -20,11 +20,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -62,6 +64,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
// Scroll handling
|
||||
private var initialScrollOffset: CGPoint = .zero
|
||||
private var stopScrollDeceleration: Bool = false
|
||||
private var scrollBounce = false
|
||||
private var scrollIndictorVisible = false
|
||||
|
||||
// MARK: - Interface
|
||||
@@ -456,7 +459,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
if velocity < 0, !allowScrollPanGesture(for: scrollView) {
|
||||
lockScrollView()
|
||||
lockScrollView(strict: true)
|
||||
}
|
||||
if velocity > 0, allowScrollPanGesture(for: scrollView) {
|
||||
unlockScrollView()
|
||||
@@ -464,7 +467,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
case .bottom, .right:
|
||||
// Hide a scroll indicator just before starting an interaction by swiping a panel down.
|
||||
if velocity > 0, !allowScrollPanGesture(for: scrollView) {
|
||||
lockScrollView()
|
||||
lockScrollView(strict: true)
|
||||
}
|
||||
// Show a scroll indicator when an animation is interrupted at the top and content is scrolled up
|
||||
if velocity < 0, allowScrollPanGesture(for: scrollView) {
|
||||
@@ -927,6 +930,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
""")
|
||||
if finished, state == layoutAdapter.mostExpandedState, abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
|
||||
unlockScrollView()
|
||||
} else if finished, shouldLooselyLockScrollView {
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,6 +939,10 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return layoutAdapter.position.mainLocation(point)
|
||||
}
|
||||
|
||||
func value(of size: CGSize) -> CGFloat {
|
||||
return layoutAdapter.position.mainDimension(size)
|
||||
}
|
||||
|
||||
func setValue(_ newValue: CGPoint, to point: inout CGPoint) {
|
||||
switch layoutAdapter.position {
|
||||
case .top, .bottom:
|
||||
@@ -1021,7 +1030,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
scrollView.transform = CGAffineTransform(translationX: 0, y: contentOffset)
|
||||
}
|
||||
|
||||
private func lockScrollView() {
|
||||
private func lockScrollView(strict: Bool = false) {
|
||||
guard let scrollView = scrollView else { return }
|
||||
|
||||
if scrollView.isLocked {
|
||||
@@ -1032,8 +1041,16 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
|
||||
|
||||
// Must not modify the UIScrollView.bounces property here. If you reset it to unlock the tracking scroll view,
|
||||
// UIScrollView may unexpectedly alter the scroll offset when dealing with small scrollable content.
|
||||
if !strict, shouldLooselyLockScrollView {
|
||||
// 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 {
|
||||
scrollBounce = scrollView.bounces
|
||||
scrollView.bounces = false
|
||||
}
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
}
|
||||
@@ -1042,15 +1059,31 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
guard let scrollView = scrollView, scrollView.isLocked else { return }
|
||||
log.debug("unlock scroll view")
|
||||
|
||||
scrollView.bounces = scrollBounce
|
||||
scrollView.isDirectionalLockEnabled = false
|
||||
scrollView.showsVerticalScrollIndicator = scrollIndictorVisible
|
||||
}
|
||||
|
||||
private var shouldLooselyLockScrollView: Bool {
|
||||
var isSmallScrollContentAndFitToBoundsMode: Bool {
|
||||
if ownerVC?.contentMode == .fitToBounds, let scrollView = scrollView,
|
||||
value(of: scrollView.contentSize) < value(of: scrollView.bounds.size) - min(layoutAdapter.offsetFromMostExpandedAnchor, 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return isSmallScrollContentAndFitToBoundsMode
|
||||
}
|
||||
|
||||
private func stopScrolling(at contentOffset: CGPoint) {
|
||||
// Must use setContentOffset(_:animated) to force-stop deceleration
|
||||
guard let scrollView = scrollView else { return }
|
||||
var offset = scrollView.contentOffset
|
||||
setValue(contentOffset, to: &offset)
|
||||
if contentOffset.y >= 0 {
|
||||
setValue(contentOffset, to: &offset)
|
||||
} else {
|
||||
offset = CGPoint(x: 0, y: 0)
|
||||
}
|
||||
scrollView.setContentOffset(offset, animated: false)
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
|
||||
extension UIScrollView {
|
||||
var isLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
|
||||
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
|
||||
}
|
||||
var fp_contentInset: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.6.2</string>
|
||||
<string>2.6.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -318,7 +318,6 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .full).y)
|
||||
fpc.move(to: .half, animated: false)
|
||||
print(1 / fpc.surfaceView.fp_displayScale)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .half).y)
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
|
||||
|
||||
@@ -12,22 +12,27 @@ class CoreTests: XCTestCase {
|
||||
|
||||
let contentVC1 = UITableViewController(nibName: nil, bundle: nil)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
fpc.set(contentViewController: contentVC1)
|
||||
fpc.track(scrollView: contentVC1.tableView)
|
||||
fpc.showForTest()
|
||||
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
|
||||
let exp1 = expectation(description: "move to full with animation")
|
||||
fpc.move(to: .full, animated: true) {
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
exp1.fulfill()
|
||||
}
|
||||
wait(for: [exp1], timeout: 1.0)
|
||||
@@ -35,6 +40,7 @@ class CoreTests: XCTestCase {
|
||||
let exp2 = expectation(description: "move to tip with animation")
|
||||
fpc.move(to: .tip, animated: false) {
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
exp2.fulfill()
|
||||
}
|
||||
wait(for: [exp2], timeout: 1.0)
|
||||
@@ -42,11 +48,13 @@ class CoreTests: XCTestCase {
|
||||
// Reset the content vc
|
||||
let contentVC2 = UITableViewController(nibName: nil, bundle: nil)
|
||||
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC2.tableView.bounces, true)
|
||||
fpc.set(contentViewController: contentVC2)
|
||||
fpc.track(scrollView: contentVC2.tableView)
|
||||
fpc.show(animated: false, completion: nil)
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC2.tableView.bounces, false)
|
||||
}
|
||||
|
||||
func test_getBackdropAlpha_1positions() {
|
||||
|
||||
@@ -464,7 +464,6 @@ class LayoutTests: XCTestCase {
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
}
|
||||
}
|
||||
func test_layoutAnchor_bottomPosition() {
|
||||
|
||||
Reference in New Issue
Block a user