스와이프 좌<->우 이동 및 버튼 좌<->우 이동 PageViewController
import UIKit
protocol IPageViewControllerDelegate: class {
///스크롤 시작 포인트
func pageViewWillBeginScrollDragging(offset: CGPoint)
///스크롤 진행 포인트 / 진행율 (0.0 ~ 1.0)
func pageViewDidScroll(scrollView: UIScrollView, percent: CGFloat, direction: Int)
///페이지 이동 완료 인덱스
func pageMoveDidFinishAnimating(currentPage index: Int)
}
class IPageViewController : UIPageViewController {
weak var pageDelegate: IPageViewControllerDelegate?
private var pageViews: Array<UIViewController>! ///페이지 뷰 컨트롤 배열
private var startOffset = CGFloat(0) ///스크롤뷰 사용
private var chooseIdx = 0 /// 선택한 페이지 Index
private var currentIdx = 0 /// 현재 페이지 Index
override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) {
super.init(transitionStyle: style, navigationOrientation: navigationOrientation, options: options)
}
deinit {
log(direction: .ETC, ofType: self, datas: "IPageViewController deinit")
pageViews.removeAll()
pageDelegate = nil
removeFromParent()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
for v in view.subviews{
if v is UIScrollView {
(v as! UIScrollView).delegate = self
(v as! UIScrollView).bouncesZoom = false
(v as! UIScrollView).isPagingEnabled = true
}
}
}
override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
//초기화 이후 호출되는 경우에는 페이지 강제이동 처리
if pageViews != nil {
super.setViewControllers([viewControllers![0]], direction: direction, animated: animated, completion: completion)
return
}
if viewControllers == nil { return }
guard let views = viewControllers else { return }
pageViews = views
super.setViewControllers([pageViews[0]], direction: direction, animated: animated, completion: completion)
}
override func willMove(toParent parent: UIViewController?){
super.willMove(toParent: parent)
}
override func didMove(toParent parent: UIViewController?){
super.didMove(toParent: parent)
}
/// IPageViewController Child View Controller 클래스 호출
func getViewControllerAtIndex(index: Int) -> UIViewController? {
if index >= pageViews.count { return nil }
let vc = pageViews![index]
return vc
}
/// IPageViewController Parent 클래스 호출
func getParentViewController() -> UIViewController? {
guard let parentVC = self.parent else {
log(direction: .ERROR, ofType: self, datas: "parent VC Load Error")
return nil
}
return parentVC
}
/// 현재 페이지 인덱스
func getCurrentPage() -> Int {
return pageViews[self.currentIdx].view.tag
}
func getTotalPages() -> Int {
return pageViews.count
}
}
extension IPageViewController : UIScrollViewDelegate{
/*
- 제스쳐 페이지 이동시 처음 시작된 페이지 값 (제스쳐 시작시 한번 호출)
- 강제로 페이지 이동시에는 호출안됨 (nextPageWithIndex 사용)
*/
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
startOffset = scrollView.contentOffset.x
pageDelegate?.pageViewWillBeginScrollDragging(offset: scrollView.contentOffset)
}
///제스쳐 이동시 터치 종료 후 호출
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if(currentIdx == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width){
scrollView.contentOffset = CGPoint(x:scrollView.bounds.size.width, y:0.0)
}else if(currentIdx == (pageViews.count - 1) && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x:scrollView.bounds.size.width, y:0.0)
}
}
/// 페이지 이동되는 경우
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 버튼으로 강제 페이지뷰 이동시 startOffset = 0
// 제스쳐 스크롤 이동중 이중터치시 startOffset 값 변경으로 UI 조작시 문제 발생됨
if startOffset == 0 || startOffset != scrollView.frame.width { return }
var direction = 0 //스크롤 멈춤
if startOffset < scrollView.contentOffset.x {
direction = 1 //오른쪽 페이지로 이동
}else if startOffset > scrollView.contentOffset.x {
direction = -1 //왼쪽 페이지로 이동
}
let positionFromStartOfCurrentPage = abs(startOffset - scrollView.contentOffset.x)
// percent 범위 (0.0 ~ 1)
let percent = positionFromStartOfCurrentPage / self.view.frame.width
pageDelegate?.pageViewDidScroll(scrollView: scrollView, percent: percent, direction: direction)
// 끝 페이지 바운스 안되게 처리
if (currentIdx == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width){
scrollView.contentOffset = CGPoint(x:scrollView.bounds.size.width, y:0.0)
}else if (currentIdx == (pageViews.count - 1) && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x:scrollView.bounds.size.width, y:0.0)
}
}
}
extension IPageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
/// 강제 페이지 변경
func nextPageWithIndex(index: Int, animated: Bool = true) {
print("nextPageWithIndex - index:\(index) chooseIdx:\(chooseIdx) currentIdx:\(currentIdx)")
/* 초기화 - startOffset = 0
- 강제로 페이지뷰 이동시(버튼사용 등..) 스크롤뷰 이벤트 발생되지만
pageViewController: didFinishAnimating 이벤트 호출이 안되서 페이지 변경 완료를 알수 없음
- 스크롤 이벤트 연동된 UI 있을경우 필히 사용
*/
var direction: UIPageViewController.NavigationDirection = .forward
if currentIdx > index {
direction = .reverse
} else {
direction = .forward
}
startOffset = 0
chooseIdx = index
currentIdx = index
setViewControllers([pageViews[index]], direction: direction, animated: animated, completion: nil)
//self.viewControllerAtIndex(index: index)
}
// 현재 페이지 로드가 끝났을 때
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
if let currentViewController = pageViewController.viewControllers?.first {
currentIdx = currentViewController.view.tag - 1001
pageDelegate?.pageMoveDidFinishAnimating(currentPage: currentIdx)
}
}
}
/// 이전 페이지 뷰 리턴
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if var index = pageViews.firstIndex(of: viewController) {
if( index == 0 || index == NSNotFound) {
return nil
}
index -= 1
return self.getViewControllerAtIndex(index: index)
}
return nil
}
/// 다음 페이지 뷰 리턴
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if var index = pageViews.firstIndex(of: viewController) {
if( index == NSNotFound) { return nil }
index += 1
if(index == self.pageViews.count){ return nil }
return self.getViewControllerAtIndex(index: index)
}
return nil
}
// 인디케이터 개수
/*
func presentationCount(for pageViewController: UIPageViewController) -> Int {
print("presentationCount - \(textArray.count)")
return self.textArray.count
}
*/
// 인디케이터 초기 선택 값
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
print("chooseIdx - \(chooseIdx)")
return chooseIdx
}
}
[사용방법]
import UIKit
// MARK: IPageViewController Delegate
extension BasicPageViewController: IPageViewControllerDelegate {
func pageViewWillBeginScrollDragging(offset: CGPoint) {
print("pageViewWillBeginScrollDragging \(offset)")
}
func pageViewDidScroll(scrollView: UIScrollView, percent: CGFloat, direction: Int) {
let page = scrollView.contentOffset.x / scrollView.bounds.width
let progressInPage = scrollView.contentOffset.x - (page * scrollView.bounds.width)
let progress = CGFloat(page) + progressInPage
print("pageViewDidScroll \(page) / \(progressInPage) / \(progress)")
}
func pageMoveDidFinishAnimating(currentPage index: Int) {
print("pageMoveDidFinishAnimating \(index)")
pageControl.currentPage = index
currentPageIndex = index
}
}
extension BasicPageViewController: UIPageViewControllerDelegate {
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let nextPage = Int(targetContentOffset.pointee.x / self.view.frame.width)
self.pageControl.currentPage = nextPage
}
}
class BasicPageViewController: UIViewController {
var currentPageIndex = 0
@IBOutlet weak var backButton: UIButton!
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var pageControl: UIPageControl! {
didSet {
}
}
// Custom PageViewController 생성
lazy var pageVC: IPageViewController! = { [weak self] in
/* Storyboard
let storyboard = UIStoryboard(storyboard: .Main)
let pageVC: IPageViewController = storyboard.instantiateViewController()
*/
// Code
let pageVC = IPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
return pageVC
}()
override func viewDidLoad() {
super.viewDidLoad()
// PageView 설정
setupPageView()
// PageController 페이지 설정
pageControl.numberOfPages = pageVC.getTotalPages()
// 이전/다음 페이지 버튼 및 PageController UI 위에 표시
self.view.bringSubviewToFront(backButton)
self.view.bringSubviewToFront(nextButton)
self.view.bringSubviewToFront(pageControl)
}
// PageViewController 설정
func setupPageView() {
pageVC.pageDelegate = self
var pageViews: Array<UIViewController> = []
let pageVC1 = UIViewController()
let pageVC2 = UIViewController()
let pageVC3 = UIViewController()
let pageVC4 = UIViewController()
let pageVC5 = UIViewController()
pageVC1.view.tag = 1001
pageVC2.view.tag = 1002
pageVC3.view.tag = 1003
pageVC4.view.tag = 1004
pageVC5.view.tag = 1005
pageVC1.view.backgroundColor = .bubblegum
pageVC2.view.backgroundColor = .rosePink
pageVC3.view.backgroundColor = .orange
pageVC4.view.backgroundColor = .bubblegum
pageVC5.view.backgroundColor = .greyish
pageViews.append(pageVC1)
pageViews.append(pageVC2)
pageViews.append(pageVC3)
pageViews.append(pageVC4)
pageViews.append(pageVC5)
self.pageVC.setViewControllers(pageViews, direction: .forward, animated: true, completion: nil)
self.addChild(self.pageVC)
self.view.addSubview(self.pageVC.view)
// AutoLayout
self.pageVC.view.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraint(NSLayoutConstraint(item: self.pageVC.view as Any, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.pageVC.view as Any, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.pageVC.view as Any, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.pageVC.view as Any, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0))
self.pageVC.didMove(toParent: self)
}
// 이전 페이지 이동
@IBAction func backButton(_ sender: UIButton) {
let backPage = currentPageIndex - 1
if backPage < 0 {
return
}
pageVC.nextPageWithIndex(index: backPage, animated: true)
pageControl.currentPage = backPage
currentPageIndex = backPage
}
// 다음 페이지 이동
@IBAction func nextButton(_ sender: UIButton) {
let nextPage = currentPageIndex + 1
if nextPage >= pageVC.getTotalPages() {
return
}
pageVC.nextPageWithIndex(index: nextPage, animated: true)
pageControl.currentPage = nextPage
currentPageIndex = nextPage
}
}