InfinitePageView.

import UIKit

class InfinitePageView: UIPageViewController {

    /// PageView의 currentIndex가 변경될때 마다 호출된다.
    var pageIndexChanged: ((Int) -> ())?

    /// PageView를 tap했을때 currentPage의 index를 반환한다.
    var didSelectPageAt: ((Int) -> ())?

    /// 보여주고자 하는 images.
    var imageData = [UIImage]() {
        didSet {setPageData()}
    }

    override init(transitionStyle style: UIPageViewControllerTransitionStyle, navigationOrientation: UIPageViewControllerNavigationOrientation, options: [String : Any]? = nil) {
        super.init(transitionStyle: style, navigationOrientation: navigationOrientation, options: options)
        delegate = self
        dataSource = self
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        delegate = self
        dataSource = self
    }

    private var pageData = [UIViewController]()
    private func setPageData() {
        pageData = imageData.enumerated().map { (index, image) -> UIViewController in
            let viewController = UIViewController()
            viewController.view.backgroundColor = .clear
            viewController.view.tag = index
            let imageView = UIImageView(frame: viewController.view.bounds)
            imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            imageView.backgroundColor = .clear
            imageView.image = image
            viewController.view.addSubview(imageView)
            return viewController
        }
        setViewControllers([pageData[0]], direction: .forward, animated: false)
    }

    private var candidateIndex = 0
    private var currentIndex = 0 {
        didSet {pageIndexChanged?(self.currentIndex)}
    }

    /// didSelectPageAt의 사용여부. (기본값: true)
    func isSelectable(_ value: Bool = true) {
        if value {
            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didSelected))
            view.addGestureRecognizer(tapGesture)
        } else {
            view.gestureRecognizers?.removeAll()
        }
    }
    @objc private func didSelected() {
        didSelectPageAt?(currentIndex)
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        for view in view.subviews {
            if view is UIScrollView { // PageView 크기를 하단 Indicator에 겹치도록
                view.frame = self.view.bounds
            } else if view is UIPageControl { // Indicator 색상변경
                (view as! UIPageControl).pageIndicatorTintColor = .clear
                (view as! UIPageControl).currentPageIndicatorTintColor = .clear
                self.view.bringSubview(toFront: view)
            }
        }
    }

}

extension InfinitePageView: UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {currentIndex = candidateIndex}
    }

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        candidateIndex = pendingViewControllers[0].view.tag
    }

}

extension InfinitePageView: UIPageViewControllerDataSource {

    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        return 0
    }

    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return pageData.count
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard pageData.count > 1 else {return nil}
        if viewController.view.tag > 0 {
            return pageData[viewController.view.tag - 1]
        }
        return pageData.last
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard pageData.count > 1 else {return nil}
        if viewController.view.tag < pageData.count - 1 {
            return pageData[viewController.view.tag + 1]
        }
        return pageData.first
    }

}