출처 블로그 – 찾아본 결과 여기에 나온 설명이 제일 이해하기 편했습니다.

iOS로 된 전자책이나 앨범 등을 만들 때 이미지를 좌우로 스와이프해서 넘기는 형태로 만들 필요가 있습니다. 이럴 때 사용하는 뷰 컨트롤러가 페이지 뷰 컨트롤러 (UIPageViewController) 입니다.

 

스토리보드

먼저 스토리보드에서 페이지 뷰 컨트롤러를 생성합니다.

 

페이지 뷰 컨트롤러는 다른 뷰 컨트롤러를 배열에 담은 다음, 그 배열을 데이터소스가 탐색하면서 페이지가 이동하는 형식으로 되어있습니다. 따라서 페이지 뷰 컨트롤러에서 나타낼 뷰 컨트롤러(UIViewController)들을 생성해야 합니다.

뷰 컨트롤러의 생성 방법에는 스토리보드에서 생성하는 방법과 뷰 컨트롤러 코드 내에서 동적으로 생성하는 방법이 있는데, 여기서는 이해하기 쉽게 뷰 컨트롤러에서 생성한 다음 각 컨트롤러에 스토리보드 아이디를 부여한 다음 페이지 뷰 컨트롤러에서 아이디를 통해 로딩하는 방식을 사용하겠습니다.

 

스토리보드에서 뷰 컨트롤러를 페이지 개수만큼 생성합니다.

 

각각 뷰 컨트롤러를 선택 후, Identity Inspector에서 스토리보드 아이디(Storyboard ID)를 설정합니다.

 

UIPageViewController를 상속받는 페이지뷰 컨트롤러 소스코드를 생성합니다.

class PageViewController: UIPageViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

 

이 소스코드를 아까 생성한 스토리뷰의 페이지 뷰 컨트롤러에 연결합니다. 마찬가지로 Identity Inspector를 사용합니다.

 

그 다음에는 가장 첫 화면에 이 페이지 뷰 컨트롤러를 표시할 부분을 만들어야 합니다. 뷰 안에 다른 뷰 컨트롤러를 포함하여 표시하려면 Container View를 사용하면 됩니다.

 

해당 컨테이너 뷰로부터 페이지 뷰 컨트롤러로 드래그한 다음, 연결 방식으로 embed를 선택합니다.

 

여기까지 마치면 메인 뷰 컨트롤러 안에 페이지 뷰 컨트롤러가 embed로 포함됩니다. 최종적으로 다음과 같은 형태로 스토리보드가 구성됩니다.

 

코드 구현

PageViewController 안에 배열과 유틸리티 함수를 작성합니다.

lazy var vcArray: [UIViewController] = {
    return [self.vcInstance(name: "FirstPageVC"),
            self.vcInstance(name: "SecondPageVC"),
            self.vcInstance(name: "ThirdPageVC")]
}()

private func vcInstance(name: String) -> UIViewController{
    return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
  • vcInstance 함수는 스토리보드 아이디에 맞는 뷰 컨트롤러 인스턴스를 반환하는 함수입니다.
  • vcArray는 위에서 생성한 페이지를 구성하는 하위 뷰 컨트롤러들의 인스턴스를 담는 배열이며, lazy var 를 사용해 실제 인스턴스가 사용되는 시점에 인스턴스가 생성돼 메모리에 적재하도록 되어있습니다.

 

UIPageViewControllerDataSource, UIPageViewControllerDelegate을 구현하는 페이지 뷰 컨트롤러의 extension을 생성합니다.

extension PageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {

    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

    }
    
}

딜리게이트, 데이터소스 프로토콜을 구현하면 IDE가 필수로 구현해야 할 protocol stub을 추천하므로 그대로 사용하면 됩니다.

위의 함수는 이전 페이지로 넘기는 과정, 아래 함수는 다음 페이지로 넘기는 과정입니다.

 

viewDidLoad 안에 딜리게이트, 데이터소스를 연결하고 첫 번째 컨트롤러(페이지)를 기본으로 나타나도록 설정합니다.

// 딜리게이트, 데이터소스 연결
self.dataSource = self
self.delegate = self

// 첫 번째 페이지를 기본 페이지로 설정
if let firstVC = vcArray.first {
    setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}

 

위의 프로토콜 stub의 구체적인 내용을 구현합니다. 먼저 이전 페이지로 넘기는 액션입니다.

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    // 배열에서 현재 페이지의 컨트롤러를 찾아서 해당 인덱스를 현재 인덱스로 기록
    guard let vcIndex = vcArray.firstIndex(of: viewController) else { return nil }
    
    // 이전 페이지 인덱스
    let prevIndex = vcIndex - 1
    
    // 인덱스가 0 이상이라면 그냥 놔둠
    guard prevIndex >= 0 else {
        return nil
        
        // 무한반복 시 - 1페이지에서 마지막 페이지로 가야함
        // return vcArray.last
    }
    
    // 인덱스는 vcArray.count 이상이 될 수 없음
    guard vcArray.count > prevIndex else { return nil }
    
    return vcArray[prevIndex]
}
  • vcIndex – 현재 페이지의 인덱스입니다. 참고로 인덱스는 배열의 인덱스를 사용하므로 0부터 시작합니다.
  • prevIndex – 이전 페이지의 인덱스입니다.
  • prevIndex 가 0 이상이라면 계속 진행하고, 아니라면 첫페이지라는 의미이므로 nil을 리턴합니다. nil을 리턴하면 더 이상 페이지 넘김이 진행되지 않습니다. 만약 무한반복되는 형태를 원한다면 마지막 페이지를 리턴하도록 합니다.
  • 최종적으로 prevIndex번째의 뷰 컨트롤러를 반환합니다.

 

다음 페이지로 넘기는 액션입니다.

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    guard let vcIndex = vcArray.firstIndex(of: viewController) else { return nil }
    
    // 다음 페이지 인덱스
    let nextIndex = vcIndex + 1
    
    guard nextIndex < vcArray.count else {
        return nil
        
        // 무한반복 시 - 마지막 페이지에서 1 페이지로 가야함
        // return vcArray.first
    }
    
    guard vcArray.count > nextIndex else { return nil }
    
    return vcArray[nextIndex]
}
  • nextIndex – 다음 페이지의 인덱스입니다.
  • nextindex(배열 카운트 - 1) 과 같다면 마지막 페이지이므로 nil 또는 첫번째 페이지를 리턴합니다.

 

 

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: Swift


1개의 댓글

bale · 2022년 3월 10일 1:51 오후

pageView Controller에 대한 예제가 많지 않아 어려웠는데 잘 정리해주셔서 감사합니다!!

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다