Skip to content

Instantly share code, notes, and snippets.

@Jimmy-Prime
Last active May 14, 2020 09:43
Show Gist options
  • Save Jimmy-Prime/688791fc4894ce28442167d6a65597ea to your computer and use it in GitHub Desktop.
Save Jimmy-Prime/688791fc4894ce28442167d6a65597ea to your computer and use it in GitHub Desktop.
Implementation of infinite scroll
import UIKit
protocol ScrollingContent: Comparable {}
protocol ScrollingContentProvider {
associatedtype Content: ScrollingContent
func previous(of content: Content) -> Content
func next(of content: Content) -> Content
}
protocol ScrollingContentView: UIViewController {
associatedtype Content: ScrollingContent
func set(content: Content)
}
class InfinitePagingScrollViewController<ContentView: ScrollingContentView, ContentProvider: ScrollingContentProvider>: UIViewController, UIScrollViewDelegate where ContentView.Content == ContentProvider.Content {
private var didLayoutSubviews: Bool = false
private let scrollView = UIScrollView()
private var contentViews: [ContentView] = (1...3).map { _ in ContentView() }
private var middleContent: ContentView.Content
private let contentProvider: ContentProvider
init(middleContent: ContentView.Content, contentProvider: ContentProvider) {
self.middleContent = middleContent
self.contentProvider = contentProvider
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
contentViews.forEach { scrollView.addSubview($0.view) }
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !didLayoutSubviews {
didLayoutSubviews = true
contentViews[0].set(content: contentProvider.previous(of: middleContent))
contentViews[1].set(content: middleContent)
contentViews[2].set(content: contentProvider.next(of: middleContent))
layoutContents()
scrollView.contentSize = CGSize(width: scrollView.bounds.width * 3, height: scrollView.bounds.height)
scrollView.contentOffset = CGPoint(x: scrollView.bounds.width, y: 0)
}
}
func layoutContents() {
for (index, vc) in contentViews.enumerated() {
vc.view.frame = CGRect(origin: .init(x: scrollView.bounds.width * CGFloat(index), y: 0), size: scrollView.bounds.size)
}
}
// MARK: - UIScrollViewDelegate
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let page = targetContentOffset.pointee.x / scrollView.bounds.width - 1
guard page != 0 else { return }
if page > 0 {
middleContent = contentProvider.next(of: middleContent)
let view = contentViews.remove(at: 0)
view.set(content: contentProvider.next(of: middleContent))
contentViews.append(view)
} else {
middleContent = contentProvider.previous(of: middleContent)
let view = contentViews.remove(at: 2)
view.set(content: contentProvider.previous(of: middleContent))
contentViews.insert(view, at: 0)
}
layoutContents()
scrollView.contentOffset.x -= scrollView.bounds.width * CGFloat(page)
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.width, y: 0)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment