Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 4.0 into main #713

Merged
merged 48 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
817a7f1
Add new API for PageView implementation in SwiftUI
rechsteiner Apr 2, 2023
d738017
Merge pull request #666 from rechsteiner/swiftui-api
rechsteiner Apr 2, 2023
75d31e8
Constrain menu to safe area insets by default
rechsteiner Apr 2, 2023
5a7b032
Fix PageViewController isScrollingFrom delegate when overshooting
rechsteiner Apr 2, 2023
12ceddc
Merge pull request #668 from rechsteiner/safe-area-constraints
rechsteiner Apr 2, 2023
76e4c51
Merge pull request #669 from rechsteiner/page-view-delegates
rechsteiner Apr 2, 2023
8c287d4
Fix "header above menu" example
rechsteiner Apr 2, 2023
217061b
Fix "hide menu on scroll" example
rechsteiner Apr 2, 2023
a7ebf1f
Fix "large titles" example
rechsteiner Apr 2, 2023
ea437d4
Fix status bar style in example app
rechsteiner Apr 2, 2023
6efc21b
Ignore .build folder
rechsteiner Apr 2, 2023
f64b593
Merge pull request #670 from rechsteiner/fix-examples
rechsteiner Apr 4, 2023
e9289d9
Drop iOS 13 for SwiftUI views and use UIContentConfiguration
rechsteiner Apr 6, 2023
b9d13c1
Fix PagingView layout constraints
rechsteiner Apr 6, 2023
f7b02db
Fix PagingView layout constraints
rechsteiner Apr 6, 2023
52b101c
Merge pull request #673 from rechsteiner/fix-auto-layout
rechsteiner Apr 6, 2023
1326fee
Update to recommended Xcode project settings
rechsteiner Apr 6, 2023
32ad9d8
Merge pull request #672 from rechsteiner/content-configuration
rechsteiner Apr 6, 2023
aaf6b8a
Add missing color modifiers to PageView
rechsteiner Apr 6, 2023
96c3708
Update default colors to support dark mode
rechsteiner Apr 6, 2023
2ce9e3e
Merge pull request #674 from rechsteiner/project-settings
rechsteiner Apr 6, 2023
589f5b1
Merge pull request #675 from rechsteiner/page-view-colors
rechsteiner Apr 6, 2023
712f289
Fix didSelectItem delegate being called before actual selection
rechsteiner Apr 6, 2023
1497254
Allow setting PageView closures multiple times
rechsteiner Apr 6, 2023
b5f96f3
Merge pull request #676 from rechsteiner/fix-delegates
rechsteiner Apr 6, 2023
f4a8fe8
Merge pull request #677 from rechsteiner/page-view-closures
rechsteiner Apr 6, 2023
c50d6ed
Add new PagingIndexable protocol to allow comparison between items
rechsteiner Apr 7, 2023
ebf6676
Merge pull request #678 from rechsteiner/paging-indexable
rechsteiner Apr 7, 2023
4e92743
Fix rotation and crash issues with calling layoutIfNeeded
rechsteiner Apr 7, 2023
4b70f44
Fix rotation issues with menu
rechsteiner Apr 7, 2023
f3108be
Add support for new wheel menu interaction
rechsteiner Apr 7, 2023
c6e5364
Merge pull request #680 from rechsteiner/fix-layout-issues
rechsteiner Apr 7, 2023
aaa3e95
Merge pull request #679 from rechsteiner/wheel
rechsteiner Apr 7, 2023
ad35dd0
import UIKit
mironal Apr 17, 2023
98178de
Merge pull request #681 from mironal/fix-cannot-find-UIViewController
rechsteiner Apr 17, 2023
a050a11
Add custom style for indicator view when using SwiftUI
rechsteiner May 23, 2023
9efa304
Merge pull request #684 from rechsteiner/indicator-style
rechsteiner May 23, 2023
6879480
Add Example target as shared scheme
rechsteiner Sep 2, 2023
bae5e44
Merge pull request #690 from rechsteiner/example-scheme
rechsteiner Sep 2, 2023
57a2144
Merge remote-tracking branch 'origin/main' into 4.0
rechsteiner Feb 17, 2024
ecbfc1a
Set fixed size modifier on custom header views
rechsteiner Feb 17, 2024
279c935
Fix issues with SwiftUI views being reset when updating
rechsteiner Feb 17, 2024
2a278d3
#697: Propagate .options modifier on SwiftUI View update
nfgrilo Feb 17, 2024
ac733ce
Merge pull request #703 from nfgrilo/697-propagate_.options_on_View_u…
rechsteiner Feb 17, 2024
6c96c13
Fix concurrency warning in with XCTestCase and @MainActor
rechsteiner May 24, 2024
9990798
Update Xcode last upgrade version
rechsteiner May 24, 2024
a08f60c
Merge remote-tracking branch 'origin/main' into 4.0
rechsteiner May 24, 2024
e6f9786
Merge remote-tracking branch 'origin/main' into 4.0
rechsteiner May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading