Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c87d6c42c9 |
@@ -76,3 +76,18 @@ class ModalPanelLayout: FloatingPanelLayout {
|
||||
return 0.3
|
||||
}
|
||||
}
|
||||
|
||||
class ModalPanelLayout2: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
[
|
||||
.full: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview)
|
||||
]
|
||||
}
|
||||
func backdropAlpha(for _: FloatingPanelState) -> CGFloat {
|
||||
0.6
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ enum UseCase: Int, CaseIterable {
|
||||
case showDetail
|
||||
case showModal
|
||||
case showPanelModal
|
||||
case showPanelModal2
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showOnWindow
|
||||
@@ -39,6 +40,7 @@ extension UseCase {
|
||||
case .showDetail: return "Show Detail Panel"
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showPanelModal2: return "Show Panel Modal 2"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showOnWindow: return "Show Panel over Window"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
@@ -81,10 +83,11 @@ extension UseCase {
|
||||
case .trackingTextView: return .storyboard("ConsoleViewController") // Storyboard only
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal2: return .storyboard("ConsoleViewController")
|
||||
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))
|
||||
case .showPageView: return .viewController(DebugTableViewController())
|
||||
case .showPageContentView: return .viewController(DebugTableViewController())
|
||||
|
||||
@@ -178,6 +178,14 @@ extension UseCaseController {
|
||||
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showPanelModal2:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.delegate = self
|
||||
fpc.track(scrollView: (contentVC as? DebugTextViewController)!.textView)
|
||||
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showMultiPanelModal:
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
@@ -435,6 +443,8 @@ extension UseCaseController: FloatingPanelControllerDelegate {
|
||||
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
|
||||
case .showIntrinsicView:
|
||||
return IntrinsicPanelLayout()
|
||||
case .showPanelModal2:
|
||||
return ModalPanelLayout2()
|
||||
case .showPanelModal:
|
||||
if vc != mainPanelVC && vc != detailPanelVC {
|
||||
return ModalPanelLayout()
|
||||
|
||||
+11
-6
@@ -1197,16 +1197,21 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
return state == layoutAdapter.mostExpandedState
|
||||
}
|
||||
|
||||
/// Adjust content inset of the tracking scroll view if the controller's
|
||||
/// `contentInsetAdjustmentBehavior` is `.always` and its `contentMode` is `.static`.
|
||||
/// if its content is scrollable, the content might not be fully visible on `.half`
|
||||
/// state, for example. Therefore the content inset needs to adjust to display the
|
||||
/// full content.
|
||||
// Adjusts content inset of the tracking scroll view when the following conditions are met:
|
||||
// - The controller's `contentInsetAdjustmentBehavior` is `.always`
|
||||
// - Its `contentMode` is `.static`
|
||||
// - Its content is scrollable
|
||||
// This ensures that the content remains fully visible in intermediate states like `.half`,
|
||||
// by using `UIScrollView.safeAreaInsets` and the panel's current position.
|
||||
// This method must not be invoked in the fully expanded state, as it may lead to unexpected
|
||||
// behavior under the top safe area (i.e., the status bar).
|
||||
func adjustScrollContentInsetIfNeeded() {
|
||||
guard
|
||||
let fpc = ownerVC,
|
||||
let scrollView = scrollView,
|
||||
fpc.contentInsetAdjustmentBehavior == .always
|
||||
fpc.contentInsetAdjustmentBehavior == .always,
|
||||
fpc.state != layoutAdapter.mostExpandedState,
|
||||
isScrollable(state: fpc.state)
|
||||
else { return }
|
||||
|
||||
switch fpc.contentMode {
|
||||
|
||||
@@ -863,11 +863,18 @@ class CoreTests: XCTestCase {
|
||||
customSafeAreaInsets
|
||||
}
|
||||
}
|
||||
class PanelDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ fpc: FloatingPanelController, shouldAllowToScroll scrollView: UIScrollView, in state: FloatingPanelState) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
}
|
||||
let delegate = PanelDelegate()
|
||||
|
||||
do {
|
||||
let scrollView = CustomScrollView()
|
||||
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 34, right: 0)
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = delegate
|
||||
fpc.track(scrollView: scrollView)
|
||||
fpc.layout = FloatingPanelBottomLayout()
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
@@ -894,6 +901,7 @@ class CoreTests: XCTestCase {
|
||||
let scrollView = CustomScrollView()
|
||||
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 91, left: 0, bottom: 0, right: 0)
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.delegate = delegate
|
||||
fpc.track(scrollView: scrollView)
|
||||
fpc.layout = FloatingPanelTopPositionedLayout()
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
@@ -916,6 +924,40 @@ class CoreTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func test_adjustScrollContentInsetIfNeeded_normal() {
|
||||
class CustomScrollView: UIScrollView {
|
||||
var customSafeAreaInsets: UIEdgeInsets = .zero
|
||||
override var safeAreaInsets: UIEdgeInsets {
|
||||
customSafeAreaInsets
|
||||
}
|
||||
}
|
||||
do {
|
||||
let scrollView = CustomScrollView()
|
||||
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 42, left: 0, bottom: 34, right: 0)
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.track(scrollView: scrollView)
|
||||
fpc.layout = FloatingPanelBottomLayout()
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.contentMode = .static
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
|
||||
XCTAssertEqual(
|
||||
scrollView.contentInset,
|
||||
UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
XCTAssertEqual(
|
||||
scrollView.contentInset,
|
||||
UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func test_initial_scroll_offset_reset() {
|
||||
let fpc = FloatingPanelController()
|
||||
let scrollView = UIScrollView()
|
||||
|
||||
Reference in New Issue
Block a user