Skip to content

Commit

Permalink
Working paged controller based on scrollview.
Browse files Browse the repository at this point in the history
  • Loading branch information
BohdanOrlov committed Oct 9, 2016
1 parent ed485e2 commit 7d089a1
Showing 1 changed file with 84 additions and 211 deletions.
295 changes: 84 additions & 211 deletions BouncyPageViewController/Classes/BouncyPageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@

import Foundation

open class BouncyPageViewController: UIViewController, UIScrollViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate {
open class BouncyPageViewController: UIViewController {
//MARK: - VLC
private let flowLayout = UICollectionViewFlowLayout()
private var collectionView: UICollectionView!
open private(set) var viewControllers: [UIViewController?] = [nil]
fileprivate let scrollView = UIScrollView()
open fileprivate(set) var viewControllers: [UIViewController?] = [nil]
public init(viewControllers: [UIViewController]) {
assert(viewControllers.count > 0)
let optionalViewControllers = viewControllers.map(Optional.init)
self.viewControllers.append(contentsOf: optionalViewControllers)
super.init(nibName: nil, bundle: nil)

}

required public init?(coder aDecoder: NSCoder) {
Expand All @@ -23,101 +21,75 @@ open class BouncyPageViewController: UIViewController, UIScrollViewDelegate, UIC

open override func viewDidLoad() {
super.viewDidLoad()
self.collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout)
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.flowLayout.scrollDirection = .vertical
self.flowLayout.minimumInteritemSpacing = 0
self.flowLayout.minimumLineSpacing = 0
self.collectionView.collectionViewLayout = flowLayout
self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast
self.collectionView.panGestureRecognizer.addTarget(self, action: #selector(didPan(recognizer:)))

self.view.addSubview(self.collectionView)
// self.addViewControllersAsChildren()
// self.addPanGestureRecongizer()
}

private func addViewControllersAsChildren(){
for viewController in self.viewControllers {
guard let viewController = viewController else {
continue;
}
self.addChildPageViewController(viewController: viewController)
}
}
private func addChildPageViewController(viewController: UIViewController){
viewController.willMove(toParentViewController: self)
self.addChildViewController(viewController)
self.collectionView.addSubview(viewController.view)
viewController.didMove(toParentViewController: parent)
}
private func addChildPageViewController(viewController: UIViewController, toView: UIView){
viewController.willMove(toParentViewController: self)
self.addChildViewController(viewController)
viewController.view.frame = toView.bounds
toView.addSubview(viewController.view)
viewController.didMove(toParentViewController: parent)
}
private func removeChildPageViewController(viewController: UIViewController){
viewController.willMove(toParentViewController: nil)
viewController.view.removeFromSuperview()
viewController.removeFromParentViewController()
}

var panRecognizer: UIPanGestureRecognizer!
func addPanGestureRecongizer() {
self.panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(recognizer:)))
self.view.addGestureRecognizer(panRecognizer)
}

private func contentOffset() -> CGFloat {
return self.collectionView.contentOffset.y
self.scrollView.panGestureRecognizer.addTarget(self, action: #selector(didPan(recognizer:)))
self.scrollView.delegate = self
self.scrollView.decelerationRate = UIScrollViewDecelerationRateFast
self.view.addSubview(self.scrollView)
}

var baseOffset: CGFloat!
fileprivate var maxOverscroll:CGFloat = 0.5
fileprivate var baseOffset: CGFloat!
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.flowLayout.itemSize = self.pageSize()
self.appendPlaceholdersIfNeeded()
self.baseOffset = self.pageSize().height
self.collectionView.frame = self.view.bounds
self.collectionView.contentOffset = CGPoint(x:0, y:self.baseOffset)

// self.layoutPages()
self.scrollView.frame = self.view.bounds
self.scrollView.contentSize = CGSize(width: self.view.bounds.width, height: self.pageSize().height * CGFloat(self.maxNumberOfPages()))
self.scrollView.contentOffset = CGPoint(x:0, y:self.baseOffset)
}

private func appendPlaceholdersIfNeeded() {
while self.viewControllers.count < self.maxNumberOfVisisblePages() {
while self.viewControllers.count < self.maxNumberOfPages() {
self.viewControllers.append(nil)
}
}

public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.reloadingData = false
return self.maxNumberOfVisisblePages()
//MARK: - Pagination

fileprivate func layoutPages() {
for idx in (0..<self.maxNumberOfPages()) {
var pageOffset = self.pageSize().height * CGFloat(idx)
let origin = CGPoint(x: 0, y: pageOffset)
var rect = CGRect(origin: origin, size: self.pageSize())

var viewController = self.viewControllers[idx];
if viewController == nil && self.scrollView.bounds.intersects(rect) {
viewController = self.requestViewController(index:idx)
self.viewControllers[idx] = viewController
}
if let viewController = viewController {
self.addChildPageViewController(viewController: viewController)
}
viewController?.view.frame = rect
}
}

public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
private func pageSize() -> CGSize {
return CGSize(width:self.view.bounds.width, height:self.view.bounds.midY)
}

return cell;
private func maxNumberOfPages() -> Int {
var numberOfPages = Int(self.view.bounds.maxY / self.pageSize().height)
numberOfPages += 2
return numberOfPages
}

public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if reloadingData {
return
}
var viewController = self.viewControllers[indexPath.item];
if viewController == nil {
viewController = self.requestViewController(index:indexPath.item)
self.viewControllers[indexPath.item] = viewController
}
if let viewController = viewController {
self.addChildPageViewController(viewController: viewController, toView: cell.contentView)
}
//MARK: - Child view controllers
private func addChildPageViewController(viewController: UIViewController){
viewController.willMove(toParentViewController: self)
self.addChildViewController(viewController)
self.scrollView.addSubview(viewController.view)
viewController.didMove(toParentViewController: parent)
}

private func removeChildPageViewController(viewController: UIViewController){
viewController.willMove(toParentViewController: nil)
viewController.view.removeFromSuperview()
viewController.removeFromParentViewController()
}

public var viewControllerBeforeViewController: ((UIViewController) -> UIViewController?)!
public var viewControllerAfterViewController: ((UIViewController) -> UIViewController?)!
private func requestViewController(index: Int) -> UIViewController? {
if index + 1 < self.viewControllers.count {
if let controller = self.viewControllers[index + 1] {
Expand All @@ -131,154 +103,55 @@ open class BouncyPageViewController: UIViewController, UIScrollViewDelegate, UIC
}
return nil
}
private var reloadingData = false
public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if reloadingData {
return
}
if let vc = self.viewControllers[indexPath.item] {
self.removeChildPageViewController(viewController: vc)
}

guard let vc = self.viewControllers[indexPath.item] else {
return
}
self.viewControllers.remove(at:indexPath.item)
if self.contentOffset() > self.baseOffset {
}


extension BouncyPageViewController: UIScrollViewDelegate {
private func contentOffset() -> CGFloat {
return self.scrollView.contentOffset.y
}

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.contentOffset() > self.baseOffset * 2 {
self.viewControllers.removeFirst()
self.viewControllers.append(nil)
// let current CV updates to finish
DispatchQueue.main.async {
self.reloadingData = true
self.collectionView.reloadData()
// if self.contentOffset() > self.baseOffset * 2 {
self.collectionView.contentOffset.y -= self.baseOffset
// }
}
} else {
// self.resetGestureRecongizer()
self.scrollView.contentOffset.y -= self.baseOffset
} else if self.contentOffset() < 0 {
self.viewControllers.removeLast()
self.viewControllers.insert(nil, at: 0)
// let current CV updates to finish
DispatchQueue.main.async {
self.reloadingData = true
self.collectionView.reloadData()
// if self.contentOffset() < self.baseOffset {
self.collectionView.contentOffset.y += self.baseOffset
// }
}
// self.resetGestureRecongizer()
self.scrollView.contentOffset.y += self.baseOffset
}
self.layoutPages()
if self.contentOffset() > self.baseOffset + self.baseOffset * self.maxOverscroll && self.viewControllers.last! == nil {
self.resetGestureRecongizer()
self.scrollView.setContentOffset(CGPoint(x:0, y:self.baseOffset), animated: true)
} else if self.contentOffset() < baseOffset * self.maxOverscroll && self.viewControllers.first! == nil{
self.resetGestureRecongizer()
self.scrollView.setContentOffset(CGPoint(x:0, y:self.baseOffset), animated: true)
}
}

// private func layoutPages() {
// for idx in (0..<self.maxNumberOfVisisblePages()) {
// var pageOffset = self.pageSize().height * CGFloat(idx)
// let origin = CGPoint(x: 0, y: pageOffset)
// var rect = CGRect(origin: origin, size: self.pageSize())
// let viewController = self.viewControllers[idx]
// viewController?.view.frame = rect
// }
// }

//MARK: - Pagination
private func pageSize() -> CGSize {
return CGSize(width:self.view.bounds.width, height:self.view.bounds.midY)
}
// private func numberOfVisisblePages() -> Int {
//
// var numberOfPages = Int(self.view.bounds.maxY / self.pageSize().height)
// if (self.scrollView.contentOffset.y != self.pageSize().height) {
// numberOfPages += 1
// }
// return numberOfPages
// }
private func maxNumberOfVisisblePages() -> Int {
var numberOfPages = Int(self.view.bounds.maxY / self.pageSize().height)
numberOfPages += 2
return numberOfPages
private func resetGestureRecongizer() {
self.scrollView.panGestureRecognizer.isEnabled = false
self.scrollView.panGestureRecognizer.isEnabled = true
}

private var initialContentOffset: CGFloat = 0
@objc private func didPan(recognizer: UIPanGestureRecognizer) {
@objc fileprivate func didPan(recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
// case .began: brea
case .changed:
if self.contentOffset() > self.baseOffset * 2 || self.contentOffset() < 0 {
// recognizer.isEnabled = false
// recognizer.isEnabled = true
}
case .ended, .cancelled, .failed:
let offset:CGFloat
if abs(self.baseOffset - self.contentOffset()) < self.baseOffset / 2 {
offset = self.baseOffset - 1
offset = self.baseOffset
} else if (self.contentOffset() - self.baseOffset > 0) {
offset = self.baseOffset * 2
} else {
offset = 0
}
self.collectionView.setContentOffset(CGPoint(x:0, y:offset), animated: true)
self.scrollView.setContentOffset(CGPoint(x:0, y:offset), animated: true)
default: break
}

}

public func scrollViewDidScroll(_ scrollView: UIScrollView) {

// self.updateViewControllersArray()
// self.updateContentOffset()
// self.layoutPages()
}
// private func updateContentOffset() {
// if self.contentOffset() > self.pageSize().height * 2 {
// self.scrollView.contentOffset.y -= self.pageSize().height
// }
// if self.contentOffset() < -self.pageSize().height * 2 {
// self.scrollView.contentOffset.y += self.pageSize().height
// }
// }


private func panOffset() -> CGFloat {
return self.panRecognizer.translation(in: self.panRecognizer.view).y
}

public var viewControllerBeforeViewController: ((UIViewController) -> UIViewController?)!
public var viewControllerAfterViewController: ((UIViewController) -> UIViewController?)!
private func updateViewControllersArray() {
// if self.contentOffset() > self.baseOffset {
// self.removeChildPageViewController(viewController: self.viewControllers.removeFirst())
// self.insertVCToBottom()
// }
// if (self.numberOfVisisblePages() > self.viewControllers.count) {
// if self.contentOffset() > self.pageSize().height {
// self.insertVCToBottom()
// } else if self.contentOffset() < self.pageSize().height {
// self.insertVCToTop()
// }
// }
// else if self.contentOffset() > self.pageSize().height {
// self.removeChildPageViewController(viewController: self.viewControllers.removeFirst())
// self.insertVCToBottom()
// }
// else if self.contentOffset() < -self.pageSize().height {
// self.removeChildPageViewController(viewController: viewControllers.removeLast())
// self.insertVCToTop()
// }
//}
//
// private func insertVCToBottom() {
// guard let nextVC = self.viewControllerAfterViewController(self.viewControllers.last!) else {
// return
// }
// self.viewControllers.append(nextVC)
// self.addChildPageViewController(viewController: nextVC)
// self.scrollView.contentOffset.y -= self.pageSize().height
// }
// private func insertVCToTop() {
// guard let nextVC = self.viewControllerBeforeViewController(self.viewControllers.first!) else {
// return
// }
// self.viewControllers.insert(nextVC, at: 0)
// self.addChildPageViewController(viewController: nextVC)
// self.scrollView.contentOffset.y += self.pageSize().height
}

}

0 comments on commit 7d089a1

Please sign in to comment.