Skip to content

Commit ddc2be3

Browse files
committed
Fixed content jump due to short tab content being pinned to tab bar after switching tabs. Added stickyHeaderTabController(_, didUpdateCompoundHeaderHeight:) delegate method.
1 parent 1ac1c1d commit ddc2be3

File tree

5 files changed

+69
-47
lines changed

5 files changed

+69
-47
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- `stickyHeaderTabController(_, didUpdateCompoundHeaderHeight:)` delegate method.
10+
11+
### Fixed
12+
- Content jump due to short tab content being pinned to tab bar after switching tabs.
813

914
## [0.1.0] - 2017-10-05
1015
### Added

Example/StickyHeaderTabController/Tabs/TabData.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ struct TabData {
2323
"Georgia",
2424
"Hawaii",
2525
"Idaho",
26-
"Illinois Indiana",
26+
"Illinois",
27+
"Indiana",
2728
"Iowa",
2829
"Kansas",
2930
"Kentucky",

StickyHeaderTabController/Classes/StickyHeaderTabController.swift

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
import UIKit
99

10-
public protocol StickyHeaderTabControllerDelegate: class {
11-
func stickyHeaderTabControllerDidScrollVertically(_ controller: StickyHeaderTabController)
10+
@objc public protocol StickyHeaderTabControllerDelegate: class {
11+
@objc optional func stickyHeaderTabController(_ controller: StickyHeaderTabController,
12+
didUpdateCompoundHeaderHeight headerHeight: CGFloat)
13+
@objc optional func stickyHeaderTabControllerDidScrollVertically(_ controller: StickyHeaderTabController)
1214
}
1315

1416
open class StickyHeaderTabController: UIViewController {
@@ -71,6 +73,7 @@ open class StickyHeaderTabController: UIViewController {
7173
/// Could instead be a calculated variable, but then we lose the `didSet` functionality.
7274
fileprivate var compoundHeaderHeight: CGFloat = 0.0 {
7375
didSet {
76+
delegate?.stickyHeaderTabController?(self, didUpdateCompoundHeaderHeight: compoundHeaderHeight)
7477
updateInsetForCompoundHeaderHeight(compoundHeaderHeight)
7578
}
7679
}
@@ -86,7 +89,7 @@ open class StickyHeaderTabController: UIViewController {
8689
/// gesture began.
8790
fileprivate weak var activeVerticalTab: StickyHeaderContentTabViewController?
8891

89-
private let verticalPanRecognizer = UIPanGestureRecognizer()
92+
fileprivate let verticalPanRecognizer = UIPanGestureRecognizer()
9093

9194
// MARK: AutoLayout magic
9295

@@ -149,8 +152,6 @@ open class StickyHeaderTabController: UIViewController {
149152

150153
horizontalScrollView.frame = view.bounds
151154
horizontalScrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
152-
153-
// view.insertSubview(gradientBackground, at: 0)
154155
}
155156

156157
private func setUpVerticalPanRecognizer() {
@@ -337,14 +338,19 @@ open class StickyHeaderTabController: UIViewController {
337338
stickyGuideTopConstraint?.constant = -trueScrollOffset
338339

339340
let offsetY = trueScrollOffset - compoundHeaderHeight
340-
for tab in tabs {
341-
if tab != activeVerticalTab {
342-
// TODO: Refine this to account for differences in `contentSize`
343-
tab.contentOffset = CGPoint(x: 0, y: offsetY)
341+
let isTabBarPinned = tabBar.frame.origin.y <= stickyHeader.pinnedHeight
342+
let otherTabs = tabs.filter { tab -> Bool in tab != activeVerticalTab }
343+
for otherTab in otherTabs {
344+
var newOffsetY = offsetY
345+
if isTabBarPinned {
346+
let tabBottom = tabBar.frame.origin.y + tabBar.frame.height
347+
newOffsetY = max(otherTab.contentOffset.y, -tabBottom)
344348
}
349+
350+
otherTab.contentOffset = CGPoint(x: 0, y: newOffsetY)
345351
}
346352

347-
delegate?.stickyHeaderTabControllerDidScrollVertically(self)
353+
delegate?.stickyHeaderTabControllerDidScrollVertically?(self)
348354
}
349355

350356
fileprivate func scrollToTabIndex(_ tabIndex: Int, animated: Bool = true) {
@@ -423,6 +429,7 @@ open class StickyHeaderTabController: UIViewController {
423429
extension StickyHeaderTabController: StickyHeaderTabBarViewDelegate {
424430
public func stickyHeaderTabBarView(_ stickyHeaderTabBarView: StickyHeaderTabBarView,
425431
tabSelectedAtIndex index: Int) {
432+
verticalPanRecognizer.cancelCurrentGesture()
426433
scrollToTabIndex(index, animated: true)
427434
}
428435
}
@@ -438,15 +445,6 @@ extension StickyHeaderTabController: StickyHeaderTabBarViewDataSource {
438445
}
439446
}
440447

441-
// MARK: - StickyHeaderViewDelegate
442-
443-
extension StickyHeaderTabController: StickyHeaderViewDelegate {
444-
public func stickyHeaderView(_ stickyHeaderView: StickyHeaderView,
445-
didChangeHeight height: CGFloat) {
446-
updateCompoundHeaderHeight()
447-
}
448-
}
449-
450448
// MARK: - UIScrollViewDelegate
451449

452450
extension StickyHeaderTabController: UIScrollViewDelegate {
@@ -480,6 +478,6 @@ extension StickyHeaderTabController: UIScrollViewDelegate {
480478
extension StickyHeaderTabController: UIGestureRecognizerDelegate {
481479
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
482480
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
483-
return false //true
481+
return true
484482
}
485483
}

StickyHeaderTabController/Classes/ViewControllers/StickyHeaderContentTabViewController.swift

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,57 @@ open class StickyHeaderContentTabViewController: UIViewController {
1919
/// Set the bottom inset for the scrollView. This will be used to calculate the "true" inset.
2020
open var bottomInset: CGFloat { return 10.0 }
2121

22+
private var supplementalBottomInset: CGFloat = 0.0 {
23+
didSet {
24+
if oldValue != supplementalBottomInset {
25+
updateContentInset()
26+
}
27+
}
28+
}
29+
2230
/// Generic access to tab's content `scrollView`.
2331
open var scrollView: UIScrollView {
2432
fatalError("subclasses must override this property!")
2533
}
2634

2735
/// This is the "opaque" content inset value. Setting this will add the appropriate inset for
2836
/// the specific tab (as defined by `topInset` and `bottomInset`).
29-
public var contentInset: UIEdgeInsets {
30-
get {
31-
let trueInset = scrollView.contentInset
32-
return UIEdgeInsets(top: trueInset.top - topInset,
33-
left: trueInset.left,
34-
bottom: trueInset.bottom - bottomInset,
35-
right: trueInset.right)
36-
}
37-
set {
38-
scrollView.contentInset = UIEdgeInsets(top: newValue.top + topInset,
39-
left: newValue.left,
40-
bottom: newValue.bottom + bottomInset,
41-
right: newValue.right)
37+
public var contentInset: UIEdgeInsets = .zero {
38+
didSet {
39+
updateContentInset()
4240
}
4341
}
4442

4543
/// This is the "opaque" content offset value. Setting this will add the appropriate offset for
4644
/// the specific tab (as defined by `topInset` and `bottomInset`).
47-
public var contentOffset: CGPoint {
48-
get {
49-
let trueOffset = scrollView.contentOffset
50-
return CGPoint(x: trueOffset.x, y: trueOffset.y + topInset)
51-
}
52-
set {
53-
scrollView.contentOffset = CGPoint(x: newValue.x, y: newValue.y - topInset)
45+
public var contentOffset: CGPoint = .zero {
46+
didSet {
47+
updateContentOffset()
5448
}
5549
}
50+
51+
// MARK: - Private Methods
52+
53+
private func updateContentInset() {
54+
let newBottomInset = contentInset.bottom + bottomInset + supplementalBottomInset
55+
scrollView.contentInset = UIEdgeInsets(top: contentInset.top + topInset,
56+
left: contentInset.left,
57+
bottom: newBottomInset,
58+
right: contentInset.right)
59+
}
60+
61+
private func updateContentOffset() {
62+
let newOffsetY = contentOffset.y - topInset
63+
scrollView.contentOffset.y = newOffsetY
64+
updateSupplementalInsetForOffsetY(newOffsetY)
65+
}
66+
67+
fileprivate func updateSupplementalInsetForOffsetY(_ offsetY: CGFloat) {
68+
let contentBottomPosition = scrollView.contentSize.height - offsetY
69+
let adjustedBottomPosition = contentBottomPosition + bottomInset
70+
71+
supplementalBottomInset = max(0, view.bounds.height - adjustedBottomPosition)
72+
}
5673
}
5774

5875
// MARK: - UIScrollViewDelegate
@@ -61,6 +78,13 @@ extension StickyHeaderContentTabViewController: UIScrollViewDelegate {
6178

6279
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
6380
scrollViewDelegate?.scrollViewDidScroll?(scrollView)
81+
82+
let offsetY = scrollView.contentOffset.y
83+
let insetBottom = scrollView.contentInset.bottom
84+
let contentBottomPosition = scrollView.contentSize.height + insetBottom - offsetY
85+
if contentBottomPosition > view.bounds.height {
86+
updateSupplementalInsetForOffsetY(scrollView.contentOffset.y)
87+
}
6488
}
6589

6690
open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {

StickyHeaderTabController/Classes/Views/StickyHeaderView.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,10 @@
77

88
import UIKit
99

10-
public protocol StickyHeaderViewDelegate: class {
11-
func stickyHeaderView(_ stickyHeaderView: StickyHeaderView, didChangeHeight height: CGFloat)
12-
}
13-
1410
open class StickyHeaderView: UIView {
1511

1612
// MARK: - Public Properties
1713

18-
public weak var delegate: StickyHeaderViewDelegate?
19-
2014
/// The minimum header height (while pinned in sticky top position)
2115
open var pinnedHeight: CGFloat {
2216
return 0.0

0 commit comments

Comments
 (0)