Skip to content

Instantly share code, notes, and snippets.

@marlonjames71
Last active February 3, 2025 02:12
Show Gist options
  • Save marlonjames71/d62fb2719089ed9b875b35dabfdffed7 to your computer and use it in GitHub Desktop.
Save marlonjames71/d62fb2719089ed9b875b35dabfdffed7 to your computer and use it in GitHub Desktop.
DynamicPagedViewController
// For one dimensional array
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = mediaItems[indexPath.item]
let pagedMediaViewController = PagedMediaController(mediaItem: item, currentIndex: indexPath.item)
pagedMediaViewController.modalPresentationStyle = .overFullScreen
present(pagedMediaViewController, animated: true)
}
// In case you're dealing with a two dimensional array like I was dealing with since the collection view's items
// had sections
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = mediaItems[indexPath.section][indexPath.item]
let joinedItems = mediaItems.flatMap { $0 }
guard let currentIndexForPagedMediaVC = joinedItems.firstIndex(of: item) else { return }
let pagedMediaViewController = PagedMediaController(mediaItem: item, currentIndex: currentIndexForPagedMediaVC)
pagedMediaViewController.modalPresentationStyle = .overFullScreen
present(pagedMediaViewController, animated: true)
}
struct MediaItem {
let id: UUID
let mediaType: MediaType
let title: String?
let mediaUrl: URL?
}
enum MediaType { case video, photo }
class MediaViewController: UIViewController {
let mediaItem: MediaItem
init(mediaItem: MediaItem) {
self.mediaItem = mediaItem
}
required init(coder: NSCoder) {
fatalError("Use init(mediaItem: MediaItem)")
}
// … More code
}
class VideoViewController: MediaViewController {
...
}
class PhotoViewController: MediaViewController {
...
}
private func mediaViewControllerAtIndex(_ index: Int) -> MediaViewController? {
// returns nil if the index is out of bounds, this way the app won't crash.
// You can also put a print statement before you return.
guard (0...mediaItems.count).contains(index) else { return nil }
let item = mediaItems[index]
switch item.mediaType {
case .video:
return VideoViewController(mediaItem: item)
case .photo:
return PhotoViewController(mediaItem: item)
}
}
class PagedMediaViewController: UIPageViewController {
// MARK: - Properties
private var mediaItems: [MediaItem]
private var currentIndex: Int
// MARK: - Init
init(mediaItems: [MediaItem], currentIndex: Int) {
self.mediaItems = mediaItems
self.currentIndex = currentIndex
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
required init(coder: NSCoder) {
fatalError("Use init(mediaItems: [MediaItem], currentIndex: Int)")
}
}
extension PagedMediaViewController: UIPageViewControllerDataSource {
pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard currentIndex > 0 else { return nil }
return mediaViewControllerAtIndex(currentIndex - 1)
}
pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard currentIndex < mediaItems.count - 1 else { return nil }
return mediaViewControllerAtIndex(currentIndex + 1)
}
}
extension PagedMediaViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard
let mediaVCs = pageViewController.viewControllers as? [MediaViewController],
let currIndex = mediaItems.firstIndex(of: mediaVCs[0].mediaItem)
else { return }
currentIndex = currIndex
}
// If you want to specify the interface orientations you want to support, implement these as well:
func pageViewControllerSupportedInterfaceOrientation(_ pageViewController: UIPageViewController) -> UIInterfaceOrientationMask {
// example
.allButUpsideDown
}
func pageViewControllerPreferredInterfaceOrientationForPresentation(_ pageViewController: UIPageViewController) -> UIInterfaceOrientationMask {
// example
.portrait
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
if let firstMediaVC = mediaViewControllerAtIndex(currentIndex) {
let viewControllers = [firstMediaVC]
setViewControllers(viewControllers, direction: .forward, animated: false, completion: nil)
}
}
class PagedMediaViewController: UIPageViewController {
// MARK: - Properties
private var mediaItems: [MediaItem]
private var currentIndex: Int
// MARK: - Init
init(mediaItems: [MediaItem], currentIndex: Int) {
self.mediaItems = mediaItems
self.currentIndex = currentIndex
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
required init(coder: NSCoder) {
fatalError("Use init(mediaItems: [MediaItem], currentIndex: Int)")
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
if let firstMediaVC = mediaViewControllerAtIndex(currentIndex) {
let viewControllers = [firstMediaVC]
setViewControllers(viewControllers, direction: .forward, animated: false, completion: nil)
}
}
// MARK: - Helper Methods
private func mediaViewControllerAtIndex(_ index: Int) -> MediaViewController? {
// returns nil if the index is out of bounds, this way the app won't crash.
// You can also put a print statement before you return.
guard (0...mediaItems.count).contains(index) else { return nil }
let item = mediaItems[index]
return switch item.mediaType {
case .video:
VideoViewController(mediaItem: item)
case .photo:
PhotoViewController(mediaItem: item)
}
}
}
// MARK: - DataSource Methods
extension PagedMediaViewController: UIPageViewControllerDataSource {
pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController
) -> UIViewController? {
guard currentIndex > 0 else { return nil }
return mediaViewControllerAtIndex(currentIndex - 1)
}
pageViewController(
_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController
) -> UIViewController? {
guard currentIndex < mediaItems.count - 1 else { return nil }
return mediaViewControllerAtIndex(currentIndex + 1)
}
}
// MARK: - Delelgate Methods
extension PagedMediaViewController: UIPageViewControllerDelegate {
func pageViewController(
_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool
) {
guard
let mediaVCs = pageViewController.viewControllers as? [MediaViewController],
let currIndex = mediaItems.firstIndex(of: mediaVCs[0].mediaItem)
else { return }
currentIndex = currIndex
}
// If you want to specify the interface orientations you want to support, implement these as well:
func pageViewControllerSupportedInterfaceOrientation(
_ pageViewController: UIPageViewController
) -> UIInterfaceOrientationMask {
// example
.allButUpsideDown
}
func pageViewControllerPreferredInterfaceOrientationForPresentation(
_ pageViewController: UIPageViewController
) -> UIInterfaceOrientationMask {
// example
.portrait
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment