Bucket Place/IOS Swift

[Swift] Page View Controller

Cloud Travel 2015. 5. 4. 17:05



이 글은 본 블러그(destiny738,tistory.com)의 관리자인 Cloud Travel이 작성하였으며, 이후 이익창출에 사용될 수도 있는 글입니다. 본 글의 저작권 역시 본 블러그의 관리자가 가지고 있습니다. 본 글을 무단 복제, 사용, 인용, 변경 등은 사전에 블러그 관리자에게 문의 바랍니다. 이를 어길 경우 법적제제를 받거나 형사고발을 당할 수 있으니 주의하시기 바랍니다. Contact : create738@bucketplace.net / create738@naver.com


1. 여는글

 

 안녕하세요 Cloud Travel입니다. 대략적인 Swift 개발에 대한 개념이 잡히셨다면 바로 실전으로 몇개를 만들어 보도록 합시다. 오늘은 그 처음으로 Page View Controller를 사용하는 방법에 대해서 알아보도록 합시다. 




2. Page View Controller란?


 Page View Controller는 안드로이드의 ViewPager와 같은 역할을 하는 컨트롤러입니다. 사용자의 행동에 따라서 선택된 페이지가 순차적으로 보이게 됩니다. 처음 어플리케이션을 시작했을때 랜딩이미지나 설명들을 보여줄 때 사용하거나, 필요에 따라 사용자 "Swipe" 행동에 따라 다양한 페이지를 보여줄 때 사용합니다. 예를 들어, 아래의 사진은 저희 회사에서 제작한 오늘의집 안드로이드 버전의 첫 시작 페이지 입니다.


 
 처음 3개의 페이지에선 같은 정보가 나오고 마지막에서는 다른 정보가 나옵니다. 여기에 추가적으로 "오늘의집 시작하기" 버튼 그리고 "Page Indicator" 스와이프 행동을 받아도 고정된 위치에 계속 나오게 됩니다. 이와 유사한 페이지를 만드는 것을 한단계 한단계 따라가면서 만들어보도록 합시다.


 그럼, 각자 하나의 프로젝트를 생성해보록합시다.



3. 이미지 저장 및 Stroyboard 생성

  

 1) 이미지 저장


 본격적인 시작에 앞서서 각각 페이지에 보여줄 사진을 먼저 저장해 두도록 하자. 생성된 프로젝트에서 폴더모양의 "Images.xcassets" 파일을 더블클릭하자. 그러면 하나의 창이 뜨는데, 이는 이미지 셋을 저장해두는 공간으로 사용이 된다. 이 창에는 기본적으로 AppIcon이라는 파일이 하나 존재 할 것이다. 


 우리가 사용할 이미지들을 드레그해서 옮겨 보도록하자.

  

 


 이제 필요한 이미지를 갖추었으니 Story-Board 제작으로 들어가보자.



 2) Stroyboard 제작


ㄱ. Object Library에서 Page View Controller를 보드에 추가한다.


ㄴ. Page View Controller의 Stroyboard Id를 정해주자. 필자는 PageController라고 이름지었다.



ㄷ. 앞의 3개의 페이지를 보여줄 View Controller와 마지막 페이지를 보여줄 View Controller를 생성하자. 그리고 각각에 Storyboard Id를 

   정해주자. 필자는 각각을 ItemController와 LastItemController로 정하였다.


ㄹ. 각각의 컨트롤러에 대응하는 xxxController.swift 파일을 만들어서 연결하자. 필자는 각 컨트롤러에 대응하여 PageItemController.swift와 

    PageLastItemController.swift를 생성하고 연결하였다.  지금까지 잘 따라 왔다면 아래와 같은 상태가 됬을 것이다.






4. 각각의 페이지를 구성하고 컨트롤러에 연결해보자.


 스토리보드에 필요한 페이지를 구성하였으니 이제 디테일한 UI를 잡아보자. UI의 시작으로 기본 페이지(PageItemController)에 이미지 뷰와 라벨을 추가해보자. 필자가 사용한 컨스트레인트 값은 아래의 그림에 같이 설명되어 있다.


 각각의 UI에 대해서 컨트롤러에 연결을 해보자. 


 그리고 어떤 상황에서 각각의 값이 바뀌여서 나타나야 되는지 생각해보자. 페이지가 전환되었을때 전환된 뷰에서 이미지뷰의 이미지 명, 각 라벨에 들어갈 값을 전달해 줄 것이다. 전달되고 뷰가 위치를잡고 그려진 다음에 각각의 값에 맞는 값을 넣어 주면 된다. 즉, viewDidLoad 함수에 넣어주면 된다. 


 이제 추가적으로 마지막 페이지에 대한 뷰를 만들고 컨트롤러를 연결해보자.


 위에서는 각각 클릭했을 때 Action을 연결해주었지만, 페이지 이동을 하거나 팝업을 띄어서 처리하는 방법도 있을 것이다. 이에 대해선 자유롭게 하면 되기 때문에 세세하게 작성하진 않겠다.



5. Page View Controller 연결


 지금까지 각각의 페이지가 어떻게 보여줄지에 대해서만 작성했지 실제로 Page View Controller가 어떻게 등록되고 어떻게 사용되는지에 대해서는 아무런 코드가 없었다. 이에 대해서 지금부터 살펴보도록 하자. 


 Page View Controller를 사용하기 위해서는 UIPageViewControllerDataSource가 필요하다. 이 DataSource는 Page View Controller를 사용하기 위한 함수들이 정의 되어 있다.


1) pageViewController:viewControllerBeforeViewController 

 ~ 필수로 들어가야 하는 함수로 이전에 불리었던 페이지 컨트롤러를 반환해준다.

 ~ 쉽게 생각하면 왼쪽에서 오른쪽으로 스와이프 했을때 현재 보여지고 있는 화면에 대한 왼쪽 화면 정보를 반환해주는 함수이다.


2) pageViewController:viewControllerAfterViewController

 ~ 필수로 들어가야 하는 함수로 앞으로 불릴 페이지 컨트롤러를 반환해준다.

 ~ 이는 반대로 오른쪽에서 왼쪽으로 스와이프 했을때 현재 보여지고 있는 화면에 대한 오른쪽 화면 정보를 반환해주는 함수이다.


3) presentationCountForPageViewController

 ~ Page indicator를 위한 함수로 필수는 아니다. 보여지고 있는 페이지의 개수를 반환해준다.

 

4) presentationIndexForPageViewController

 ~ Page indicator를 변화시키기 위해서 필요한 함수로 현재 보여지고 있는 페이지 값을 반환해준다. 이 또한 필수는 아니다.


 ViewController에 UIPageViewControllerDataSource를 추가하고 각각의 필요한 함수를 작성 해보자. 


 첫번째로 필요한 함수는 각각에 해당하는 페이지 컨트롤러를 반환해주는 함수이다. 반환하려고 보니 우리가 갖고 있는 컨트롤러의 종류가 두가지로 나뉘게 된다. 이를 쉽게 처리하는 방법은 상속을 통해서 상위 변수를 반환해주는 방법이 있다. PageSuperItemController를 생성하고, PageListItemController와 PageItemController에 각각 상속해주자. 


 또한, 각각의 PageIndex 정보를 알아야 이전페이지와 다음 페이지에 대한 정보를 알 수 있을 것이다. 이를 위해서 PageSuperItemController에 index 값을 넣어주자.  
 


 이제 다시 돌아가서 페이지 컨트롤러를 반환해주는 함수를 작성해보자.

import UIKit


class ViewController: UIViewController, UIPageViewControllerDataSource {


    let countOfLandingPage = 3

    

    // 뷰페이지에 나올 데이터를 설정한다.

    private let landingImages = [ "landing1.jpg", "landing2.jpg", "landing3.jpg"]

    private let landingTitles = ["title1", "title2", "title3"]

    private let landingSlot1 = ["line1-1", "line2-1", "line3-1"]

    private let landingSlot2 = ["line2-1", "line2-2", "line2-3"]

    

    private func getItemController(itemIndex: Int) -> PageSuperItemController? {

        

        if itemIndex < countOfLandingPage {

            let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as! PageItemController

            pageItemController.index = itemIndex

            pageItemController.imageName = landingImages[itemIndex]

            pageItemController.landingSlot1 = landingSlot1[itemIndex]

            pageItemController.landingSlot2 = landingSlot2[itemIndex]

            

            return pageItemController

        } else if itemIndex == countOfLandingPage {

            let pageLastItemController = self.storyboard!.instantiateViewControllerWithIdentifier("LastItemController") as

PageLastItemController

            pageLastItemController.index = itemIndex

            return pageLastItemController

        }

        

        return nil

    }

    

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: 

UIViewController) -> UIViewController? {

        

        let itemController = viewController as! PageSuperItemController

        

        if itemController.index > 0 {

            return getItemController(itemController.index-1)

        }

        

        return nil

    }

    

    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: 

UIViewController) -> UIViewController? {

        

        let itemController = viewController as! PageSuperItemController

        

        if itemController.index + 1 < countOfLandingPage + 1 {

            return getItemController(itemController.index+1)

        }

        

        return nil

    }

}

  

 countOfLandingPage는 로그인 페이지를 제외하고 존재하는 페이지 수이다. getItemController 함수는 각각 페이지에 해당하는 Index값을 전달받아서 필요한 컨트롤러를 반환해준다 . 컨트롤러를 반환하기 전에는 컨트롤러에서 필요한 변수를 모두 설정해주고 반환한다.


 pageViewController(after, before)함수는 위에서 설명했듯이 자신의 index값을 기준으로 좌우가 존재하면 해당 뷰컨트롤러를 반환해준다.


 이제 PageViewController를 사용하기 위한 준비는 끝났다. 뷰에 해당 컨트롤러를 등록만 해주면 된다.


         let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as! UIPageViewController

        pageController.dataSource = self

        

        let firstController = getItemController(0)!

        let startingViewControllers: NSArray = [firstController]

        pageController.setViewControllers(startingViewControllers as [AnyObject], direction:                 UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)

        

        addChildViewController(pageController)

        self.view.addSubview(pageController.view)

        pageController.didMoveToParentViewController(self)



6. 마침글


 위의 설명에 대한 소스코드는 아래의 파일에 존재한다.


PageControllerReview.zip


 위 설명에서 하지 않은 Indicator를 원하는 위치에 놓는 법, 모든 페이지에 공통적으로 뜨는 버튼을 넣는 법에 대해서는 질문을 통해서만 답변해드리도록 하겠다.



7. 추가사항


 Index 값을 조정하여 페이지를 구현하고 싶다는 분을위해서 pageViewController 에서 CurrentIndex값을 조정하는 법을 올립니다.

 제가 개인적으로 생각해서 만든 거라 다른 곳에서 어떻게 쓰는지 궁금하네요.


import UIKit


class ViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {


    let countOfLandingPage = 3

    

    var isSwipeLeft = Bool()

    var currentIndex = 0

    

    func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) {

        

        NSLog("isSwiftLeft?: \(isSwipeLeft)")

        if isSwipeLeft {

            currentIndex++

        } else {

            currentIndex--

        }

        NSLog("current Index: \(currentIndex)")

    }

    

    // 뷰페이지에 나올 데이터를 설정한다.

    private let landingImages = [ "landing1.jpg", "landing2.jpg", "landing3.jpg"]

    private let landingTitles = ["title1", "title2", "title3"]

    private let landingSlot1 = ["line1-1", "line2-1", "line3-1"]

    private let landingSlot2 = ["line2-1", "line2-2", "line2-3"]

    

    private func getItemController(itemIndex: Int) -> PageSuperItemController? {

        

        if itemIndex < countOfLandingPage {

            let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as! PageItemController

            pageItemController.index = itemIndex

            pageItemController.imageName = landingImages[itemIndex]

            pageItemController.landingTitle = landingTitles[itemIndex]

            pageItemController.landingSlot1 = landingSlot1[itemIndex]

            pageItemController.landingSlot2 = landingSlot2[itemIndex]

            

            return pageItemController

        } else if itemIndex == countOfLandingPage {

            let pageLastItemController = self.storyboard!.instantiateViewControllerWithIdentifier("LastItemController") as! PageLastItemController

            pageLastItemController.index = itemIndex

            return pageLastItemController

        }

        

        return nil

    }

    

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {

        

        let itemController = viewController as! PageSuperItemController

        

        isSwipeLeft = false

        

        if itemController.index > 0 {

            return getItemController(itemController.index-1)

        }

        

        return nil

    }

    

    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {

        

        let itemController = viewController as! PageSuperItemController

        

        isSwipeLeft = true

        

        if itemController.index + 1 < countOfLandingPage + 1 {

            return getItemController(itemController.index+1)

        }

        

        return nil

    }

    

    override func viewDidLoad() {

        super.viewDidLoad()

        

        let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as! UIPageViewController

        pageController.dataSource = self

        pageController.delegate = self

        

        let firstController = getItemController(0)!

        let startingViewControllers: NSArray = [firstController]

        pageController.setViewControllers(startingViewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)

        

        addChildViewController(pageController)

        self.view.addSubview(pageController.view)

        pageController.didMoveToParentViewController(self)

    }

}