Skip to content

Commit

Permalink
Merge pull request #713 from rechsteiner/4.0
Browse files Browse the repository at this point in the history
Merge 4.0 into main
  • Loading branch information
rechsteiner committed May 25, 2024
2 parents 6b1e939 + e6f9786 commit 38dbade
Show file tree
Hide file tree
Showing 56 changed files with 2,277 additions and 565 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Xcode
#
build/
.build/
*.pbxuser
!default.pbxuser
*.mode1v3
Expand Down
41 changes: 17 additions & 24 deletions Example/Examples/Header/HeaderViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import UIKit
class HeaderPagingView: PagingView {
static let HeaderHeight: CGFloat = 200

var headerHeightConstraint: NSLayoutConstraint?
var headerHeightConstraint: NSLayoutConstraint!

private lazy var headerView: UIImageView = {
private(set) lazy var headerView: UIImageView = {
let view = UIImageView(image: UIImage(named: "Header"))
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
Expand All @@ -29,7 +29,12 @@ class HeaderPagingView: PagingView {
headerHeightConstraint = headerView.heightAnchor.constraint(
equalToConstant: HeaderPagingView.HeaderHeight
)
headerHeightConstraint?.isActive = true
headerHeightConstraint.isActive = true
headerHeightConstraint.priority = .defaultLow

let bottomConstraint = headerView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor)
bottomConstraint.isActive = true
bottomConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
Expand Down Expand Up @@ -73,9 +78,8 @@ class HeaderViewController: UIViewController {

private let pagingViewController = HeaderPagingViewController()

private var headerConstraint: NSLayoutConstraint {
let pagingView = pagingViewController.view as! HeaderPagingView
return pagingView.headerHeightConstraint!
private var pagingView: HeaderPagingView {
return pagingViewController.view as! HeaderPagingView
}

override func viewDidLoad() {
Expand Down Expand Up @@ -128,7 +132,8 @@ extension HeaderViewController: PagingViewControllerDataSource {
let height = pagingViewController.options.menuHeight + HeaderPagingView.HeaderHeight
let insets = UIEdgeInsets(top: height, left: 0, bottom: 0, right: 0)
viewController.tableView.contentInset = insets
viewController.tableView.scrollIndicatorInsets = insets
viewController.tableView.scrollIndicatorInsets = UIEdgeInsets(top: height, left: 0, bottom: 0, right: 0)
viewController.tableView.contentOffset.y = -insets.top

return viewController
}
Expand All @@ -155,46 +160,34 @@ extension HeaderViewController: PagingViewControllerDelegate {
}
}

func pagingViewController(_: PagingViewController, willScrollToItem _: PagingItem, startingViewController _: UIViewController, destinationViewController: UIViewController) {
func pagingViewController(_: PagingViewController, isScrollingFromItem currentPagingItem: PagingItem, toItem upcomingPagingItem: PagingItem?, startingViewController: UIViewController, destinationViewController: UIViewController?, progress: CGFloat) {
guard let destinationViewController = destinationViewController as? TableViewController else { return }

// Update the content offset based on the height of the header
// view. This ensures that the content offset is correct if you
// swipe to a new page while the header view is hidden.
if let scrollView = destinationViewController.tableView {
let offset = headerConstraint.constant + pagingViewController.options.menuHeight
let offset = pagingView.headerView.bounds.height + pagingViewController.options.menuHeight
scrollView.contentOffset = CGPoint(x: 0, y: -offset)
updateScrollIndicatorInsets(in: scrollView)
}
}
}

extension HeaderViewController: UITableViewDelegate {
func updateScrollIndicatorInsets(in scrollView: UIScrollView) {
let offset = min(0, scrollView.contentOffset.y) * -1
let insetTop = max(pagingViewController.options.menuHeight, offset)
let insets = UIEdgeInsets(top: insetTop, left: 0, bottom: 0, right: 0)
scrollView.scrollIndicatorInsets = insets
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y < 0 else {
// Reset the header constraint in case we scrolled so fast that
// the height was not set to zero before the content offset
// became negative.
if headerConstraint.constant > 0 {
headerConstraint.constant = 0
if pagingView.headerHeightConstraint.constant > 0 {
pagingView.headerHeightConstraint.constant = 0
}
return
}

// Update the scroll indicator insets so they move alongside the
// header view when scrolling.
updateScrollIndicatorInsets(in: scrollView)

// Update the height of the header view based on the content
// offset of the currently selected view controller.
let height = max(0, abs(scrollView.contentOffset.y) - pagingViewController.options.menuHeight)
headerConstraint.constant = height
pagingView.headerHeightConstraint.constant = height
}
}
23 changes: 5 additions & 18 deletions Example/Examples/Icons/IconsViewController.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
import Parchment
import UIKit

struct IconItem: PagingItem, Hashable {
struct IconItem: PagingItem, PagingIndexable, Hashable {
let identifier: Int
let icon: String
let index: Int
let image: UIImage?

init(icon: String, index: Int) {
self.identifier = icon.hashValue
self.icon = icon
self.index = index
image = UIImage(named: icon)
}

/// By default, isBefore is implemented when the PagingItem conforms
/// to Comparable, but in this case we want a custom implementation
/// where we also compare IconItem with PagingIndexItem. This
/// ensures that we animate the page transition in the correct
/// direction when selecting items.
func isBefore(item: PagingItem) -> Bool {
if let item = item as? PagingIndexItem {
return index < item.index
} else if let item = item as? Self {
return index < item.index
} else {
return false
}
self.image = UIImage(named: icon)
}
}

Expand Down Expand Up @@ -64,7 +51,7 @@ class IconsViewController: UIViewController {
pagingViewController.select(pagingItem: IconItem(icon: icons[0], index: 0))

// Add the paging view controller as a child view controller
// and contrain it to all edges.
// and constrain it to all edges.
addChild(pagingViewController)
view.addSubview(pagingViewController.view)
view.constrainToEdges(pagingViewController.view)
Expand Down
4 changes: 2 additions & 2 deletions Example/Examples/Images/UnsplashViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ class ImagePagingView: PagingView {
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),

pageView.leadingAnchor.constraint(equalTo: leadingAnchor),
pageView.trailingAnchor.constraint(equalTo: trailingAnchor),
pageView.bottomAnchor.constraint(equalTo: bottomAnchor),
pageView.topAnchor.constraint(equalTo: topAnchor),
pageView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
])
}
}
Expand Down
22 changes: 2 additions & 20 deletions Example/Examples/LargeTitles/LargeTitlesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,8 @@ class LargeTitlesViewController: UIViewController {
// Tell the navigation bar that we want to have large titles
navigationController.navigationBar.prefersLargeTitles = true

// Customize the menu to match the navigation bar color
let blue = UIColor(red: 3 / 255, green: 125 / 255, blue: 233 / 255, alpha: 1)

if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = blue
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]

UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
} else {
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().barTintColor = blue
UINavigationBar.appearance().isTranslucent = false
}

view.backgroundColor = .white
pagingViewController.menuBackgroundColor = blue
pagingViewController.menuBackgroundColor = .systemBlue
pagingViewController.menuItemSize = .fixed(width: 150, height: 30)
pagingViewController.textColor = UIColor.white.withAlphaComponent(0.7)
pagingViewController.selectedTextColor = UIColor.white
Expand Down Expand Up @@ -148,6 +129,7 @@ extension LargeTitlesViewController: PagingViewControllerDataSource {
let insets = UIEdgeInsets(top: pagingViewController.options.menuItemSize.height, left: 0, bottom: 0, right: 0)
viewController.tableView.scrollIndicatorInsets = insets
viewController.tableView.contentInset = insets
viewController.tableView.contentOffset.y = -insets.top
return viewController
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ class NavigationBarViewController: UIViewController {
pagingViewController.selectedTextColor = .white

// Make sure you add the PagingViewController as a child view
// controller and contrain it to the edges of the view.
// controller and constrain it to the edges of the view.
addChild(pagingViewController)
view.addSubview(pagingViewController.view)
view.constrainToEdges(pagingViewController.view)
pagingViewController.didMove(toParent: self)

// Set the menu view as the title view on the navigation bar. This
// will remove the menu view from the view hierachy and put it
// will remove the menu view from the view hierarchy and put it
// into the navigation bar.
navigationItem.titleView = pagingViewController.collectionView
}
Expand Down
25 changes: 20 additions & 5 deletions Example/Examples/Scroll/ScrollViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,21 @@ class ScrollViewController: UIViewController {
super.viewDidLoad()

// Add the paging view controller as a child view controller and
// contrain it to all edges.
// constrain it to all edges.
addChild(pagingViewController)
view.addSubview(pagingViewController.view)
view.constrainToEdges(pagingViewController.view)
pagingViewController.didMove(toParent: self)

// Prevent the menu from showing when scrolled out of view.
pagingViewController.view.clipsToBounds = true

// Set our data source and delegate.
pagingViewController.dataSource = self
pagingViewController.delegate = self
}

/// Calculate the menu offset based on the content offset of the
/// scroll view.
/// Calculate the menu offset based on the content offset of the scroll view.
private func menuOffset(for scrollView: UIScrollView) -> CGFloat {
return min(pagingViewController.options.menuHeight, max(0, scrollView.contentOffset.y))
}
Expand All @@ -80,6 +82,9 @@ extension ScrollViewController: PagingViewControllerDataSource {
// Set delegate so that we can listen to scroll events.
viewController.tableView.delegate = self

// Ensure that the scroll view is scroll to the top.
viewController.tableView.contentOffset.y = -insets.top

return viewController
}

Expand All @@ -94,8 +99,13 @@ extension ScrollViewController: PagingViewControllerDataSource {

extension ScrollViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Offset the menu view based on the content offset of the
// scroll view.
// Only update the menu when the currently selected view is scrolled.
guard
let selectedViewController = pagingViewController.pageViewController.selectedViewController as? TableViewController,
selectedViewController.tableView === scrollView
else { return }

// Offset the menu view based on the content offset of the scroll view.
if let menuView = pagingViewController.view as? ScrollPagingView {
menuView.menuTopConstraint?.constant = -menuOffset(for: scrollView)
}
Expand All @@ -116,6 +126,11 @@ extension ScrollViewController: PagingViewControllerDelegate {
let to = menuOffset(for: destinationViewController.tableView)
let offset = ((to - from) * abs(progress)) + from

// Reset the content offset when scrolling to a new page. You
// could also remove this, and it will hide the menu when
// swiping back to the previous page.
destinationViewController.tableView.contentOffset.y = -pagingViewController.options.menuHeight

menuView.menuTopConstraint?.constant = -offset
}
}
28 changes: 28 additions & 0 deletions Example/Examples/Wheel/WheelViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Parchment
import UIKit

final class WheelViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let viewControllers = [
ContentViewController(index: 0),
ContentViewController(index: 1),
ContentViewController(index: 2),
ContentViewController(index: 3),
ContentViewController(index: 4),
ContentViewController(index: 5),
ContentViewController(index: 6),
]

let pagingViewController = PagingViewController(viewControllers: viewControllers)
pagingViewController.menuInteraction = .wheel
pagingViewController.selectedScrollPosition = .center
pagingViewController.menuItemSize = .fixed(width: 100, height: 60)

addChild(pagingViewController)
view.addSubview(pagingViewController.view)
view.constrainToEdges(pagingViewController.view)
pagingViewController.didMove(toParent: self)
}
}
36 changes: 35 additions & 1 deletion Example/ExamplesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum Example: CaseIterable {
case selfSizing
case calendar
case sizeDelegate
case wheel
case images
case icons
case storyboard
Expand All @@ -25,6 +26,8 @@ enum Example: CaseIterable {
return "Calendar"
case .sizeDelegate:
return "Size delegate"
case .wheel:
return "Wheel"
case .images:
return "Images"
case .icons:
Expand Down Expand Up @@ -82,7 +85,7 @@ final class ExamplesViewController: UITableViewController {

switch example {
case .largeTitles:
let navigationController = UINavigationController(rootViewController: viewController)
let navigationController = NavigationController(rootViewController: viewController)
navigationController.modalPresentationStyle = .fullScreen
viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .done,
Expand All @@ -106,6 +109,8 @@ final class ExamplesViewController: UITableViewController {
return SelfSizingViewController()
case .sizeDelegate:
return SizeDelegateViewController(nibName: nil, bundle: nil)
case .wheel:
return WheelViewController()
case .images:
return UnsplashViewController(nibName: nil, bundle: nil)
case .icons:
Expand All @@ -131,3 +136,32 @@ final class ExamplesViewController: UITableViewController {
dismiss(animated: true)
}
}

final class NavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}

override func viewDidLoad() {
super.viewDidLoad()
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = .systemBlue
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
navigationBar.tintColor = .white
navigationBar.standardAppearance = appearance
navigationBar.compactAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
}

// For debugging purposes. Adds a hook to push a new view
// controller to debug navigation controller related issues.
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
let viewController = UIViewController()
viewController.title = "Page"
viewController.view.backgroundColor = .white
pushViewController(viewController, animated: true)
}
}
}
Loading

0 comments on commit 38dbade

Please sign in to comment.