[iOS] Paging Tabbar 커스텀하기

2020. 11. 8. 23:12iOS

반응형

이번 포스팅에서는 iOS 앱 개발 때, 페이징이 가능한 Tabbar을 이용한 것을 보셨나요?

 

저는 예전에는 이런 요구사항을 받으면 라이브러리를 사용해서 개발을 진행하였는데, 라이브러리를 사용하지 않고 개발해보면 좋을 것 같아서 이번에 해봤습니다.

 

우선 Paging에 가능한 탭바라고 하면 다음 그림과 같이 구현되어 스크롤을 통해 옆의 화면에 넘어가는 방식입니다.


구현 방법

우선 저 화면을 보면 화면을 어떤 순으로 구성해야할 것 같나요?

 

저는 제일 위에 페이징이 나타난 Tabbar을 CollectionView로 구현하고 주황색 줄의 뷰를 UIView을 이용해 작성하였습니다. 그리고 밑의 콘텐츠를 나타내는 뷰는 역시 CollectionView로 구현하였습니다.

 

즉, 다음과 같은 그림으로 구성하였습니다.

✔️ 페이지 표시를 위한 화면 👉 UICollectionView
✔️ 현재 페이지를 위한 Indicator View  👉 UIView
✔️ 콘텐츠 표시를 위한 화면 👉 UICollectionView

 

이제 제일 위에 페이지를 위한 UICollectionView의 세팅먼저 해보도록 할게요.

 

저는 별도의 Xib 파일을 생성하게 만들어주었어요.

그 외에는 전부 코드로 구현하였기에 참고해주시면 될 것 같아요.

 

우선, 위의 페이지를 위한 탭바에서 해야할 역할을 보면 2가지 입니다.

1️⃣ 탭바의 페이지를 클릭했을 때, Indicator View가 이동한다.

2️⃣ 탭바의 페이지를 클릭했을 때, 콘텐츠 표시를 위해 화면이 해당 페이지로 이동한다.

 

해야할 역할을 숙지하시고 다음 초기 세팅을 코드를 통해 해보도록 할게요.

protocol PagingTabbarDelegate {
    func scrollToIndex(to index: Int)
}

class CategoryTabbar: UIView {
    static let nibName = "CategoryTabbar"
    private var view: UIView!
    
    // 페이지를 표시할 Tabbar
    @IBOutlet weak var categoryCollectionView: UICollectionView! {
    	didSet {
            categoryCollectionView.dataSource = self
            categoryCollectionView.delegate = self
            categoryCollectionView.register(UINib(nibName: CategoryCell.nibName, bundle: nil), forCellWithReuseIdentifier: CategoryCell.identifier)
            categoryCollectionView.selectItem(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: [])
        }
    }
    
    // 콘텐츠 뷰에 따라 페이지를 바꾸어주는 코드
    func scroll(to index: Int) {
        categoryCollectionView.selectItem(at: IndexPath(row: index, section: 0), animated: true, scrollPosition: [])
    }
    
    // Indicator로 현재 페이지 표시 UIView
    // 색상은 자유롭게 표시해주세요.
    var indicatorView: UIView = {
    	let indicatorView = UIView()
        indicatorView.backgroundColor = .grapefruit
        indicatorView.translatesAutoresizingMaskIntoConstraints = false
        return indicatorView
    }()
    
    var indicatorLeadingConstaraint: NSLayoutConstraint!
    
    // IndictorView의 왼쪽 오토레이아웃 조정을 통한 이동을 해줄 것이기 때문에 다음과 같이 합니다.
    private func setConstraint() {
        indicatorLeadingConstaraint = indicatorView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
        NSLayoutConstraint.activate([
            indicatorView.topAnchor.constraint(equalTo: categoryCollectionView.bottomAnchor),
            indicatorView.widthAnchor.constraint(equalToConstant: categoryCollectionView.frame.width/2),
            indicatorView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            indicatorLeadingConstaraint
        ])
    }
}

 

지금 코드까지는 초기 세팅을 위한 코드였습니다.

Xib 세팅을 위한 것이랑 CollectionView DataSource, Delegate를 세팅해야 하는데 이것을 선언하는건 개인적으로 하면 될 것 같습니다 🤗

 

그렇다면 이제 위의 2가지 해야할 행동을 코드로 정의할건데요.

여기서 Delegate을 이용해서 탭을 눌렀을 때, 메인 콘텐츠 화면의 이동을 설정해 줄 것입니다.

그리고 Cell을 이용해 셀 클릭 시, 색상을 지정할 수 있게 해볼게요.

extension CategoryTabbar: UICollectionViewDelegate {
    // 셀을 클릭했을 때, 콘텐츠 뷰를 해당 index로 이동
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        delegate?.scrollToIndex(to: indexPath.row)
    }
}

// 해당 이벤트를 받을 ViewController에서 받아서 처리를 한다.
extension MainVC: PagingTabbarDelegate {
    // 탭바를 클릭했을 때, 콘텐츠 뷰 이동
    func scrollToIndex(to index: Int) {
        pageCollectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true)
    }
}
class CategoryCell: UICollectionViewCell {
    static let identifier = "CategoryCell"
    
    private var textLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 14)
        label.textColor = .veryLightPink
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    // 클릭했을 때, 색상이 바뀔 수 있게 설정
    override var isSelected: Bool {
        didSet {
            textLabel.font = isSelected ? UIFont.boldSystemFont(ofSize: 14) : UIFont.systemFont(ofSize: 14)
            textLabel.textColor = isSelected ? .grapefruit : .veryLightPink
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.addSubview(textLabel)
        setAutoLayout()
    }
    
    private func setAutoLayout() {
        NSLayoutConstraint.activate([
            textLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            textLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor)
        ])
    }
}

이 코드까지 따라하셨을 때, 제가 핵심 부분만 작서한 것이기 때문에 추가적으로 필요한 부분은 개인적으로 작성하면 될 것 같습니다.

이렇게까지하면 2가지의 액션은 구현되었습니다.

 

이제 탭을 클릭했을 때의 액션은 끝이 났고, 콘텐츠를 나타내는 뷰를 스크롤 했을 때도 탭이 이동이 있어야하겠죠?

 

이번에도 2가지로 나누어서 설명해드릴게요.

1️⃣ 콘텐츠 뷰 스크롤에 따라 IndicatorView도 따라 움직임
2️⃣ 콘텐츠 뷰의 스크롤에 따라 Tabbar의 라벨이 하이라이팅 됨.

 

이렇게 구현하려면 아까 PagingTabbarDelegate을 구현하였던 것을 활용할게요.

우선 콘텐츠 뷰를 나타내는 CollectionView을 설정할 때, 당연히 Delegate을 채택해주어야겠죠?

그리고 CollectionView의 스크롤에 따라 이벤트가 일어나기 때문에 ScrollView의 Delegate도 채택하여서 활용하여야겠죠?

 

이 두 가지를 어떻게 활용하는지 아직은 감이 안올 수 있는데 코드를 보고 따라 오시면 될 것 같습니다.

class MainVC: UIViewController {
    // 여기는 아까 탭바에서 탭을 클릭했을 때, 콘텐츠 뷰 이동의 이벤트를 전달하기 위한 선언입니다.
    @IBOutlet weak var categoryTabbarView: CategoryTabbar! {
        didSet {
            categoryTabbarView.delegate = self
        }
    }
    
    @IBOutlet weak var pageCollectionView: UICollectionView! {
        didSet {
            pageCollectionView.dataSource = self
            pageCollectionView.delegate = self
            pageCollectionView.showsHorizontalScrollIndicator = false
            pageCollectionView.isPagingEnabled = true
        }
    }
}

extension MainVC: UICollectionViewDelegate {
    // 스크롤이 실행될 때, IndicatorView를 움직임
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        categoryTabbarView.indicatorLeadingConstarint.constant = scrollView.contentOffset.x / 2
    }
    
    // 스크롤이 끝났을 때, 페이지를 계산해서 Tab을 이동시킴
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let page = Int(targetContentOffset.pointee.x / scrollView.frame.width)
        categoryTabbarView.scroll(to: page)
    }
}

 

이제 이렇게 설정을 해주시면 아마 여러분이 원하는 동작을 할 것 같습니다.


그렇다면 핵심 함수들을 간단하게 살펴볼게요.

 

protocol PagingTabbarDelegate, func scrollToIndex(to index: Int)

: 페이지를 위한 탭을 클릭했을 때의 이벤트를 전달하기 위한 Delegate

 

 

func scroll(to index: Int)

: 콘텐츠 뷰의 스크롤에 따라 탭의 페이지를 바꾸어주기 위한 함수

 

func scrollViewDidScroll(_ scrollView: UIScrollView)

: 콘텐츠 뷰의 스크롤에 따라 Indicator View 이동을 위한 함수

 

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)

: 콘텐츠 뷰의 스크롤이 끝났을 때, 해당 페이지로 탭 페이지 변경을 위한 함수

 

메인 함수들은 이게 끝인 것 같습니다.

여기에서 색상이나 폰트 같은 것만 조절을 하면 여러분이 원하는 뷰를 구현할 수 있을 것 같습니다.

 

현재는 한 화면에 원하는 페이지만큼 나누기를 수행해서 페이지를 나누었지만 다음은 더 많은 수의 탭을 구현하기 위한 방법을 포스팅해보겠습니다.

 

감사합니다 🤗

이해가 안되는 점이나 더 필요한 부분은 댓글로 남겨주세요~~

 

 

반응형