I am attempting to create a
UIScrollView
with infinitely many horizontally scrolling pages. I plan to use instances of several reusable cached ViewControllers to fill the pages. To achieve this I am trying to create a similar effect to UIPageViewController by using a beforeViewController
a currentViewController
and an afterViewController
to fill the the three positions as the user scrolls so there is always one viewController before and after the current one the user is looking at to scroll to.I would like to keep setting these instances equal to varying viewControllers from my cache but as I scroll back and forth eventually I am left with an empty ViewController. I believe it is caused by the passed by reference nature of the ViewController and setting one equal to the other.
Below is the code I have created is there any way to improve the functionality. Thank you in advance for your help.
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
var previousOffset:CGFloat = 0
var viewControllers:[UIViewController] = [UIViewController](){
didSet{
print("viewControllers.count: \(viewControllers.count)")
}
}
var beforeViewController:UIViewController?
var currentViewController:UIViewController?
var nextViewController:UIViewController?
var scrollView:UIScrollView = {
let scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.backgroundColor = UIColor.lightGray
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
scrollView.delegate = self
//3 Pages
scrollView.contentSize = CGSize(width: 3 * self.view.bounds.width, height: self.view.bounds.height)
self.view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0),
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
])
currentViewController = getViewController()
addViewController(viewController: currentViewController!, index: 0) { (view) in
view.backgroundColor = UIColor.green
}
nextViewController = getViewController()
addViewController(viewController: nextViewController!, index: 1) { (view) in
view.backgroundColor = UIColor.purple
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentPage = scrollView.contentOffset.x/scrollView.bounds.width
print("current page: \(currentPage)")
if scrollView.contentOffset.x > previousOffset{
print("User scrolled Forward")
scrolledForwards(currentPage: Int(currentPage))
}else if scrollView.contentOffset.x < previousOffset{
print("User scrolled backwards")
scrolledBackwards(currentPage: Int(currentPage))
}
previousOffset = scrollView.contentOffset.x
}
func getViewController()->UIViewController{
let unusedViewControllers:[UIViewController] = self.viewControllers.filter({return $0.parent == nil})
if let unusedViewController = unusedViewControllers.first{
print("reusing viewController: \(viewControllers.count)")
print("reusing viewController: \(unusedViewController.description)")
return unusedViewController
}else{
let newViewController = UIViewController()
self.viewControllers.append(newViewController)
print("creating new viewController")
return newViewController
}
}
func addViewController(viewController:UIViewController,index:Int, completion: ((UIView)->Void)? = nil){
self.willMove(toParent: viewController)
self.addChild(viewController)
guard let view = viewController.view else{
removeViewController(viewController: viewController)
fatalError("view controller sent without a view")
}
self.scrollView.addSubview(view)
viewController.didMove(toParent: self)
let offset = self.view.bounds.width * CGFloat(index)
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0),
view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: offset),
view.heightAnchor.constraint(equalTo: scrollView.heightAnchor, constant: 0),
view.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: 0)
])
if let completion = completion{
completion(view)
}
}
func removeViewController(viewController:UIViewController?, completion: ((UIView)->Void)? = nil){
viewController?.willMove(toParent: nil)
viewController?.view.removeFromSuperview()
viewController?.removeFromParent()
if let completion = completion{
completion(view)
}
}
func scrolledForwards(currentPage:Int = 0){
removeViewController(viewController: beforeViewController)
let index = currentPage + 1
self.beforeViewController = self.currentViewController
self.currentViewController = self.nextViewController
print("index: \(index)")
if index > 2{
print("There is no more forwards")
return
}
self.nextViewController = getViewController()
if nextViewController?.parent == nil{
let index = currentPage + 1
self.addViewController(viewController: nextViewController!, index: index) { (view) in
view.backgroundColor = .magenta
}
}
}
func scrolledBackwards(currentPage:Int = 0){
let index = currentPage - 1
removeViewController(viewController: nextViewController)
nextViewController = currentViewController
currentViewController = beforeViewController
print("index: \(index)")
if index < 0{
print("There is no more backwards")
return
}
beforeViewController = getViewController()
currentViewController?.view.backgroundColor = UIColor.brown
if beforeViewController?.parent == nil{
self.addViewController(viewController: beforeViewController!, index: index) { (view) in
view.backgroundColor = .cyan
}
}
}
}