PageViewController로 UI 구성하기

6 답변 글타래를 보이고 있습니다
  • 글쓴이
    • lidium
      참가자
      • 글작성 : 9
      • 답글작성 : 8
      770 포인트


      과 같은 뷰를 구현해 보았습니다. 유명한 라이브러리로는 https://github.com/xmartlabs/XLPagerTabStrip – XLPagerTabStrip과 같은것이 있습니다.
      안드로이드는 ViewPager라는 내장 컴포넌트가 있지만 iOS에서는 해당 뷰를 구현하기 위해서는 직접 만들거나 라이브러리를 사용해야 합니다.
      보통 SegmentedControl을 Custom해서 구현하지만, 한번 다르게 구현해 보았습니다. 이유는  각 MenuBar 밑에 들어가는 밑줄 등
      커스텀 요소가 있었기 때문입니다.

      All, Daily, Support는 각각 CollectionViewCell 입니다
      각 Cell에 해당하는 View가 그려질 공간은 ContainerView입니다.
      ContainerView는 UIPageViewController를 가지고 있습니다.
      그럼 한번 구현해 보겠습니다.

      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
              return CGSize(width: width, height: 44)
          }
      
      

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

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

      와 같이 인터페이스를 구현해 주고, ViewDidLoad()에서 실행시켜 줍니다.
      선택된 셀은 밑줄이 있어야 하므로,

      import UIKit
      
      protocol PageIndexDelegate {
          func SelectMenuItem(pageIndex: Int)
      }
      class MenuCVCell: UICollectionViewCell {
      
          var delegate: PageIndexDelegate?
      
          @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 = isHighlighted ? .boldSystemFont(ofSize: 25) : .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 파일을 isSelected() 와 isHighlited()로 구현해 보았습니다.
      밑줄에 해당하는 부분이 선택되는 로직을 Alpha로 구현했는데,
      사실 보통 사용하는 스르륵 하며 미끌어져 가는 애니메이션을 구현하고 싶었던 것입니다.
      그 애니메이션은 Constraint를 수정해서 주는 방식입니다.
      기회가 되면 설명해보도록 하겠습니다.

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

      2. ContainerView 생성


      남는 빈 공간에 ContainerView를 그리면, 해당 사이즈와 일치하는 뷰가 오른쪽에 Segue로 연결이 됩니다.
      저는 오른쪽 뷰를 PageViewController로 사용할 것입니다. 그러기 위해
      해당 인스펙터 창에서 Class를 선언해 줍니다. PageVC는 UIPageViewController를 상속받는 친구입니다.
      코드는 아래와 같습니다.

      class PageVC: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
      
          var pageDelegate: PageIndexDelegate?
          var pendingPage: Int?
          let identifiers: NSArray = ["AllVC", "DailyVC", "SupportVC"]
      
          lazy var VCArray: [UIViewController] = {
              return [self.VCInstance(name: "AllVC"),
                      self.VCInstance(name: "DailyVC"),
                      self.VCInstance(name: "SupportVC")]
          }()
      
          required init?(coder aDecoder: NSCoder) {
              super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
      
          }
      
          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를 구현해 주어야 합니다. 책을 넘기는 애니메이션과 같이 슬라이딩 애니메이션을 지원합니다.
      저는 세 화면이 필요하니 세개를 그렸습니다.
      각각의 뷰에 다른 화면을 구현해야하니, 각각에 해당하는 ViewController 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]
              }
          }
      

      해당 코드와 같이 구현하고 나면, 마지막 화면인 세번째 스크롤 화면에서 첫번째 화면으로 스크롤,
      반대로 스크롤하게 된다면 첫번째 화면에서 마지막 화면인 세번째 화면으로도 스크롤할 수 있습니다.
      다음시간에는 Delegate를 통해 CollectionViewCell과 PageViewController의
      Index를 맞추는 글을 써 보겠습니다.

      Todo : Custom ActionSheet

      • 이 게시글은 lidium에 의해 2 years, 2 months 전에 수정됐습니다.
      • 이 게시글은 lidium에 의해 2 years, 2 months 전에 수정됐습니다.
      avatar
    • 야곰
      키 마스터
      • 글작성 : 37
      • 답글작성 : 554
      20,170 포인트

      Segmented Control을 사용해도 밑줄은 그을 수 있었을 것 같아요(오히려 좀 더 쉽게…?)
      🙂

      avatar
    • larger93
      참가자
      • 글작성 : 0
      • 답글작성 : 3
      1,200 포인트

      좋은 글 감사합니다!
      혹시 CollectionViewCell 와 PageViewController를 어떤 식으로 연결하셨는지 알 수 있을까요?

      • 이 답변은 larger93에 의해 1 year, 10 months 전에 수정됐습니다.
      avatar
      • 야곰
        키 마스터
        • 글작성 : 37
        • 답글작성 : 554
        20,170 포인트

        제가 이해한 바가 확실한지는 모르겠지만, 제가 이해한 바 대로 설명드리면 Delegation 디자인 패턴을 활용한 것 같습니다.

        @elesahich
        맞을까요?

        avatar
    • lidium
      참가자
      • 글작성 : 9
      • 답글작성 : 8
      770 포인트

      넵 맞습니다! ^_^

       

    • larger93
      참가자
      • 글작성 : 0
      • 답글작성 : 3
      1,200 포인트

      답변 감사합니다 🙂

      저도 delegate 패턴을 이용해서 구현을 시도했으나

      menuBar의 클릭을 인식하고 이에 맞게 pageViewController을 움직이게 하는 것에 실패하였습니다.

      setViewControllers() 를 통해서 pageViewController 내의 viewController를 움직일 수는 있지만 이는 앞/뒤로만  움직일 수 있어 3개 이상의 menu가 있을때 jump를 하지 못하는 문제가 있었습니다.

      혹시 delegate 패턴을 활용해서 MenuBar 선택에 따른 알맞은 Page로 이동시킬때

      어떤 메서드 혹은 어떤 방법을 사용하셨는지 궁금합니다!

    • 야곰
      키 마스터
      • 글작성 : 37
      • 답글작성 : 554
      20,170 포인트

      https://stackoverflow.com/a/7240719
      이 글을 보면 setViewControllers 메서드로도 점프가 가능할 것 같아요 🙂

    • larger93
      참가자
      • 글작성 : 0
      • 답글작성 : 3
      1,200 포인트

      감사합니다 🙂 덕분에 잘 구현했습니다~!

6 답변 글타래를 보이고 있습니다
  • 답변은 로그인 후 가능합니다.

logo landscape small

사업자번호 : 260-27-00477
통신판매업 신고번호 : 제 2020-충북청주-0663 호
고객센터 : 카카오톡채널 @yagom