Replace the pagecontrol with a collection view (#690)

Scrolling wasn't smooth with pageviews, as they do some black magic with scrollviews (like resetting contentOffset). If you depend on contentOffset, this breaks things and makes them hard.
This commit is contained in:
Nathan Mattes 2023-01-07 16:02:46 +01:00
parent 8ff47a72d0
commit 44d85e0263
4 changed files with 68 additions and 132 deletions

View File

@ -117,8 +117,7 @@
D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; }; D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; };
D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; };
D8A6FE5B293244B500666A47 /* WelcomeContentPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */; }; D8A6FE5B293244B500666A47 /* WelcomeContentPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */; };
D8A6FE5D293244C300666A47 /* WelcomeContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5C293244C300666A47 /* WelcomeContentViewController.swift */; }; D8A6FE5F29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5E29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift */; };
D8A6FE5F29324BBC00666A47 /* WelcomeContentPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5E29324BBC00666A47 /* WelcomeContentPageView.swift */; };
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; };
DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; };
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
@ -666,8 +665,7 @@
D8916DBF29211BE500124085 /* ContentSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizedTableView.swift; sourceTree = "<group>"; }; D8916DBF29211BE500124085 /* ContentSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizedTableView.swift; sourceTree = "<group>"; };
D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = "<group>"; }; D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = "<group>"; };
D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentPage.swift; sourceTree = "<group>"; }; D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentPage.swift; sourceTree = "<group>"; };
D8A6FE5C293244C300666A47 /* WelcomeContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentViewController.swift; sourceTree = "<group>"; }; D8A6FE5E29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentCollectionViewCell.swift; sourceTree = "<group>"; };
D8A6FE5E29324BBC00666A47 /* WelcomeContentPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentPageView.swift; sourceTree = "<group>"; };
D8A6FE6129325F5900666A47 /* Intents.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Intents.stringsdict; sourceTree = "<group>"; }; D8A6FE6129325F5900666A47 /* Intents.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Intents.stringsdict; sourceTree = "<group>"; };
D8A6FE6229325F5900666A47 /* app.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = app.json; sourceTree = "<group>"; }; D8A6FE6229325F5900666A47 /* app.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = app.json; sourceTree = "<group>"; };
D8A6FE6329325F5900666A47 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; D8A6FE6329325F5900666A47 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -1607,8 +1605,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */, D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */,
D8A6FE5C293244C300666A47 /* WelcomeContentViewController.swift */, D8A6FE5E29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift */,
D8A6FE5E29324BBC00666A47 /* WelcomeContentPageView.swift */,
); );
path = Pages; path = Pages;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3336,8 +3333,7 @@
DB603113279EBEBA00A935FE /* DataSourceFacade+Block.swift in Sources */, DB603113279EBEBA00A935FE /* DataSourceFacade+Block.swift in Sources */,
DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */, DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */,
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */, DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */, D8A6FE5F29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift in Sources */,
D8A6FE5F29324BBC00666A47 /* WelcomeContentPageView.swift in Sources */,
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */, 5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */, DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */, DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
@ -3549,7 +3545,6 @@
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */, DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */,
0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */, 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */,
DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */, DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */,
D8A6FE5D293244C300666A47 /* WelcomeContentViewController.swift in Sources */,
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */,
DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */, DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */,

View File

@ -7,7 +7,9 @@
import UIKit import UIKit
class WelcomeContentPageView: UIView { class WelcomeContentCollectionViewCell: UICollectionViewCell {
static let identifier = "WelcomeContentCollectionViewCell"
//TODO: Put in ScrollView? //TODO: Put in ScrollView?
private let contentStackView: UIStackView private let contentStackView: UIStackView
@ -15,17 +17,14 @@ class WelcomeContentPageView: UIView {
private let label: UILabel private let label: UILabel
private let blurryBackgroundView: UIVisualEffectView private let blurryBackgroundView: UIVisualEffectView
init(page: WelcomeContentPage) { override init(frame: CGRect) {
titleView = UILabel() titleView = UILabel()
titleView.font = WelcomeViewController.largeTitleFont titleView.font = WelcomeViewController.largeTitleFont
titleView.textColor = WelcomeViewController.largeTitleTextColor.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)) titleView.textColor = WelcomeViewController.largeTitleTextColor.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light))
titleView.attributedText = page.title
titleView.adjustsFontForContentSizeCategory = true titleView.adjustsFontForContentSizeCategory = true
titleView.numberOfLines = 0 titleView.numberOfLines = 0
label = UILabel() label = UILabel()
label.text = page.content
label.font = WelcomeViewController.subTitleFont label.font = WelcomeViewController.subTitleFont
label.textColor = WelcomeViewController.largeTitleTextColor.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)) label.textColor = WelcomeViewController.largeTitleTextColor.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light))
label.adjustsFontForContentSizeCategory = true label.adjustsFontForContentSizeCategory = true
@ -43,7 +42,7 @@ class WelcomeContentPageView: UIView {
blurryBackgroundView.contentView.addSubview(contentStackView) blurryBackgroundView.contentView.addSubview(contentStackView)
super.init(frame: .zero) super.init(frame: frame)
addSubview(blurryBackgroundView) addSubview(blurryBackgroundView)
@ -55,16 +54,21 @@ class WelcomeContentPageView: UIView {
private func setupConstraints() { private func setupConstraints() {
let constraints = [ let constraints = [
blurryBackgroundView.topAnchor.constraint(equalTo: topAnchor), blurryBackgroundView.topAnchor.constraint(equalTo: topAnchor),
blurryBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), blurryBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingAnchor.constraint(equalTo: blurryBackgroundView.trailingAnchor, constant: 16), trailingAnchor.constraint(equalTo: blurryBackgroundView.trailingAnchor),
bottomAnchor.constraint(greaterThanOrEqualTo: blurryBackgroundView.bottomAnchor), bottomAnchor.constraint(greaterThanOrEqualTo: blurryBackgroundView.bottomAnchor),
contentStackView.topAnchor.constraint(equalTo: blurryBackgroundView.contentView.topAnchor, constant: 8), contentStackView.topAnchor.constraint(equalTo: blurryBackgroundView.contentView.topAnchor, constant: 8),
contentStackView.leadingAnchor.constraint(equalTo: blurryBackgroundView.contentView.leadingAnchor, constant: 8), contentStackView.leadingAnchor.constraint(equalTo: blurryBackgroundView.contentView.leadingAnchor, constant: 8),
blurryBackgroundView.contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 8), blurryBackgroundView.contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor),
blurryBackgroundView.contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 8), blurryBackgroundView.contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor),
] ]
NSLayoutConstraint.activate(constraints) NSLayoutConstraint.activate(constraints)
} }
func update(with page: WelcomeContentPage) {
titleView.attributedText = page.title
label.text = page.content
}
} }

View File

@ -1,28 +0,0 @@
//
// WelcomeContentViewController.swift
// Mastodon
//
// Created by Nathan Mattes on 26.11.22.
//
import UIKit
class WelcomeContentViewController: UIViewController {
let page: WelcomeContentPage
var contentView: WelcomeContentPageView {
view as! WelcomeContentPageView
}
init(page: WelcomeContentPage) {
self.page = page
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
let pageView = WelcomeContentPageView(page: page)
self.view = pageView
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

View File

@ -29,6 +29,7 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
private(set) lazy var dismissBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(WelcomeViewController.dismissBarButtonItemDidPressed(_:))) private(set) lazy var dismissBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(WelcomeViewController.dismissBarButtonItemDidPressed(_:)))
let buttonContainer = UIStackView() let buttonContainer = UIStackView()
let educationPages: [WelcomeContentPage] = [.whatIsMastodon, .mastodonIsLikeThat, .howDoIPickAServer]
private(set) lazy var signUpButton: PrimaryActionButton = { private(set) lazy var signUpButton: PrimaryActionButton = {
let button = PrimaryActionButton() let button = PrimaryActionButton()
@ -58,13 +59,24 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
return button return button
}() }()
private(set) lazy var pageViewController: UIPageViewController = { private(set) lazy var pageCollectionView: UICollectionView = {
let pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) let flowLayout = UICollectionViewFlowLayout()
pageController.setViewControllers([WelcomeContentViewController(page: .whatIsMastodon)], direction: .forward, animated: false) flowLayout.scrollDirection = .horizontal
return pageController flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
//FIXME: cell-size.
flowLayout.itemSize = CGSize(width: self.view.frame.width, height: 300)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.isPagingEnabled = true
collectionView.backgroundColor = nil
collectionView.showsHorizontalScrollIndicator = false
collectionView.bounces = false
collectionView.register(WelcomeContentCollectionViewCell.self, forCellWithReuseIdentifier: WelcomeContentCollectionViewCell.identifier)
return collectionView
}() }()
var currentPage: WelcomeContentPage = .whatIsMastodon
var currentPageOffset = 0
} }
extension WelcomeViewController { extension WelcomeViewController {
@ -121,24 +133,18 @@ extension WelcomeViewController {
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside) signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside) signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
pageViewController.delegate = self pageCollectionView.delegate = self
pageViewController.dataSource = self pageCollectionView.dataSource = self
addChild(pageViewController) view.addSubview(pageCollectionView)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pageViewController.view)
pageViewController.didMove(toParent: self)
let scrollviews = pageViewController.view.subviews.filter { type(of: $0).isSubclass(of: UIScrollView.self) }.compactMap { $0 as? UIScrollView } let scrollView = pageCollectionView as UIScrollView
for scrollView in scrollviews {
scrollView.delegate = self scrollView.delegate = self
}
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
pageViewController.view.topAnchor.constraint(equalTo: view.topAnchor, constant: computedTopAnchorInset), pageCollectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: computedTopAnchorInset),
pageViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), pageCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: pageViewController.view.trailingAnchor), view.trailingAnchor.constraint(equalTo: pageCollectionView.trailingAnchor),
buttonContainer.topAnchor.constraint(equalTo: pageViewController.view.bottomAnchor, constant: 16), buttonContainer.topAnchor.constraint(equalTo: pageCollectionView.bottomAnchor, constant: 16),
]) ])
viewModel.$needsShowDismissEntry viewModel.$needsShowDismissEntry
@ -287,70 +293,29 @@ extension WelcomeViewController: UIAdaptivePresentationControllerDelegate {
} }
} }
//MARK: - UIPageViewControllerDelegate //MARK: - UIScrollViewDelegate
extension WelcomeViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let currentViewController = pageViewController.viewControllers?.first as? WelcomeContentViewController else { return }
currentPage = currentViewController.page
if let pageIndex = WelcomeContentPage.allCases.firstIndex(of: currentPage) {
let offset = Int(pageIndex) * Int(pageViewController.view.frame.width)
currentPageOffset = offset
welcomeIllustrationView.update(contentOffset: CGFloat(offset))
}
}
}
//MARK: - UIPageViewDataSource
extension WelcomeViewController: UIPageViewControllerDataSource {
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
WelcomeContentPage.allCases.firstIndex(of: currentPage) ?? 0
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return WelcomeContentPage.allCases.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewController = viewController as? WelcomeContentViewController else { return nil }
let currentPage = viewController.page
switch currentPage {
case .whatIsMastodon:
return nil
case .mastodonIsLikeThat:
return WelcomeContentViewController(page: .whatIsMastodon)
case .howDoIPickAServer:
return WelcomeContentViewController(page: .mastodonIsLikeThat)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewController = viewController as? WelcomeContentViewController else { return nil }
let currentPage = viewController.page
switch currentPage {
case .whatIsMastodon:
return WelcomeContentViewController(page: .mastodonIsLikeThat)
case .mastodonIsLikeThat:
return WelcomeContentViewController(page: .howDoIPickAServer)
case .howDoIPickAServer:
return nil
}
}
}
extension WelcomeViewController: UIScrollViewDelegate { extension WelcomeViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
let weirdScrollViewJumpingCorrectionFactor = pageViewController.view.frame.width let contentOffset = scrollView.contentOffset.x
let contentOffset = CGFloat(currentPageOffset) + scrollView.contentOffset.x - weirdScrollViewJumpingCorrectionFactor
welcomeIllustrationView.update(contentOffset: contentOffset) welcomeIllustrationView.update(contentOffset: contentOffset)
} }
} }
//MARK: - UICollectionViewDelegate
extension WelcomeViewController: UICollectionViewDelegate { }
//MARK: - UICollectionViewDataSource
extension WelcomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
educationPages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WelcomeContentCollectionViewCell.identifier, for: indexPath) as? WelcomeContentCollectionViewCell else { fatalError("WTF? Wrong cell?") }
let page = educationPages[indexPath.item]
cell.update(with: page)
return cell
}
}