Skip to content

Commit 09f9036

Browse files
authored
Address #661 issue since v2.8.0 (#662)
See this comment for more details. #661 (comment)
1 parent dfa9a77 commit 09f9036

5 files changed

Lines changed: 82 additions & 7 deletions

File tree

Examples/Samples/Sources/PanelLayouts.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,18 @@ class ModalPanelLayout: FloatingPanelLayout {
7676
return 0.3
7777
}
7878
}
79+
80+
class ModalPanelLayout2: FloatingPanelLayout {
81+
let position: FloatingPanelPosition = .bottom
82+
let initialState: FloatingPanelState = .half
83+
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
84+
[
85+
.full: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
86+
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview)
87+
]
88+
}
89+
func backdropAlpha(for _: FloatingPanelState) -> CGFloat {
90+
0.6
91+
}
92+
}
93+

Examples/Samples/Sources/UseCases/UseCase.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ enum UseCase: Int, CaseIterable {
99
case showDetail
1010
case showModal
1111
case showPanelModal
12+
case showPanelModal2
1213
case showMultiPanelModal
1314
case showPanelInSheetModal
1415
case showOnWindow
@@ -39,6 +40,7 @@ extension UseCase {
3940
case .showDetail: return "Show Detail Panel"
4041
case .showModal: return "Show Modal"
4142
case .showPanelModal: return "Show Panel Modal"
43+
case .showPanelModal2: return "Show Panel Modal 2"
4244
case .showMultiPanelModal: return "Show Multi Panel Modal"
4345
case .showOnWindow: return "Show Panel over Window"
4446
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
@@ -81,10 +83,11 @@ extension UseCase {
8183
case .trackingTextView: return .storyboard("ConsoleViewController") // Storyboard only
8284
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
8385
case .showModal: return .storyboard(String(describing: ModalViewController.self))
86+
case .showPanelModal: return .viewController(DebugTableViewController())
87+
case .showPanelModal2: return .storyboard("ConsoleViewController")
8488
case .showMultiPanelModal: return .viewController(DebugTableViewController())
8589
case .showOnWindow: return .viewController(DebugTableViewController())
8690
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
87-
case .showPanelModal: return .viewController(DebugTableViewController())
8891
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
8992
case .showPageView: return .viewController(DebugTableViewController())
9093
case .showPageContentView: return .viewController(DebugTableViewController())

Examples/Samples/Sources/UseCases/UseCaseController.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ extension UseCaseController {
178178

179179
mainVC.present(fpc, animated: true, completion: nil)
180180

181+
case .showPanelModal2:
182+
let fpc = FloatingPanelController()
183+
fpc.set(contentViewController: contentVC)
184+
fpc.delegate = self
185+
fpc.track(scrollView: (contentVC as? DebugTextViewController)!.textView)
186+
187+
mainVC.present(fpc, animated: true, completion: nil)
188+
181189
case .showMultiPanelModal:
182190
let fpc = MultiPanelController()
183191
mainVC.present(fpc, animated: true, completion: nil)
@@ -435,6 +443,8 @@ extension UseCaseController: FloatingPanelControllerDelegate {
435443
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
436444
case .showIntrinsicView:
437445
return IntrinsicPanelLayout()
446+
case .showPanelModal2:
447+
return ModalPanelLayout2()
438448
case .showPanelModal:
439449
if vc != mainPanelVC && vc != detailPanelVC {
440450
return ModalPanelLayout()

Sources/Core.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,16 +1197,21 @@ class Core: NSObject, UIGestureRecognizerDelegate {
11971197
return state == layoutAdapter.mostExpandedState
11981198
}
11991199

1200-
/// Adjust content inset of the tracking scroll view if the controller's
1201-
/// `contentInsetAdjustmentBehavior` is `.always` and its `contentMode` is `.static`.
1202-
/// if its content is scrollable, the content might not be fully visible on `.half`
1203-
/// state, for example. Therefore the content inset needs to adjust to display the
1204-
/// full content.
1200+
// Adjusts content inset of the tracking scroll view when the following conditions are met:
1201+
// - The controller's `contentInsetAdjustmentBehavior` is `.always`
1202+
// - Its `contentMode` is `.static`
1203+
// - Its content is scrollable
1204+
// This ensures that the content remains fully visible in intermediate states like `.half`,
1205+
// by using `UIScrollView.safeAreaInsets` and the panel's current position.
1206+
// This method must not be invoked in the fully expanded state, as it may lead to unexpected
1207+
// behavior under the top safe area (i.e., the status bar).
12051208
func adjustScrollContentInsetIfNeeded() {
12061209
guard
12071210
let fpc = ownerVC,
12081211
let scrollView = scrollView,
1209-
fpc.contentInsetAdjustmentBehavior == .always
1212+
fpc.contentInsetAdjustmentBehavior == .always,
1213+
fpc.state != layoutAdapter.mostExpandedState,
1214+
isScrollable(state: fpc.state)
12101215
else { return }
12111216

12121217
switch fpc.contentMode {

Tests/CoreTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,11 +863,18 @@ class CoreTests: XCTestCase {
863863
customSafeAreaInsets
864864
}
865865
}
866+
class PanelDelegate: FloatingPanelControllerDelegate {
867+
func floatingPanel(_ fpc: FloatingPanelController, shouldAllowToScroll scrollView: UIScrollView, in state: FloatingPanelState) -> Bool {
868+
return state == .full || state == .half
869+
}
870+
}
871+
let delegate = PanelDelegate()
866872

867873
do {
868874
let scrollView = CustomScrollView()
869875
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 34, right: 0)
870876
let fpc = FloatingPanelController()
877+
fpc.delegate = delegate
871878
fpc.track(scrollView: scrollView)
872879
fpc.layout = FloatingPanelBottomLayout()
873880
fpc.contentInsetAdjustmentBehavior = .always
@@ -894,6 +901,7 @@ class CoreTests: XCTestCase {
894901
let scrollView = CustomScrollView()
895902
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 91, left: 0, bottom: 0, right: 0)
896903
let fpc = FloatingPanelController()
904+
fpc.delegate = delegate
897905
fpc.track(scrollView: scrollView)
898906
fpc.layout = FloatingPanelTopPositionedLayout()
899907
fpc.contentInsetAdjustmentBehavior = .always
@@ -916,6 +924,40 @@ class CoreTests: XCTestCase {
916924
}
917925
}
918926

927+
func test_adjustScrollContentInsetIfNeeded_normal() {
928+
class CustomScrollView: UIScrollView {
929+
var customSafeAreaInsets: UIEdgeInsets = .zero
930+
override var safeAreaInsets: UIEdgeInsets {
931+
customSafeAreaInsets
932+
}
933+
}
934+
do {
935+
let scrollView = CustomScrollView()
936+
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 42, left: 0, bottom: 34, right: 0)
937+
let fpc = FloatingPanelController()
938+
fpc.track(scrollView: scrollView)
939+
fpc.layout = FloatingPanelBottomLayout()
940+
fpc.contentInsetAdjustmentBehavior = .always
941+
fpc.contentMode = .static
942+
fpc.showForTest()
943+
944+
fpc.move(to: .half, animated: false)
945+
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
946+
947+
XCTAssertEqual(
948+
scrollView.contentInset,
949+
UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
950+
)
951+
952+
fpc.move(to: .full, animated: false)
953+
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
954+
XCTAssertEqual(
955+
scrollView.contentInset,
956+
UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
957+
)
958+
}
959+
}
960+
919961
func test_initial_scroll_offset_reset() {
920962
let fpc = FloatingPanelController()
921963
let scrollView = UIScrollView()

0 commit comments

Comments
 (0)