TIL

64일차 TIL _ LargeTitle / 페이지 뷰 컨트롤러로 화면 구성

h_luz 2024. 5. 29. 02:13

 

네비게이션 바 Large Title & UIPageViewController

 

 

 

정리가 안된 상태에서 이것저것 해보다보니까 엄청 엉켜서 헤맸다..

 

정리하고, 차근차근해보니까 성공 :)

 

일단 UIPageViewController

 

UIPageViewController 

: 하위 뷰 컨트롤러가 각 페이지를 관리하는 콘텐츠 페이지 간의 탐색을 관리하는 컨테이너 뷰 컨트롤러

https://developer.apple.com/documentation/uikit/uipageviewcontroller

 

UIPageViewController | Apple Developer Documentation

A container view controller that manages navigation between pages of content, where a subview controller manages each page.

developer.apple.com

 

탭바 없이 진행해보자는 팀원들의 의견을 받아서 !

양 옆으로 스크롤해서 이동하고 페이지 컨트롤러를 밑에 작게 만들어서 페이지 이동을 하게 하기로 했다 ㆅㆅ 

 

찾아봤더니 Scroll 뷰로 직접 만드는 방법이랑 페이지 컨트롤러 사용해서 만드는 방법이 있는데,

내가 만들 앱은 탐색페이지 / 내 페이지 / 설정 페이지로 나눌 것이기 때문에  각 뷰컨트롤러를 페이지에 넣을 수 있게 분리되어있는 구조가 좋을 것 같다고 생각하기도 했고, 스크롤뷰로 만들면 그 페이지마다 스크롤 크기랑 이것저것 맞춰줘야할 게 많다고 해서.. 그냥 기본적으로 제공되는 PageViewController를 사용하기로 했다 !

 

구조는 PageViewController 안에 ExploreViewController, MyTripsViewController, MyPageViewController가 들어가는 방식!

 

func setupPVC() {
    //UIPageViewController 설정
    pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    pageViewController.dataSource = self
    pageViewController.delegate = self
      
    //첫번째 페이지 뷰컨 설정
    if let initialViewController = viewControllerAtIndex(index: 0) {
        pageViewController.setViewControllers([initialViewController], direction: .forward, animated: true, completion: nil)
    }
        
    // PageViewController를 부모 뷰에 추가
    addChild(pageViewController)
    view.addSubview(pageViewController.view)
    pageViewController.didMove(toParent: self)
        
    if let scrollView = pageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first {
        scrollView.panGestureRecognizer.addTarget(self, action: #selector(handlePanGesture(_:)))
    }
        
    pageViewController.view.snp.makeConstraints {
        $0.edges.equalToSuperview()
    }
}

여기 설정에서 transitionStyle을 변경해주면 넘기는 스타일도 변경할 수 있어서 신기했다 !

이 페이지 컨트롤러를 사용하면 밑에 동그라미로 자기 페이지 위치를 알려주는 그런 것들도 기본으로 제공해서 사용할 수 있다고 한다 !

여튼 이렇게 페이지 컨트롤러를 세팅해주고,

 

func viewControllerAtIndex(index: Int) -> UINavigationController? {
    guard index >= 0 && index < pageContent.count else {
        return nil
    }
    let contentViewController: UIViewController
    if index == 0 {
        contentViewController = ExploreViewController()
    } else if index == 1 {
        contentViewController = MyTripsViewController()
    } else {
        contentViewController = MyPageViewController()
    }
    if let exploreVC = contentViewController as? ExploreViewController {
        exploreVC.pageIndex = index
        exploreVC.pageText = pageContent[index]
    } else if let myTripsVC = contentViewController as? MyTripsViewController {
        myTripsVC.pageIndex = index
        myTripsVC.pageText = pageContent[index]
    } else if let myPageVC = contentViewController as? MyPageViewController {
        myPageVC.pageIndex = index
    }

    let navigationController = UINavigationController(rootViewController: contentViewController)
    navigationController.navigationBar.prefersLargeTitles = true
    navigationController.navigationBar.tintColor = .font

    navigationController.navigationBar.largeTitleTextAttributes = [
        NSAttributedString.Key.font: UIFont.systemFont(ofSize: 34, weight: .bold)
    ]

    navigationController.navigationBar.titleTextAttributes = [
        NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20, weight: .regular)
    ]

    // 하단 선 제거
    navigationController.navigationBar.setBackgroundImage(UIImage(), for: .default)
    navigationController.navigationBar.shadowImage = UIImage()

    let appearance = UINavigationBarAppearance()

    appearance.backgroundColor = .systemBackground
    appearance.backgroundEffect = nil
    navigationController.navigationBar.standardAppearance = appearance

    return navigationController
}

우리는 라지 타이틀을 사용할거라서 이렇게 부모뷰인 PageViewController에서 전체적인 네비게이션 컨트롤러 설정 ( +라지타이틀)

 

* 페이지 컨트롤러로 화면 이동 시에 맨 끝 화면에서도 스크롤되는 이슈!

맨 끝화면에서는 굳이 왼쪽으로 스크롤하면 모션을 줄 필요가 없어서 이렇게 코드로 모션을 제거해 줬다

@objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
    if let scrollView = gestureRecognizer.view as? UIScrollView {
        let translation = gestureRecognizer.translation(in: scrollView)

        // 네비게이션 이동 시 스크롤 비활성화
        if !isScrollEnabled {
            gestureRecognizer.isEnabled = false
            gestureRecognizer.isEnabled = true
            return
        }

        // 첫 번째 페이지에서 왼쪽으로 스크롤할 때 스크롤을 막음
        if selectedIndex == 0 && translation.x > 0 {
            gestureRecognizer.isEnabled = false
            gestureRecognizer.isEnabled = true
        }
        // 마지막 페이지에서 오른쪽으로 스크롤할 때 스크롤을 막음
        else if selectedIndex == pageContent.count - 1 && translation.x < 0 {
            gestureRecognizer.isEnabled = false
            gestureRecognizer.isEnabled = true
        }
    }
}

 

* UIPageViewControllerDelegate 프로토콜은 페이지 전환과 관련된 다양한 이벤트를 처리

* UIPageViewControllerDataSource 프로토콜은 페이지 컨트롤러가 어떤 뷰 컨트롤러를 표시할지를 결정

extension PageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let navigationController = viewController as? UINavigationController,
              let viewController = navigationController.topViewController as? UIViewController & PageIndexed,
              let index = viewController.pageIndex, index > 0 else {
            return nil
        }
        return viewControllerAtIndex(index: index - 1)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let navigationController = viewController as? UINavigationController,
              let viewController = navigationController.topViewController as? UIViewController & PageIndexed,
              let index = viewController.pageIndex, index < pageContent.count - 1 else {
            return nil
        }
        return viewControllerAtIndex(index: index + 1)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed, let viewController = pageViewController.viewControllers?.first as? UINavigationController,
           let pageIndexed = viewController.topViewController as? PageIndexed, let pageIndex = pageIndexed.pageIndex {
            selectedIndex = pageIndex
            NotificationCenter.default.post(name: .didChangePage, object: nil, userInfo: ["selectedIndex": selectedIndex])
        }
    }
}

 

* PageViewController 라는 페이지 컨트롤러의 역할을 하는 ViewController가 있고, 하위에 실제 컨텐츠들을 가진 자식뷰컨들이 페이지에 하나씩 들어간다 ! 나는 이 개념이 잘 안 잡혀서 어려웠다.. 지금와서 생각해보면 간단한데,, !!ㅠㅠ

 


 

라지타이틀은 너무 예쁘지만 다루기가 좀 까다로운 것 같다..

- 여백을 주기가 어렵다. (불가능할지도..

   라지타이틀에 왼쪽에 여백을 주는 것은 불가능한 거 같다.. 엄청 찾아보고 도전해봤는데 실패했다..

-> 큰 제목에는 여백을 줄 수 없고, 작은 제목이 되었을 때는 줄 수 있는 거 같음 !

- 네비게이션이 동적으로 작아졌다가 커졌다가 하기 때문에 가끔씩 알 수 없는 오류를 마주할 때가 있다..

 

 

그리고 이건 라지타이틀에 버튼 넣기 구현 :)

- 라지타이틀은 기본적으로 네비게이션 바가 두줄로 들어가는 거 같다 ! 그래서 그냥 네비게이션바에 rightItem으로 넣으면 타이틀은 두번째 줄에 있는데 버튼아이템은 첫번째 줄에 생긴다..

 

 

만약에 그냥 넣으면 저 오리 위치에 버튼이 생긴다 ! 그래서 커스텀해줘야 한다.. ㆅㆅ 

 

lazy var plusButton: UIButton = {
        let button = UIButton(type: .system)
        let imageConfig = UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
        let image = UIImage(systemName: "plus", withConfiguration: imageConfig)
        button.setImage(image, for: .normal)
        button.tintColor = UIColor(named: "textColor")
        button.backgroundColor = .font
        button.layer.cornerRadius = 15
        button.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
        return button
}()

이렇게 플러스 버튼을 만들어 놓고

 

private func setupNV() {
        navigationItem.title = pageText
        
        if let navigationBarSuperview = navigationController?.navigationBar.superview {
            navigationBarSuperview.addSubview(plusButton)
            
            plusButton.snp.makeConstraints {
                $0.trailing.equalTo(navigationController!.navigationBar.snp.trailing).offset(-16)
                $0.bottom.equalTo(navigationController!.navigationBar.snp.bottom).offset(-10)
                $0.size.equalTo(CGSize(width: 30, height: 30))
            }
        }
}

plsuButton을 addSubview해서 네비게이션바에 넣어주면 된다 ! 위치도 맞춰줄 수 있다!

그리고 이번에 네비게이션 바에 버튼을 넣으면서 

 

let imageConfig = UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
        let image = UIImage(systemName: "plus", withConfiguration: imageConfig)
        button.setImage(image, for: .normal)

 

이 방식으로 버튼 사이즈를 많이 조정해줬던 거 같음 !

 

그리고 막상 실행해보면 내가 설정한 사이즈대로 나오지 않는 경우도 많아서 레이아웃이 완전히 완료된 후에 프레임을 확인할 수 있도록

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    print("Button width: \(searchButton.frame.size.width), height: \(searchButton.frame.size.height)") 
}

이런 식으로 프린트해보면 실제 크기를 알아보고 크기를 조정할 수 있다..

 

사실 나도 확실하게 잘은 몰라서..

저기 위에 코드도 보면 위에서 한 번 포인트 사이즈를 정해주고, 또 밑에서 CGSize를 정해주는데 이게 맞나.. 싶기도 하고,, 잘 모르겠다.. 

더 공부해봐야지... 화이팅...

'TIL' 카테고리의 다른 글

SwiftUI _ ViewModel (@Published, @ObservedObject, @StateObject)  (1) 2024.07.22
61일차 TIL _ 팀프로젝트 회고  (4) 2024.05.25
59일차 TIL  (5) 2024.05.22
58일차 TIL  (0) 2024.05.21
57일차 TIL  (0) 2024.05.20