관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] UIPageViewController의 첫 페이지, 마지막 페이지 Scroll disable 방법 본문

iOS 응용 (swift)

[iOS - swift] UIPageViewController의 첫 페이지, 마지막 페이지 Scroll disable 방법

jake-kim 2023. 1. 25. 23:59

UIPageViewController 첫 페이지와 마지막 페이지 스크롤 disable 방법

  • UIPageViewController의 일반적인 경우
    • 첫번째 페이지에서 swipe left to right를 해도 스크롤 (bounce)
    • 마지막 페이지에서 swipe right to left를 해도 스크롤 (bounce)

  • 첫번째 페이지와 마지막 페이지에서 bounce 효과를 없애는 방법?

구현 방법

  • UIPageViewController에는 내부적으로 UIScrollView가 있는데, 이 스크롤뷰를 스크롤 할때마다 bounces 프로퍼티를 가지고 on/off 시도하여 구현
  • 기본 PageViewController UI 준비
    • PageViewController 내부에는 LabelViewController가 있는 구성
import UIKit

class ViewController: UIViewController {
    private let pageVC: UIPageViewController = {
        let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
        pageVC.view.translatesAutoresizingMaskIntoConstraints = false
        return pageVC
    }()
    
    private var items = (0...2).map(String.init)
    fileprivate var contentViewControllers = [UIViewController]()
    var currentPageIndex: Int {
        contentViewControllers
            .enumerated()
            .first(where: { _, vc in vc == pageVC.viewControllers?.first })
            .map(\.0) ?? 0
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setUpViewControllers()
        
        setUpViews()
        
        setUpLayout()
    }
    
    private func setUpViewControllers() {
        items
            .forEach { title in
                let vc = LabelViewController() // 단순히 UILabel을 가지고 있는 VC
                vc.titleText = title
                contentViewControllers.append(vc)
            }
    }
    
    private func setUpViews() {
        pageVC.dataSource = self
        addChild(pageVC)
        pageVC.didMove(toParent: self)
        pageVC.setViewControllers([contentViewControllers[0]], direction: .forward, animated: false)
    }
    
    private func setUpLayout() {
        view.addSubview(pageVC.view)
        
        NSLayoutConstraint.activate([
            pageVC.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            pageVC.view.rightAnchor.constraint(equalTo: view.rightAnchor),
            pageVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            pageVC.view.topAnchor.constraint(equalTo: view.topAnchor),
        ])
    }
}

extension ViewController: UIPageViewControllerDataSource {
    // left -> right 스와이프 하기 직전 호출 (다음 화면은 무엇인지 리턴)
    func pageViewController(
        _ pageViewController: UIPageViewController,
        viewControllerBefore viewController: UIViewController
    ) -> UIViewController? {
        guard let index = contentViewControllers.firstIndex(of: viewController) else { return nil }
        let previousIndex = index - 1
        guard previousIndex >= 0 else { return nil }
        return contentViewControllers[previousIndex]
    }
    
    // right -> left 스와이프 하기 직전 호출 (이전 화면은 무엇인지 리턴)
    func pageViewController(
        _ pageViewController: UIPageViewController,
        viewControllerAfter viewController: UIViewController
    ) -> UIViewController? {
        guard let index = contentViewControllers.firstIndex(of: viewController) else { return nil }
        let nextIndex = index + 1
        guard nextIndex < contentViewControllers.count else { return nil }
        return contentViewControllers[nextIndex]
    }
}
  • PageViewController에서 scrollView를 찾아서, delegate = self 로 할당
// 1.
let scrollView = pageVC.view.subviews
    .compactMap { $0 as? UIScrollView }
    .first

scrollView?.delegate = self
  • 델리게이트 구현
    • 스크롤 될 때마다 현재의 page index 값을 구한 후, 지금 페이지가 첫번째거나 마지막인 경우 bounces를 비활성화하면 완료
extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 2.
        print(scrollView.bounces, scrollView.contentOffset)
        
        let currentPageIndex = contentViewControllers
            .enumerated()
            .first(where: { _, vc in vc == pageVC.viewControllers?.first })
            .map(\.0) ?? 0
        
        let isFirstable = currentPageIndex == 0
        let isLastable = currentPageIndex == contentViewControllers.count - 1
        let shouldDisableBounces = isFirstable || isLastable
        scrollView.bounces = !shouldDisableBounces
    }
}

* 전체 코드: https://github.com/JK0369/ExPageVC

Comments