Compare commits

...

6 Commits

23 changed files with 131 additions and 36 deletions

View File

@ -88,6 +88,10 @@
argument = "-com.apple.CoreData.ConcurrencyDebug 1" argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
<CommandLineArgument
argument = "-UIFocusLoggingEnabled YES"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument <CommandLineArgument
argument = "-UIFocusLoopDebuggerEnabled YES" argument = "-UIFocusLoopDebuggerEnabled YES"
isEnabled = "YES"> isEnabled = "YES">

View File

@ -36,6 +36,7 @@ class AccountListViewController: UIViewController, CollectionViewController {
view = UICollectionView(frame: .zero, collectionViewLayout: layout) view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
dataSource = createDataSource() dataSource = createDataSource()
} }

View File

@ -79,6 +79,7 @@ class AssetCollectionViewController: UIViewController, UICollectionViewDelegate
collectionView.alwaysBounceVertical = true collectionView.alwaysBounceVertical = true
collectionView.allowsMultipleSelection = true collectionView.allowsMultipleSelection = true
collectionView.allowsSelection = true collectionView.allowsSelection = true
collectionView.allowsFocus = true
collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier) collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier)

View File

@ -33,6 +33,8 @@ class AssetCollectionsListViewController: UITableViewController {
tableView.register(UINib(nibName: "AllPhotosTableViewCell", bundle: .main), forCellReuseIdentifier: "allPhotosCell") tableView.register(UINib(nibName: "AllPhotosTableViewCell", bundle: .main), forCellReuseIdentifier: "allPhotosCell")
tableView.register(UINib(nibName: "AlbumTableViewCell", bundle: .main), forCellReuseIdentifier: "albumCell") tableView.register(UINib(nibName: "AlbumTableViewCell", bundle: .main), forCellReuseIdentifier: "albumCell")
tableView.allowsFocus = true
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
switch item { switch item {
case .cameraRoll: case .cameraRoll:

View File

@ -41,6 +41,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 140 tableView.estimatedRowHeight = 140
tableView.allowsFocus = true
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell)

View File

@ -61,13 +61,13 @@ class ConversationTableViewController: EnhancedTableViewController {
tableView.delegate = self tableView.delegate = self
tableView.dataSource = self tableView.dataSource = self
tableView.prefetchDataSource = self
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell") tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell")
tableView.register(UINib(nibName: "ExpandThreadTableViewCell", bundle: .main), forCellReuseIdentifier: "expandThreadCell") tableView.register(UINib(nibName: "ExpandThreadTableViewCell", bundle: .main), forCellReuseIdentifier: "expandThreadCell")
tableView.prefetchDataSource = self tableView.allowsFocus = true
tableView.backgroundColor = .secondarySystemBackground tableView.backgroundColor = .secondarySystemBackground
// separators are disabled on the table view so we can re-add them ourselves // separators are disabled on the table view so we can re-add them ourselves
// so they're not inserted in between statuses in the ame sub-thread // so they're not inserted in between statuses in the ame sub-thread

View File

@ -26,7 +26,7 @@ class ExpandThreadTableViewCell: UITableViewCell {
threadLinkView = UIView() threadLinkView = UIView()
threadLinkView.translatesAutoresizingMaskIntoConstraints = false threadLinkView.translatesAutoresizingMaskIntoConstraints = false
threadLinkView.backgroundColor = tintColor.withAlphaComponent(0.5) threadLinkView.backgroundColor = .tintColor.withAlphaComponent(0.5)
threadLinkView.layer.cornerRadius = 2.5 threadLinkView.layer.cornerRadius = 2.5
contentView.addSubview(threadLinkView) contentView.addSubview(threadLinkView)
threadLinkViewFullHeightConstraint = threadLinkView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) threadLinkViewFullHeightConstraint = threadLinkView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)

View File

@ -31,7 +31,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2 replies" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dcm-ll-GeE"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2 replies" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dcm-ll-GeE">
<rect key="frame" x="108" y="12" width="65" height="20.5"/> <rect key="frame" x="108" y="12" width="65" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="systemBlueColor"/> <color key="textColor" systemColor="tintColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
@ -74,7 +74,7 @@
<systemColor name="separatorColor"> <systemColor name="separatorColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
<systemColor name="systemBlueColor"> <systemColor name="tintColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
</resources> </resources>

View File

@ -42,6 +42,7 @@ class AddSavedHashtagViewController: UIViewController {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self collectionView.delegate = self
collectionView.allowsFocus = true
view.addSubview(collectionView) view.addSubview(collectionView)
let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { (headerView, collectionView, indexPath) in let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { (headerView, collectionView, indexPath) in

View File

@ -50,6 +50,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate, Collect
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
view.addSubview(collectionView) view.addSubview(collectionView)
dataSource = createDataSource() dataSource = createDataSource()

View File

@ -68,6 +68,7 @@ class ProfileDirectoryViewController: UIViewController {
collectionView.register(UINib(nibName: "FeaturedProfileCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: "featuredProfileCell") collectionView.register(UINib(nibName: "FeaturedProfileCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: "featuredProfileCell")
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
view.addSubview(collectionView) view.addSubview(collectionView)
dataSource = createDataSource() dataSource = createDataSource()

View File

@ -40,6 +40,7 @@ class TrendingHashtagsViewController: UIViewController {
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
view.addSubview(collectionView) view.addSubview(collectionView)
let registration = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { cell, indexPath, hashtag in let registration = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { cell, indexPath, hashtag in

View File

@ -36,6 +36,7 @@ class TrendingLinksViewController: EnhancedTableViewController {
tableView.register(TrendingLinkTableViewCell.self, forCellReuseIdentifier: "trendingLinkCell") tableView.register(TrendingLinkTableViewCell.self, forCellReuseIdentifier: "trendingLinkCell")
tableView.estimatedRowHeight = 100 tableView.estimatedRowHeight = 100
tableView.allowsFocus = true
dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingLinkCell", for: indexPath) as! TrendingLinkTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "trendingLinkCell", for: indexPath) as! TrendingLinkTableViewCell

View File

@ -62,12 +62,15 @@ class TrendingStatusesViewController: UIViewController {
} }
let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment) let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
section.contentInsetsReference = .readableContent section.contentInsetsReference = .readableContent
}
return section return section
} }
view = UICollectionView(frame: .zero, collectionViewLayout: layout) view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
dataSource = createDataSource() dataSource = createDataSource()
} }

View File

@ -53,6 +53,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 66 tableView.estimatedRowHeight = 66
tableView.allowsSelection = false
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
guard case let .account(id) = item else { fatalError() } guard case let .account(id) = item else { fatalError() }

View File

@ -55,7 +55,9 @@ class NotificationsTableViewController: DiffableTimelineLikeTableViewController<
tableView.register(UINib(nibName: "PollFinishedTableViewCell", bundle: .main), forCellReuseIdentifier: pollCell) tableView.register(UINib(nibName: "PollFinishedTableViewCell", bundle: .main), forCellReuseIdentifier: pollCell)
tableView.register(UINib(nibName: "StatusUpdatedNotificationTableViewCell", bundle: .main), forCellReuseIdentifier: updatedCell) tableView.register(UINib(nibName: "StatusUpdatedNotificationTableViewCell", bundle: .main), forCellReuseIdentifier: updatedCell)
tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: unknownCell) tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: unknownCell)
tableView.cellLayoutMarginsFollowReadableWidth = true tableView.cellLayoutMarginsFollowReadableWidth = true
tableView.allowsFocus = true
} }
private func request(range: RequestRange) -> Request<[Pachyderm.Notification]> { private func request(range: RequestRange) -> Request<[Pachyderm.Notification]> {

View File

@ -85,13 +85,16 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
return .list(using: .init(appearance: .plain), layoutEnvironment: environment) return .list(using: .init(appearance: .plain), layoutEnvironment: environment)
} else { } else {
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment) let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
section.contentInsetsReference = .readableContent section.contentInsetsReference = .readableContent
}
return section return section
} }
} }
view = UICollectionView(frame: .zero, collectionViewLayout: layout) view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
registerTimelineLikeCells() registerTimelineLikeCells()
dataSource = createDataSource() dataSource = createDataSource()
@ -553,6 +556,15 @@ extension ProfileStatusesViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self) MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
} }
func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
switch dataSource.itemIdentifier(for: indexPath) {
case .header(_), .loadingIndicator:
return false
default:
return true
}
}
} }
extension ProfileStatusesViewController: UICollectionViewDragDelegate { extension ProfileStatusesViewController: UICollectionViewDragDelegate {

View File

@ -83,6 +83,8 @@ class SearchResultsViewController: EnhancedTableViewController {
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell)
tableView.register(UINib(nibName: "HashtagTableViewCell", bundle: .main), forCellReuseIdentifier: hashtagCell) tableView.register(UINib(nibName: "HashtagTableViewCell", bundle: .main), forCellReuseIdentifier: hashtagCell)
tableView.allowsFocus = true
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in
switch item { switch item {
case let .account(id): case let .account(id):

View File

@ -70,6 +70,7 @@ class SearchViewController: UIViewController {
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.backgroundColor = .secondarySystemBackground collectionView.backgroundColor = .secondarySystemBackground
collectionView.allowsFocus = true
view.addSubview(collectionView) view.addSubview(collectionView)
dataSource = createDataSource() dataSource = createDataSource()

View File

@ -74,6 +74,7 @@ class StatusActionAccountListViewController: UIViewController, CollectionViewCon
view = UICollectionView(frame: .zero, collectionViewLayout: layout) view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
dataSource = createDataSource() dataSource = createDataSource()
} }

View File

@ -90,12 +90,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
// just setting layout.configuration.contentInsetsReference doesn't work with UICollectionViewCompositionalLayout.list // just setting layout.configuration.contentInsetsReference doesn't work with UICollectionViewCompositionalLayout.list
let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment) let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
section.contentInsetsReference = .readableContent section.contentInsetsReference = .readableContent
}
return section return section
} }
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dragDelegate = self collectionView.dragDelegate = self
collectionView.allowsFocus = true
collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView) view.addSubview(collectionView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@ -353,10 +356,26 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
@MainActor @MainActor
private func loadStatusesToRestore(position: TimelinePosition) async -> Bool { private func loadStatusesToRestore(position: TimelinePosition) async -> Bool {
{
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Original statusIDs before filtering"
crumb.data = [
"statusIDs": position.statusIDs,
]
SentrySDK.addBreadcrumb(crumb: crumb)
}()
let unloaded = position.statusIDs.filter({ mastodonController.persistentContainer.status(for: $0) == nil }) let unloaded = position.statusIDs.filter({ mastodonController.persistentContainer.status(for: $0) == nil })
guard !unloaded.isEmpty else { guard !unloaded.isEmpty else {
return true return true
} }
{
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Unloaded ids"
crumb.data = [
"unloaded": unloaded
]
SentrySDK.addBreadcrumb(crumb: crumb)
}()
let results = await withTaskGroup(of: (String, Result<Status, Swift.Error>).self) { group -> [(String, Result<Status, Swift.Error>)] in let results = await withTaskGroup(of: (String, Result<Status, Swift.Error>).self) { group -> [(String, Result<Status, Swift.Error>)] in
for id in unloaded { for id in unloaded {
group.addTask { group.addTask {
@ -377,6 +396,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
switch result { switch result {
case .success(let status): case .success(let status):
statuses.append(status) statuses.append(status)
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Loaded status \(id)"
SentrySDK.addBreadcrumb(crumb: crumb)
case .failure(let error): case .failure(let error):
let crumb = Breadcrumb(level: .error, category: "TimelineViewController") let crumb = Breadcrumb(level: .error, category: "TimelineViewController")
crumb.message = "Error loading status" crumb.message = "Error loading status"
@ -389,12 +411,14 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} }
await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext) await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext)
_ = {
let crumb = Breadcrumb(level: .info, category: "TimelineViewController") let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Original position statusIDs" crumb.message = "Position statusIDs before filtering"
crumb.data = [ crumb.data = [
"statusIDs": position.statusIDs, "statusIDs": position.statusIDs,
] ]
SentrySDK.addBreadcrumb(crumb: crumb) SentrySDK.addBreadcrumb(crumb: crumb)
}()
// update the timeline position in case some statuses couldn't be loaded // update the timeline position in case some statuses couldn't be loaded
if let center = position.centerStatusID { if let center = position.centerStatusID {
@ -408,12 +432,14 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
!unloaded.contains(id) || statuses.contains(where: { $0.id == id }) !unloaded.contains(id) || statuses.contains(where: { $0.id == id })
} }
let crumb2 = Breadcrumb(level: .info, category: "TimelineViewController") {
crumb2.message = "Filtered position statusIDs" let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb2.data = [ crumb.message = "Filtered position statusIDs"
crumb.data = [
"statusIDs": position.statusIDs, "statusIDs": position.statusIDs,
] ]
SentrySDK.addBreadcrumb(crumb: crumb2) SentrySDK.addBreadcrumb(crumb: crumb)
}()
return !position.statusIDs.isEmpty return !position.statusIDs.isEmpty
} }

View File

@ -12,7 +12,7 @@ protocol SegmentedPageViewControllerPage: Hashable {
var segmentedControlTitle: String { get } var segmentedControlTitle: String { get }
} }
class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIPageViewController, UIPageViewControllerDelegate, TabbedPageViewController { class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIViewController, UIPageViewControllerDelegate, TabbedPageViewController {
private(set) var pages: [Page]! private(set) var pages: [Page]!
private let pageProvider: (Page) -> UIViewController private let pageProvider: (Page) -> UIViewController
@ -23,9 +23,7 @@ class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIPage
var currentIndex: Int! { var currentIndex: Int! {
pages.firstIndex(of: currentPage) pages.firstIndex(of: currentPage)
} }
var currentViewController: UIViewController! { var currentViewController: UIViewController!
viewControllers?.first
}
let segmentedControl = ScrollingSegmentedControl<Page>() let segmentedControl = ScrollingSegmentedControl<Page>()
@ -37,7 +35,7 @@ class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIPage
initialPage = pages.first! initialPage = pages.first!
currentPage = pages.first! currentPage = pages.first!
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) super.init(nibName: nil, bundle: nil)
setPages(pages, animated: false) setPages(pages, animated: false)
@ -104,12 +102,12 @@ class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIPage
initialPage = page initialPage = page
return return
} }
let direction: UIPageViewController.NavigationDirection let direction: AnimationMode
if let prevIndex = currentIndex { if let prevIndex = currentIndex {
let index = pages.firstIndex(of: page)! let index = pages.firstIndex(of: page)!
direction = index - prevIndex > 0 ? .forward : .reverse direction = index - prevIndex > 0 ? .forward : .reverse
} else { } else {
direction = .forward direction = .none
} }
currentPage = page currentPage = page
@ -121,12 +119,40 @@ class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIPage
pageControllers[page] = newController pageControllers[page] = newController
} }
setViewControllers([newController], direction: direction, animated: animated) setViewController(newController, animated: animated ? direction : .none)
navigationItem.title = newController.title navigationItem.title = newController.title
segmentedControl.setSelectedOption(page, animated: animated) segmentedControl.setSelectedOption(page, animated: animated)
} }
private func setViewController(_ newViewController: UIViewController, animated: AnimationMode) {
guard let currentViewController,
animated != .none else {
currentViewController?.removeViewAndController()
newViewController.view.translatesAutoresizingMaskIntoConstraints = false
embedChild(newViewController)
self.currentViewController = newViewController
return
}
guard currentViewController !== newViewController else {
return
}
self.currentViewController = newViewController
newViewController.view.translatesAutoresizingMaskIntoConstraints = false
embedChild(newViewController)
let direction: CGFloat = animated == .forward ? 1 : -1
newViewController.view.transform = CGAffineTransform(translationX: direction * view.bounds.width, y: 0)
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UISpringTimingParameters(dampingRatio: 1, initialVelocity: .zero))
animator.addAnimations {
newViewController.view.transform = .identity
currentViewController.view.transform = CGAffineTransform(translationX: -1 * direction * self.view.bounds.width, y: 0)
}
animator.addCompletion { _ in
currentViewController.removeViewAndController()
}
animator.startAnimation()
}
// MARK: TabbedPageViewController // MARK: TabbedPageViewController
func selectNextPage() { func selectNextPage() {
@ -138,7 +164,14 @@ class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIPage
guard currentIndex > 0 else { return } guard currentIndex > 0 else { return }
selectPage(pages[currentIndex - 1], animated: true) selectPage(pages[currentIndex - 1], animated: true)
} }
}
extension SegmentedPageViewController {
enum AnimationMode: Equatable {
case none
case forward
case reverse
}
} }
extension SegmentedPageViewController: TabBarScrollableViewController { extension SegmentedPageViewController: TabBarScrollableViewController {

View File

@ -495,8 +495,8 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
filteredLabel.removeFromSuperview() filteredLabel.removeFromSuperview()
contentView.addSubview(statusContainer) contentView.addSubview(statusContainer)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
statusContainer.leadingAnchor.constraint(equalTo: UIDevice.current.userInterfaceIdiom == .pad ? contentView.readableContentGuide.leadingAnchor : contentView.leadingAnchor), statusContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
statusContainer.trailingAnchor.constraint(equalTo: UIDevice.current.userInterfaceIdiom == .pad ? contentView.readableContentGuide.trailingAnchor : contentView.trailingAnchor), statusContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
statusContainer.topAnchor.constraint(equalTo: contentView.topAnchor), statusContainer.topAnchor.constraint(equalTo: contentView.topAnchor),
statusContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), statusContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
]) ])
@ -506,8 +506,8 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
statusContainer.removeFromSuperview() statusContainer.removeFromSuperview()
contentView.addSubview(filteredLabel) contentView.addSubview(filteredLabel)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
filteredLabel.leadingAnchor.constraint(equalTo: UIDevice.current.userInterfaceIdiom == .pad ? contentView.readableContentGuide.leadingAnchor : contentView.leadingAnchor), filteredLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
filteredLabel.trailingAnchor.constraint(equalTo: UIDevice.current.userInterfaceIdiom == .pad ? contentView.readableContentGuide.trailingAnchor : contentView.trailingAnchor), filteredLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
filteredLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1), filteredLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1),
contentView.bottomAnchor.constraint(equalToSystemSpacingBelow: filteredLabel.bottomAnchor, multiplier: 1), contentView.bottomAnchor.constraint(equalToSystemSpacingBelow: filteredLabel.bottomAnchor, multiplier: 1),
]) ])