본문 바로가기
Xcode/IOS

IOS) PageViewController로 화면전환을 만들어보자!

by 후르륵짭짭 2020. 8. 16.
728x90
반응형

안녕하세요 후르륵짭짭입니다!

이번에는 PageViewController를 이용해서 저렇게 화면 전환을 하는 것을 

만들어 보도록 하겠습니다.

 

** 스토리 보드에 설정하기 **

일단 이렇게 pageViewController를 만들고 그 밑에 ViewController를 만들어주세요

그리고 반드시 pageViewController와 ViewController들의 StoryBoard ID를 설정해주세요!!!

이렇게 새로운 Viewcontroller를 만들어주고 containerView와 stackview를 만들어주세요

그리고 containerView에 드래그 해서 pageView를 가르키게 하고 segue를 embed로 해주셔야합니다!!!

반드시 이름도 설정해주셔야합니다!!!

 

** Viewcontroller Class 생성하기 ** 

이제 이렇게 클래스를 만들어 주고 Green , Blue , Red ViewController를 만들어주세요!

 

** PageViewController 설정해주기 **

 -- 전체코드 -- 

더보기
class PageViewController: UIPageViewController , UIPageViewControllerDataSource , UIPageViewControllerDelegate{
    
    
    var completeHandler : ((Int)->())?
    
    let viewsList : [UIViewController] = {
        
        let storyBoard = UIStoryboard(name: "Main", bundle: nil)
       
        let vc1 = storyBoard.instantiateViewController(withIdentifier: "GreenViewController")
        let vc2 = storyBoard.instantiateViewController(withIdentifier: "RedViewController")
        let vc3 = storyBoard.instantiateViewController(withIdentifier: "BlueViewController")
        
        return [vc1, vc2, vc3]
        
    }()
    
    
    var currentIndex : Int {
        guard let vc = viewControllers?.first else { return 0 }
        return viewsList.firstIndex(of: vc) ?? 0
    }

    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = self
        self.delegate = self
        
        if let firstvc = viewsList.first {
            self.setViewControllers([firstvc], direction: .forward, animated: true, completion: nil)
        }
        
    }
    
    func setViewcontrollersFromIndex(index : Int){
          if index < 0 && index >= viewsList.count {return }
        self.setViewControllers([viewsList[index]], direction: .forward, animated: true, completion: nil)
        completeHandler?(currentIndex)
    }
    
     func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            
            completeHandler?(currentIndex)
        }
    }
    
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        
        guard let index = viewsList.firstIndex(of: viewController) else {return nil}
        
        let previousIndex = index - 1
        
        if previousIndex < 0 { return nil}
        
        return viewsList[previousIndex]
        
        
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        
        
        guard let index = viewsList.firstIndex(of: viewController) else {return nil}
              
        let nextIndex = index + 1
              
        if nextIndex == viewsList.count { return nil}
              
        return viewsList[nextIndex]
              
        
    }
    
}

이제 이코드를 하나 씩 분석하도록 하겠습니다.

PageViewController를 사용하기 위해서는  UIPageViewControllerDataSource , UIPageViewControllerDelegate 를 상속 받아야합니다.

 

우리는 PageViewController에 viewController를 넣을 것이기 때문에 

  let viewsList : [UIViewController] = {
        
        let storyBoard = UIStoryboard(name: "Main", bundle: nil)
       
        let vc1 = storyBoard.instantiateViewController(withIdentifier: "GreenViewController")
        let vc2 = storyBoard.instantiateViewController(withIdentifier: "RedViewController")
        let vc3 = storyBoard.instantiateViewController(withIdentifier: "BlueViewController")
        
        return [vc1, vc2, vc3]
        
    }()
    

이렇게 UIViewController 배열을 가진 viewsList 함수를 설정해줍니다!

여기서 중요한 것은 어떤 스토리 보드에 이름이 어떤 view인지 적어줘야합니다!

 

이제 viewDidLoad() 에 몇 가지 코드를 적어주도록 하겠습니다.

    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = self
        self.delegate = self
        
        if let firstvc = viewsList.first {
            self.setViewControllers([firstvc], direction: .forward, animated: true, completion: nil)
        }
        
    }

이렇게 우리가 상속 받은 dataSource와 delegate를 self로 맺어주고

viewList.first(첫 번째 배열이 존재하는가 ?) 라면 

pageViewController에 있는 self.setViewControllers([firstvc], direction: .forward, animated: true, completion: nil) 를 넣어줍니다.

setViewControllers([보여질 뷰 위치 ], 방향 : 앞/뒤, animated: true, completion: nil) 이렇게 해주시면 됩니다!

이 때 setViewControllers가 배열인 이유는 한페이지에 여러개를 보여줄 수 있기 때문입니다!

 

이제 기본적으로 설정해줘야할 datasource 함수를 보도록 하겠습니다.

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        
        guard let index = viewsList.firstIndex(of: viewController) else {return nil}
        
        let previousIndex = index - 1
        
        if previousIndex < 0 { return nil}
        
        return viewsList[previousIndex]
        
        
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        
        
        guard let index = viewsList.firstIndex(of: viewController) else {return nil}
              
        let nextIndex = index + 1
              
        if nextIndex == viewsList.count { return nil}
              
        return viewsList[nextIndex]
              
        
    }

둘의 차이점은 before 냐 after냐 의 차이만 존재합니다.

이 함수는 뷰가 이전할 때 미리 이전 뷰와 앞의 뷰를 감지 해놓는 겁니다.

따라서 저렇게 해준 겁니다.

guard let index = viewsList.firstIndex(of: viewController) else {return nil}

viewList에서 가장 처음에 나오는 함수 매개변수 viewController의 위치를 반환해줍니다. 만약에 없다면 nil!

그래서 before 면 index가 0 보다 커야하고 after면 viewList.count 보다 크거나 같으면 안돼니깐 저렇게 해주고

viewList[다음 위치]를 반환해줍니다!

 

자!! 여기까지가 기본적인 pageViewContoller의 설정 입니다!

이제 현재 뷰의 위치를 알려주는 것을 만들 도록 하겠습니다.

     func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            
            completeHandler?(currentIndex)
        }
    }

이것 함수는 페이지 이동이 끝나면 호출 되는 함수 입니다. (즉, 스크롤 해서 마쳤을 때 ? 아니면 안 마쳤을 때)

completed를 보면 타입이 Bool 입니다. 따라서 다 끝났을 때, completeHandler 함수를 호출해도록 해줬습니다.

currentIndex는 stored 프로퍼티로 설정 해줬습니다.

    var currentIndex : Int {
        guard let vc = viewControllers?.first else { return 0 }
        return viewsList.firstIndex(of: vc) ?? 0
    }

 

** MainViewController를 설정해주자 **

더보기
class MainViewController: UIViewController {
    @IBOutlet weak var firstBtn: UIButton!
    @IBOutlet weak var secondBtn: UIButton!
    @IBOutlet weak var thirdBtn: UIButton!
    
    var btnLists : [UIButton] = []
    
    var currentIndex : Int = 0 {
        didSet{
            changeBtnColor()
            print(currentIndex)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setBtnList()
        // Do any additional setup after loading the view.
    }
    
    func setBtnList(){
        firstBtn.tintColor = .orange
        btnLists.append(firstBtn)
        btnLists.append(secondBtn)
        btnLists.append(thirdBtn)
        
    }
    
    func changeBtnColor(){
        
        for (index, element) in btnLists.enumerated(){
            
            if index == currentIndex{
                element.tintColor = .orange
            }
            else{
                element.tintColor = .systemBlue
            }
            
        }
        
    }
    
    
    var pageViewController : PageViewController!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        if segue.identifier == "PageViewController" {
            print("Connected")
            guard let vc = segue.destination as? PageViewController else {return}
            pageViewController = vc
            
            pageViewController.completeHandler = { (result) in
                self.currentIndex = result
            }
            
        }
        
    }
    

    @IBAction func firstBtn(_ sender: Any) {
    
        pageViewController.setViewcontrollersFromIndex(index: 0)
    }
    @IBAction func secondeBtn(_ sender: Any) {
        pageViewController.setViewcontrollersFromIndex(index: 1)
    }
    @IBAction func thirdBtn(_ sender: Any) {
        pageViewController.setViewcontrollersFromIndex(index: 2)
    }
}

자,,,,, 여기서 부터 제가 하루종일 고생 했습니다.

PageViewController에서 페이지가 바뀌면 이 정보를 MainViewController에 전달 하고 싶은데,,, 어떻게 전달 해야할 지 몰랐습니다.

물론 Notification을 이용하거나 새롭게 instantiateViewController를 해주면 되지만,,, 너무 지저분 해 보였습니다.

그래서 어떻게 하지,,,, 고민을 많이 했는데,,, 

PageViewController는 ContainerView 하부에 존재합니다. 

그리고 우리는 이것을 segue로 전달 했습니다. 따라서 segue로 내용을 보내야합니다 ㅠㅠ (stackoverflow.com/questions/47411431/how-can-i-access-data-from-a-container-view-from-the-parent-view-controller-in-s)

    var currentIndex : Int = 0 {
        didSet{
            changeBtnColor()
            print(currentIndex)
        }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        if segue.identifier == "PageViewController" {
            print("Connected")
            guard let vc = segue.destination as? PageViewController else {return}
            pageViewController = vc
            
            pageViewController.completeHandler = { (result) in
                self.currentIndex = result
            }
            
        }
        
    }

prepare는 우리가 데이터를 전달 할 때 사용합니다.

prepare는 viewDidLoad()에서 미리 한번 호출 됩니다.

그래서 데이터 전송이 이뤄지기 전에 미리 대기를 하고 있다가 매번 데이터 전송이 이뤄지면 호출 되는 함수 입니다.

이렇게 한번 호출 될 때 completeHandler 클로저의 결과 값을 받아 오도록 했습니다. 

(여기에 자세한 글이 있습니다. learnappmaking.com/pass-data-between-view-controllers-swift-how-to/ )

그래서 PageViewContoller에 아래와 같이 Closure 변수를 만들어 준 겁니다.

var completeHandler : ((Int)->())?

(참고로 ? 인 이유는 일단 전역으로 변수를 설정하는 것이기 때문에  초기값을 prepare에서 설정해주게 되므로 nil 값이 존재 할 수 있도록 해줘야합니다.  그래서  completeHandler?(currentIndex) 에 ? 이 있는 이유도 completeHandler의 값이 존재하면 함수를 호출하고 없으면 호출 하지 않도록 한겁니다.)

 

이제 버튼을 누르면 페이지 변경을 시켜야하는데, 여기서 고생을 또 많이 했습니다.

MainViewController에서 Container에 존재하는 PageViewContoller을 다루기 위해서는 segue 형식으로 접속해야합니다....

이것을 알지 못해 별의 별 방법을 많은 시행착오를 격었습니다. (예를들어 instantiateViewController 사용했습니다. ㅠㅠ)

    var pageViewController : PageViewController!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        if segue.identifier == "PageViewController" {
            print("Connected")
            guard let vc = segue.destination as? PageViewController else {return}
            pageViewController = vc
            
            pageViewController.completeHandler = { (result) in
                self.currentIndex = result
            }
            
        }
        
    }
    

    @IBAction func firstBtn(_ sender: Any) {
    
        pageViewController.setViewcontrollersFromIndex(index: 0)
    }
    @IBAction func secondeBtn(_ sender: Any) {
        pageViewController.setViewcontrollersFromIndex(index: 1)
    }
    @IBAction func thirdBtn(_ sender: Any) {
        pageViewController.setViewcontrollersFromIndex(index: 2)
    }

위에서 코드를 보면 변수 pageViewController에 vc 값을 넘겨주게 됩니다.

이렇게 하면 segue로 넘어온 PageViewController에 해당하는 주소값이 pageViewController 변수들어 오게 되어 MainViewController에서 PageViewController에 접근할 수 있게 됩니다.

그리고 이제 PageViewController에서 setViewControllerFromIndex 함수를 만들어주시고 위에서 설명한 

setViewController 함수를 이용하면 버튼으로 화면 전환이 이뤄어 집니다.

    func setViewcontrollersFromIndex(index : Int){
          if index < 0 && index >= viewsList.count {return }
        self.setViewControllers([viewsList[index]], direction: .forward, animated: true, completion: nil)
        completeHandler?(currentIndex)
    }

 

** 버튼 색 변경하기 **

    @IBOutlet weak var firstBtn: UIButton!
    @IBOutlet weak var secondBtn: UIButton!
    @IBOutlet weak var thirdBtn: UIButton!
    
    var btnLists : [UIButton] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setBtnList()
        // Do any additional setup after loading the view.
    }
    
    func setBtnList(){
        firstBtn.tintColor = .orange
        btnLists.append(firstBtn)
        btnLists.append(secondBtn)
        btnLists.append(thirdBtn)
        
    }

위와 같이 코드를 작성해줍니다. ( setBtnList()는 초기 설정 입니다. )

그리고 위에서 currentIndex 값 변경을 설명했습니다.

이렇게 currentIndex의 값이 변경 된 시점에 chageBtnColor 함수를 호출하게 되고

아래와 같이 변경 해줬습니다.

    var currentIndex : Int = 0 {
        didSet{
            changeBtnColor()
            print(currentIndex)
        }
    }
    
    func changeBtnColor(){
        
        for (index, element) in btnLists.enumerated(){
            
            if index == currentIndex{
                element.tintColor = .orange
            }
            else{
                element.tintColor = .systemBlue
            }
            
        }
        
    }
    

 

부족한 점이 많이 있지만,,,, 

긴 글 읽어주셔서 감사합니다!

모두 즐코코하세요!!

참고 :

www.youtube.com/watch?v=u8PFHMEkSAw

stackoverflow.com/questions/47572204/move-pageviewcontroller-with-button

 

move pageViewController with button

Basically I'd like to move my PageViewController not only with swipe but also with buttons. @IBAction func nextButtonAction(_ sender: Any){ } My PageViewController looks like in this guide Wha...

stackoverflow.com

 

728x90
반응형

댓글