본문 바로가기

iOS

[Crecker, iOS] PageViewController (android : ViewPager)

저번주에 이어서, ViewPager 만들고 있습니다. 동시에 ActionSheet도 만들고 있는데,

그거는 다음주 주제로 하기로 했습니다...ㅎㅎ.

 

구조를 말씀드려 보겠습니다

안드로이드의 ViewPager를 구현해야 합니다.

보통 SegmentedControl을 Custom해서 구현하지만, 한번 다르게 구현해 보았습니다.

 

 1. All, Daily, Support는 각각 CollectionView 안의 CollectionViewCell 입니다.

 2. 그 밑의 탭들은 ContainerView입니다. 

 3. ContainerView는 UIPageViewController를 가지고 있습니다.

 

이 뷰를 만들어 보면서 여러개 방법이 있다는 것을 알게 되었는데, 그중에서 이 친구가 가장 저에게 적합했습니다.

이유 : ContainerView 안에도 TableView 또는 CollectionView가 존재

 

그럼 한번 구현해 보겠습니다.

1, ViewPager Tab ( Menu Tab ) 생성

쉽습니다. View의 화면에 맞춰 ViewController를 생성해 줍니다. 

 

제 경우, Menu Tab이 3개인데 왜 저 사이즈는 뭔가 어정쩡하냐면 

당연히 Cell Size는 코드로 줘야하기 때문입니다.

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        let width = view.frame.width / 3
        //        let height = width * (40 / 375)
        
        return CGSize(width: width, height: 44)
    }
 }
    

입니다. height는 디자이너는 40을 줬지만 기본 디폴트가 44 이상이여야 하니까 44를 줘봤습니다.

 

또한, Cell의 언더바는 선택된 곳에만 밑줄이 쳐져야 합니다. 디폴트는 첫번째 Cell입니다.

   func setFirstIndexIsSelected() {

        let selectedIndexPath = IndexPath(item: 0, section: 0)
        collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: .bottom)
        // 0번째 Index로
    }

setFirstIndexIsSelected()를 ViewDidLoad()에 넣습니다.

 

또한, 해당 셀에서는 밑줄이 있어야 하므로,

 

class MenuCVCell: UICollectionViewCell {
    
    @IBOutlet weak var menuLabel: UILabel!
    @IBOutlet weak var menuUnderBar: UIView!
    @IBOutlet weak var boundView: UIView!
    
    var collectionView: UICollectionView?

    override var isHighlighted: Bool {
        didSet {
            menuLabel.font = isSelected ? .boldSystemFont(ofSize: 17) : .boldSystemFont(ofSize: 15)
            
            UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseOut, animations: {
                self.menuUnderBar.layoutIfNeeded()
                self.menuUnderBar.alpha = self.isSelected ? 1 : 0
                
            }, completion: nil)
        }
    }
    override var isSelected: Bool {
        didSet {
            
            menuLabel.font = isSelected ? .boldSystemFont(ofSize: 17) : .boldSystemFont(ofSize: 15)
            
            
            UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
                self.menuUnderBar.layoutIfNeeded()
                self.menuUnderBar.alpha = self.isSelected ? 1 : 0
            }, completion: nil)
        }
    }
}

CollectionViewCell에 해당하는 Swift 파일을 이렇게 구현해 보았습니다. Alpha로 줬는데, 사실 보통 

스르륵 하며 미끌어져 가는 애니메이션을 구현하고 싶었던 것입니다. 그 애니메이션은 Constraint를 수정해서 주는 방식입니다. 

기회가 되면 설명해보도록 하겠습니다.

위 코드는 Cell이 선택됨에 따라 실행되는 코드입니다.

 

그리고  필요한 함수들을 구현하고 나면 Cell은 멋지게 구현이 완료가 됩니다.

 

2. ContainerView 생성

남는 빈 공간에 ContainerView를 그리면, 해당 사이즈와 일치하는 뷰가 오른쪽에 Segue로 연결이 되어 있습니다.

저는 오른쪽 뷰를 PageViewController로 사용할 것입니다. 그러기 위해

 

 

해당 인스펙터 창에서 Class를 선언해 줍니다. PageVC는 UIPageViewController를 상속받는 친구입니다.

코드는 아래와 같습니다.

 

class PageVC: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
    
    var identifiers: NSArray = ["AllVC", "DailyVC", "SupportVC"]
    
    lazy var VCArray: [UIViewController] = {
        return [self.VCInstance(name: "AllVC"),
                self.VCInstance(name: "DailyVC"),
                self.VCInstance(name: "SupportVC")]
    }()
    
    private func VCInstance(name: String) -> UIViewController {
        return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: name)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.dataSource = self
        self.delegate = self
        
        if let firstVC = VCArray.first{
            setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
        }
    }
    
    
    ~
    
}

 

1. 해당 뷰는 ContainerView로 나머지 3개의 뷰를 가지고 있어야 합니다. 3개 뷰의 Identifier는 위와 같이

AllVC, DailyVC, SupportVC 입니다. 

2. datasource와 delegate를 잊으시고 안된다고 하면 안됩니다. 저처럼.

3. 상속받는 프로토콜에도 저 친구들이 필수입니다. 잊지 마세요!

 

3. PageViewController ( 위에서 PageVC )

그럼 PageVC를 구현해 주어야 합니다. 책을 넘기는 애니메이션과 같이 슬라이딩 애니메이션을 지원합니다.

 

 저는 세 화면이 필요하니 세개를 그렸습니다.

각각의 뷰에 다른 화면을 구현해야하니, Cocoatouch file에서 각각에 해당하는 swift파일도 세개 만들어야 합니다.

제 경우에는 AllVC, DailyVC, SupportVC 입니다.

 

default로 Scroll되는 방식은 책장을 넘기는 방식인 PageCurl 방식입니다. 

UIPageViewController를 Storyboard로 그리면 해당 인스펙터 창에서 수정할 수 있지만 ( PageCurl -> Scroll)

우리는 지금 코드로 그리고 있으니 required init에서, 코드로 변경해주어야 합니다. 코드는 다음과 같습니다.

  required init?(coder aDecoder: NSCoder) {
        super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
        
    }

 

또한, 필수 구현해야 하는 함수는 아래와 같습니다.

이전 페이지와 다음 페이지를 띄워주는 함수입니다. 함수명도 그와 같습니다.

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

해당 코드와 같이 구현하고 나면, 세번째 스크롤 화면에서 첫번째 화면으로 스크롤, 또한

반대로 스크롤하게 된다면 첫번째 화면에서 마지막 화면인 세번째 화면으로도 스크롤할 수 있습니다.

 

그다음 VIewDidLoad()에도 아래와 같이 구현해 줍니다.

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

이상입니다. 아래에 PageVC 전체 코드를 첨부하니, 필요하신 분은 적재적소에 잘 사용해 주세요!

 

 

//
//  PageVC.swift
//  SemiViewPagerTest
//
//  Created by elesahich on 2020/04/03.
//  Copyright © 2020 elesahich. All rights reserved.
//

import UIKit

class PageVC: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
    
    var identifiers: NSArray = ["AllVC", "DailyVC", "SupportVC"]
    
    lazy var VCArray: [UIViewController] = {
        return [self.VCInstance(name: "AllVC"),
                self.VCInstance(name: "DailyVC"),
                self.VCInstance(name: "SupportVC")]
    }()
    
    private func VCInstance(name: String) -> UIViewController {
        return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: name)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.dataSource = self
        self.delegate = self
        
        if let firstVC = VCArray.first{
            setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
        }
    }
    

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        
        let currentPageIndex: Int = self.identifiers.index(of: pendingViewControllers[0].restorationIdentifier ?? 0)
//        NSLog("ViewController_willTransitionTo index = %d", currentPageIndex)
      
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        
        guard let viewControllerIndex = VCArray.firstIndex(of: viewController) else { return nil }
        
        let previousIndex = viewControllerIndex - 1
        //        print(previousIndex)
        guard previousIndex >= 0 else {
            return VCArray.last
        }
        
        guard VCArray.count > previousIndex else {
            return nil
        }
        
        return VCArray[previousIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        
        guard let viewControllerIndex = VCArray.firstIndex(of: viewController) else { return nil }
        
        let nextIndex = viewControllerIndex + 1
        guard nextIndex >= 0 else {
            return VCArray.first
        }
        
        guard VCArray.count > nextIndex else {
            print(nextIndex)
            return nil
        }
        
        return VCArray[nextIndex]
    }
    
    public func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return VCArray.count
    }
   
    public func presentationIndex(for pageViewController: UIPageViewController) -> Int {

        //        print(pageViewController.index)
        guard let firstViewController = viewControllers?.first else { return 0 }
        guard let firstViewControllerIndex = VCArray.firstIndex(of: firstViewController) else { return 0 }

//        print(firstViewControllerIndex)
        return firstViewControllerIndex
    }
    
    func getViewControllerAtIndex(index: NSInteger) -> UIViewController? {
//        NSLog("ViewController getViewControllerAtIndex index: %d", index)

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        
        if index == 0 {
            let AllVC = storyboard.instantiateViewController(withIdentifier: "AllVC") as! AllVC
            return AllVC
            
        } else if index == 1 {
            let DailyVC = storyboard.instantiateViewController(withIdentifier: "DailyVC") as! DailyVC
            return DailyVC
            
        } else if index == 2 {
            let SupportVC = storyboard.instantiateViewController(withIdentifier: "SupportVC") as! SupportVC
            return SupportVC
            
        } else {
            return nil
            
        }
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        for view in self.view.subviews {
            if view is UIScrollView {
                view.frame = UIScreen.main.bounds
            } else if view is UIPageControl {
                view.backgroundColor = UIColor.clear
            }
        }
    }
}

'iOS' 카테고리의 다른 글

[iOS] Calendar (1)  (0) 2020.04.25
[iOS] LinkPresentation  (0) 2020.04.18
[iOS, Crecker] ActionSheet,  (0) 2020.04.11
[Crecker,iOS] Swift ViewPager 구성  (4) 2020.03.28
[iOS] 8주차) Coredata  (0) 2020.03.21