티스토리 뷰

개발 블로그/iOS

PageViewController 사용해보기(1탄)

개발자 아라찌 2020. 8. 12. 11:37
728x90

PageViewController란?

"각 페이지가 Child View Controller에 의해 관리되는 컨텐츠 페이지 간의 탐색을 관리하는 컨테이너 뷰 컨트롤러" 라네요.. 어떻게 생긴 녀석이냐면..

미리보기

이렇게 생긴 녀석입니다.. 마치 페이지를 슥슥 넘기듯 제스처를 통해서 뷰를 이동하는 뷰 컨트롤러죠.

그럼 우선 PageViewController가 어떤녀석인지 애플 개발자 문서를 통해 알아본 다음에, 어떻게 사용하는지 코드를 통해 알아보도록 하겠습니다.

init

3개의 파라미터값이 있어요.

  • style: 페이지뷰 전환 스타일로 scroll과 pageCurl 방법이 있네요.
  • navigationOrientation: 페이지뷰 탐색 방향으로 horizontal, vertical이 있네요.
  • options: 추가적인 옵션으로 페이지 사이의 간격이라던지 spineLocation이 뭔지는 잘 모르겠지만.. 이 값을 설정할 수 있네요.. 참고로 spineLocation값은 pageCurl 스타일일 경우에만 설정 가능하다네요. 옵셔널이기 때문에 우선 넘어가는걸로..
// code로 사용할때 예시
// 1. init
let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)

// pageViewController 크기 설정
// 2.1 이런식으로 전체 뷰 크기랑 같게 정해줘도 되고..
pageViewController.view.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)

// 2.2 특정 뷰에서만 하고싶을 때에는 이런식으로 정의해주면 됩니다.
let mainView = UIView()
pageViewController.view.frame = mainView.frame

// 3.1 뷰 추가 
view.addSubview(pageViewController.view)

// 3.2
mainView.addSubview(pageViewController.view)


// 4. 필요한 delegate, dataSource추가
addChild(pageViewController)
pageViewController.dataSource = self // 뷰 넘기는 메소드가 있어서 정의 해줘야 해요

setViewControllers

PageViewController에서 보여줄 ViewController들을 설정해주는 메소드 입니다. 뭔가 많죠..? ㅋ...

총 4개의 파라미터값이 있네요.

  • viewControllers: 표시할 뷰 컨트롤러 array
  • direction: 탐색 방향설정
  • animated: 애니메이션으로 표시여부
  • completion: 완료 핸들러

코드로 예를 들어보면

...
let viewController = [FirstViewController()] // 이부분에 왜 하나만 들어갈까요?
        pageViewController.setViewControllers(viewController, direction: .reverse, animated: true, completion: nil)

이런식으로..? 그런데 이렇게만 하면 작동이 안될꺼에요. 왜냐하면 페이지를 좌우로 넘겼을때 작동하는 dataSource 추가를 안해줬기 때문이죵

UIPageViewController의 Delegate, DataSource

  • DataSource

    PageViewController의 제스처 기반 탐색을 사용할려면 setViewControllers()와 함께 DataSource를 사용해야 한다고 하네요. DataSource를 사용하려면 필수로 작성해줘야 하는 녀석들이 있어요.

첫번째 메소드는 이전 뷰를 설정해줄수 있는(반환하는) 녀석이고 두번째 메소드는 다음 뷰를 설정해줄 수 있는(반환하는) 녀석이네요. 예를 들어서 PageViewController의 다음뷰를 특정뷰로 지정하고 싶다면 조건을 준 다음 원하는 뷰를 return 해주면 될 것 같아요.

  // MARK: - Extension
  extension ViewController: UIPageViewControllerDataSource {
      func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
          print("before")

          if viewController as? FirstViewController != nil {
              return nil
          }

          return FirstViewController()
      }

      func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
          print("after")

          if viewController as? SecondViewController != nil {
              return nil
          }

          return SecondViewController()
      }
  }
  • Delegate

    UIPageViewDelegate를 사용하면 사용자가 새 페이지로 전환(탐색)할 때 다양한 트리거가 되는 메소드를 사용할 수 있다고 하네요. 다양한 트리거가 있지만 전체적인 내용은 개발자 문서를 참고해주시고 이번엔 주로 사용하는 2가지 메소드에 대해 알아볼게요.

  • func pageViewController(UIPageViewController, willTransitionTo: [UIViewController])

    • 제스처가 시작되기 전에 호출
  • func pageViewController(UIPageViewController, didFinishAnimating: Bool, previousViewControllers: [UIViewController], transitionCompleted: Bool)

    • 제스처가 완료된 후 호출

    제스처가 끝나고 추가로 애니메이션 효과나 텍스트 강조를 해주고 싶을 때 이곳에서 작성해주시면 될 것 같아요. 코드로 예를 들어보면

    // MARK: - Extension
    extension PageViewController: UIPageViewControllerDelegate {
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed {
                // 애니메이션 or 버튼 강조효과 or 변경 후 적용할 부분들 적용
              // 저는 pageControl 의 currentPage값 변경을 위해 넣어봤습니당
              if let currentViewController = pageViewController.viewControllers?[0] as? FirstViewController {
                    pageControl.currentPage = currentViewController.index
                } else if let currentViewController = pageViewController.viewControllers?[0] as? SecondViewController {
                    pageControl.currentPage = currentViewController.index
                }
            }
        }
    }

    이런식으로 작성해주시면 될 것 같아요.

PageViewController 구현해보기

준비물

  1. 보여줄 여러 ViewController들
  2. pageViewController 구현

1. 보여줄 여러 ViewController들

저는 2개의 뷰를 만들어 볼게요. 구분하기 쉽도록 가운데에 label을 하나씩 놔주고 pageControl에서 몇번째 뷰인지 구분할 수 있도록 index값을 Int형으로 선언해 볼게요

//  FirstViewController.swift
import UIKit

class FirstViewController: UIViewController {

    // MARK: - Property
    weak var label: UILabel?
    let index = 0

    // MARK: - View Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .yellow
        self.view = view

        let label = UILabel()
        self.label = label
        label.text = "첫번째 뷰"
        label.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(label)
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
        ])
    }
}

2번째 뷰도 똑같이 만들어 주시면 될 것 같아용

2. pageViewController 구현

필요한 부분은 따로 initPageViewController func을 만들어서 구현을 해줬습니다. 주석을 확인해주세용

//  ViewController.swift
import UIKit

class ViewController: UIViewController {
          // 우선 뷰를 만들어 줘야 겠죠? 뷰를 정의해주고 constraint를 위에서 잡아줍시다.
  ...
    func initPageViewController() {
                // 1. pageViewController를 정의해줍니다. init 부분에서 한 부분을 진행해주면 되요.
        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)

        // 2. pageViewController의 frame을 지정해줍시다. 저는 이번에 특정 뷰 부분만 페이징이 되도록 mainView를 따로 정의해서 넣어줄게요.
        pageViewController.view.frame = mainView.frame

        // 3. setViewControllers를 넣어주고, 뷰를 넣어주고.. delegate, dataSource를 추가해줍니다.
        let viewController = [FirstViewController()]
        pageViewController.setViewControllers(viewController, direction: .reverse, animated: true, completion: nil)

        mainView.addSubview(pageViewController.view)
        self.addChild(pageViewController)

        pageViewController.dataSource = self
        pageViewController.delegate = self
    }
}

// 페이징 제스처 이용시 DataSource 필요!
extension ViewController: UIPageViewControllerDataSource {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        print("before")

        if viewController as? FirstViewController != nil {
            return nil
        }

        return FirstViewController()
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        print("after")

        if viewController as? SecondViewController != nil {
            return nil
        }

        return SecondViewController()
    }
}

// 페이징 이벤트 시작 or 끝났을 때 이벤트 추가시 Delegate 필요!
extension ViewController: UIPageViewControllerDelegate {
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            if let currentViewController = pageViewController.viewControllers?[0] as? FirstViewController {
                pageControl.currentPage = currentViewController.index
            } else if let currentViewController = pageViewController.viewControllers?[0] as? SecondViewController {
                pageControl.currentPage = currentViewController.index
            }
        }
    }
}

정리해하면 페이지뷰 컨트롤러를 생성 후에, setViewController 정의해주고, 페이지뷰 컨트롤러 frame 지정해준 후에 뷰에 넣어준다.. 사용한 delegate, dataSource 추가해주고 구현.. 필수 메소드는 dataSource의 before와 after 메소드 정도 겠네용

PageViewController를 공부하면서..

처음 앱개발할때 만들어볼려 했던 UI가 네이버 메인 페이지였었는데, 처음엔 페이지뷰 컨트롤러인지도 몰랐지만 나중에 iOS 앱 개발 오픈톡방 사람분들에게 질문해서 알게되고 구현을 하고싶었었는데, 너무 어려워서 꼭 한번 정리를 해보고 싶었던 컨트롤러 였어요. 그리고 현재 협업 프로젝트를 진행하면서 페이지뷰컨트롤러를 사용하게 되었는데, 리팩토링 과정에서 저도 이 코드를 이해했어야 했고, 정리를 해보고 싶었던 컨트롤러라 이번기회에 개발자 문서를 보면서 정리를 해본 시간이었던 것 같아요. 저처럼 페이지 뷰 컨트롤러를 한번쯤 구현을 해보고 싶었던 분들에게 도움이 되셧으면 좋겠고.. 이 포스팅 뿐만 아니라 꼭!! 애플 개발자 문서까지 한번 확인해보시는걸 추천드려요.

프로젝트 완성본 repo

참고

추가로 혹시 iOS 앱 개발이 아직 미숙하다고 느끼시고 이 강의를 안들어 보신 분은 edWith - 부스트코스 iOS 강의를 꼭 들어보시는걸 추천드려요. 카카오톡 오픈톡방에 iOS 부스트코스 오픈톡방도 있으니 한번쯤 들어오셔서 swift 질문도 하고 정보도 공유하면 좋을 것 같아요.

728x90
댓글