Things take time

[SWIFT] UIPageViewController 사용하기 (하나의 뷰 컨트롤러) 본문

iOS (기능)

[SWIFT] UIPageViewController 사용하기 (하나의 뷰 컨트롤러)

겸손할 겸 2018. 12. 31. 13:11

[UIPageViewController]


안드로이드에 있는 뷰 페이저(View Pager)의 대응 기능이다.

여러 개의 뷰를 페이징 처럼 넘기면서 볼 때 사용한다.


옛날 포스팅에는 여러개의 뷰 컨트롤러를 두고 해당 뷰 컨트롤러를 페이징하는 기본적인 페이지 뷰 컨트롤러를 사용했지만, 이번에 사용할 것은 하나의 뷰 컨트롤러를 재활용해서 사용할 것이다.


기본 완성화면은 다음과 같다.




여러 개의 이미지 들을 페이지로 넘기면서 각 이미지들을 다운받을 수 있도록 한다.


[준비물]


필요한 뷰 컨트롤러는 3개다. UIPageViewController 프로토콜을 상속받을 컨트롤러, 실제 재활용될 뷰 컨트롤러, 그리고 껍데기로 가져다 쓸 UIPageViewController

직접 뷰 컨트롤러를 PageViewController를 컴포넌트로 사용해도 되는데, 나같은 경우에는 위의 화면처럼 다운로드 버튼, X버튼을 넣을거라 일반 뷰 컨트롤러를 사용했다. 물론 페이저 뷰 컨트롤러 베이스로 직접 UI를 코드로 넣어도 되지만 귀찮으니까!



기본 뷰 컨트롤러 1 (위 아래 이미지 뷰를 제외한 가운데 뷰를 페이지 뷰 컨트롤러를 상속받게 할 것), PageMainViewController.swift 파일로 연결된다.



기본 뷰 컨트롤러2 각 페이지 뷰 컨트롤러의 기본이 되는 뷰 컨트롤러, 이미지 뷰 하나를 오토레이아웃으로 화면을 다 잡는다.

스토리보드 네임은 PageContentViewController 및 스위프트 파일 명도 동일하다.



그리고 기본 UIPageViewController, 스토리보드 네임은 PageViewController


[소스 코드]


1. PageContentViewController

import UIKit

class PageContentViewController: UIViewController {
    @IBOutlet weak var ivImage: UIImageView!
    
    var index: Int!
    var imgStr: String!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.ivImage.image = UIImage(named: imgStr)
    }
}

재활용할 기본 뷰, 이미지 뷰 하나와 index를 넣는다. index는 현재 보여줄 이미지들과 매치시킬 때 사용한다.


2. PageMainViewController

import UIKit

class PageMainViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    
    @IBOutlet weak var vMain: UIView!
    @IBOutlet weak var ivClose: UIImageView!
    @IBOutlet weak var ivDown: UIImageView!
    
    var pageVC: UIPageViewController!
    var pageImages: Array!
    var currentIdx:Int = 0
    var chooseIdx = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.pageImages = ["img01" , "img02" , "img03" , "img04" , "img05"]
        
        self.pageVC = UIStoryboard(name: "Popup", bundle: nil).instantiateViewController(withIdentifier: "PageViewController") as? UIPageViewController
        self.pageVC.dataSource = self
        self.pageVC.delegate = self
        
        let startVC = self.viewControllerAtIndex(index: chooseIdx) as PageContentViewController
        let viewControllers = NSArray(object: startVC)
        
        self.pageVC.setViewControllers(viewControllers as? [UIViewController] , direction: .forward, animated: true, completion: nil)
        self.addChild(self.pageVC)
        self.vMain.addSubview(self.pageVC.view)
        
        // AutoLayout
        self.pageVC.view.translatesAutoresizingMaskIntoConstraints = false
        self.vMain.addConstraint(NSLayoutConstraint(item: self.pageVC.view, attribute: .top, relatedBy: .equal, toItem: self.vMain, attribute: .top, multiplier: 1, constant: 0))
        self.vMain.addConstraint(NSLayoutConstraint(item: self.pageVC.view, attribute: .left, relatedBy: .equal, toItem: self.vMain, attribute: .left, multiplier: 1, constant: 0))
        self.vMain.addConstraint(NSLayoutConstraint(item: self.pageVC.view, attribute: .bottom, relatedBy: .equal, toItem: self.vMain, attribute: .bottom, multiplier: 1, constant: 0))
        self.vMain.addConstraint(NSLayoutConstraint(item: self.pageVC.view, attribute: .right, relatedBy: .equal, toItem: self.vMain, attribute: .right, multiplier: 1, constant: 0))
        
        ivClose.isUserInteractionEnabled = true
        ivClose.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ivCloseClicked)))
        
        ivDown.isUserInteractionEnabled = true
        ivDown.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ivDownClicked)))
    }
    
    @objc func ivCloseClicked(){
        self.dismiss(animated: true, completion: nil)
    }
    
    @objc func ivDownClicked(){
        // 다운로드 로직
    }
    
    func viewControllerAtIndex (index : Int) -> PageContentViewController {
        guard let vc = UIStoryboard(name: "Popup", bundle: nil).instantiateViewController(withIdentifier: "PageContentViewController") as? PageContentViewController else { return PageContentViewController() }
        vc.index = index
        vc.imgStr = self.pageImages[index]
        return vc
    }
    
    // 현재 페이지 로드가 끝났을 때
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            if let currentViewController = pageVC.viewControllers![0] as? ImageContentViewController {
                currentIdx = currentViewController.index
            }
        }
    }
    
    // 현재 페이지 뷰의 이전 뷰를 미리 로드
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let vc = viewController as! PageContentViewController
        var index = vc.index as Int
        
        if( index == 0 || index == NSNotFound) {
            return nil
        }
        index -= 1
        return self.viewControllerAtIndex(index: index)
    }
    
    // 현재 페이지 뷰의 다음 뷰를 미리 로드
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let vc = viewController as! PageContentViewController
        var index = vc.index as Int
        
        if( index == NSNotFound) {
            return nil
        }
        index += 1
        
        if(index == self.pageImages.count){
            return nil
        }
        return self.viewControllerAtIndex(index: index)
    }
    
    // 인디케이터 개수
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return self.pageImages.count
    }
    
    // 인디케이터 초기 선택 값
    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        return chooseIdx
    }
}

주석만 봐도 이해하기 쉬울 것이다.

일단 pageVC라는 변수에 페이지뷰 컨트롤러를 가져와서 넣고, dataSource를 연결한다. 기본적인 사용에는 delegate가 필요 없다.


그리고 viewControllerAtIndex라는 함수에 초기 값을 넣어주고 이미지를 넣는다.

setViewControllers를 통해 페이지 뷰 안에 들어가는 실제 컨텐츠 뷰를 세팅한다. (페이지 뷰 컨트롤러의 기본 세팅 문법)


그리고 UI, Autolayout설정으로 viewDidLoad() 마무리.


pageViewController(didFinishAnimation)이란 함수를 통해 현재 몇 번째 이미지가 로드 되었는지를 알 수 있다.(하단 인디케이터 위치), delegate소속함수


index 세팅은 소스를 보면 이해가 될 것이고, 마지막 presentationIndex를 통해 초기 선택 값을 설정할 수 있다. 이 예제에서는 이미지를 5개로 고정시켰고, 기본 img01부터 로드되도록 했기 때문에 필요는 없다.


그러나 실제 현업 소스에서는 이미지 리스트에서 한 이미지를 선택하고, 그 이미지가 전체 이미지들 사이에서 몇 번째인지, 기준 값을 세팅해줘야하는 로직이 필요하기 때문에 따로 넣은 소스다.


pageImages도 이전 뷰 에서 넘겨받아야 할 것이고, 실제 PageContentView도 이미지 뷰 하나만 사용할 수도 있지만, 이미지가 실제 저장되지 않았다면 다운로드 받도록 하는 UI, ProgressBar등 여러 로직들도 포함되어야 한다.


이 예제는 말 그대로 기본만을 담고 있기 때문에, 이것을 바탕으로 응용해서 사용하면 된다.