출처 블로그 – 찾아본 결과 여기에 나온 설명이 제일 이해하기 편했습니다.
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또는 첫번째 페이지를 리턴합니다.





1개의 댓글
bale · 2022년 3월 10일 1:51 오후
pageView Controller에 대한 예제가 많지 않아 어려웠는데 잘 정리해주셔서 감사합니다!!