iyOmSd/Title: Swift

[Swift] - TabBar 애니메이션: CollectionView를 이용한 페이징

냄수 2020. 4. 29. 03:05
반응형

생각으로만 구현한 코드라서 완벽하진 않을거에요

좋은 방법이 있거나 틀린게 있다면

많은 피드백 부탁드립니다!!

 

 

글로 설명하기 조금 어려워서

이번에 구현할 애니메이션을 영상으로 먼저 볼게요!!

 

 

 

이런 상단 탭바 많이들 보셧죠

저도 보면서 와 이런건 어떻게하나...

궁금했어요 그래서 도전해봤어요

 

 

기본적인 레이아웃으로는

collectionView -> 상단tab부분

view -> 탭바 밑줄

collectionView -> page부분

이렇게 3개를 사용할거에요

 

 

우선 기본적인 Cell부터 구현해볼게요

상단 tab이될 Cell이에요

class TabBarCollectionViewCell: UICollectionViewCell {
    static let reuseIdentifier = "TabBarCell"
    @IBOutlet weak var titleLabel: UILabel!
    
    func setTitle(title: String) {
        titleLabel.text = title
    }
    
    override var isSelected: Bool {
        willSet {
            if newValue {
                titleLabel.textColor = .black
            } else {
                titleLabel.textColor = .lightGray
            }
        }
    }
    
    override func prepareForReuse() {
        isSelected = false
    }
}

클릭효과를 주기위해서 단순하게 label색만 조절했어요

 

 

 

하단 page가 될 Cell이에요

class TabPageCollectionViewCell: UICollectionViewCell {
    static let reuseIdentifier = "TabPageCell"
    lazy var backColor: [UIColor] = [.lightGray, .purple, .orange, .cyan, .magenta]
    
    func setColor(index: Int){
        self.backgroundColor = backColor[index]
    }
}

페이지 구분을 위해서 배경색만 조절했어요

 

 

제일 중요한거라고 생각되는거중 하나가

초기값 설정이에요

저는 여기서 시간좀 많이 썻던거 같아요

원하는 초기값이 안나와서요...

보통 selectItem하면 클릭하는 함수가 실행 되겟지하고 설정해보면

collectionView의 delegate함수의 didSelectItemAt가 실행되지 않아요

 

selectItem는 Cell의 클릭상태 즉, Cell의 isSelected상태를 변경시키고

collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)

didSelectItemAt을 실행시켜주더라구요!!

 

원하는 동작에 맞게 설정해주시면 될거같아요

 

    func setTabbar() {
        tabCollectionView.delegate = self
        tabCollectionView.dataSource = self
        let firstIndexPath = IndexPath(item: 0, section: 0)
        // delegate 호출
        collectionView(tabCollectionView, didSelectItemAt: firstIndexPath)
        // cell select
        tabCollectionView.selectItem(at: firstIndexPath, animated: false, scrollPosition: .right)
    }

 

 

 

탭바를 클릭 할때의 애니메이션을 줄거에요

탭바를 클릭하면 클릭한 곳으로 밑의 바가 이동하고

타이틀이 진해질거에요

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if collectionView == tabCollectionView {    
        
            highlightView.translatesAutoresizingMaskIntoConstraints = false
            constraints = [
                highlightView.leadingAnchor.constraint(equalTo: cell.leadingAnchor),
                highlightView.trailingAnchor.constraint(equalTo: cell.trailingAnchor)
            ]
            NSLayoutConstraint.activate(constraints)
            
            UIView.animate(withDuration: 0.3) {
                self.view.layoutIfNeeded()
            }
        }
    }

클릭 할 때 cell과 오토레이아웃을 맞춰놓으면

알아서 움직이더라구요...!!

처음에는 center를 계산해서 했엇는데 이게 더 좋은 방법같아요

 

 

 

이렇게하면 문제가 생겨요

왜냐하면 AutoLayout이 중복되거든요..!!

다른 아이템을 선택하면 

highlightView의 leadinng, trailinng이 또 적용되면서 중복되는 현상이 일어나서

제대로된 레이아웃이 잡히지 않아요

 

 

지금까지 모르고 지나쳤는데 이번에 구현하면서

더 레이아웃에 대해 알게된거 같아요

 

이 문제를 해결 하기위해선

[NSLayoutConstraint] 타입을 저장할 변수를 하나 두고

activate, deactivate 로 오토레이아웃을

중복되지않도록 조절해주는 거에요!

 

오토레이아웃을 새로잡기전에 deactivate하고

적용하고나서 activate하는 식으로 하면 되요

 

 

 

이제 클릭할 때는 잘 움직이는것을 확인할 수 있어요

 

 

이제는 아래있는 Cell을 페이징 할 때도 위의 탭바가 움직여야겟죠??

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        if scrollView == tabPageCollectionView {
            let index = Int(targetContentOffset.pointee.x / tabPageCollectionView.frame.width)           
            let indexPath = IndexPath(item: index, section: 0)
            
            tabCollectionView.selectItem(at: indexPath, animated: true, scrollPosition: .bottom)
            collectionView(tabCollectionView, didSelectItemAt: indexPath)
            
            if direction > 0 {
                // >>>> 스와이프하면 스크롤은 중앙으로
                tabCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
            } else {
                // <<<< 스와이프하면 스크롤은 왼쪽으로
                tabCollectionView.scrollToItem(at: indexPath, at: .left, animated: true)
            }
        }
    }
    
    // 스크롤 방향을 알아내기 위한 함수
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
        
        if velocity.x < 0 {
            // -: 오른쪽에서 왼쪽 <<<
            direction = -1
        } else if velocity.x > 0 {
            // +: 왼쪽에서 오른쪽 >>>
            direction = 1
        } else {
            
        }
    }

넘겨진 페이지의 index를 구하고

해당 index에 해당하는 탭바의 타이틀에 select시켜줘요

그리고 탭바가 스크롤되는 형식이라면

클릭되면 포커스되듯이 스크롤 시켜주는 코드에요

 

 

 

앞에서 말씀드린 오토레이아웃 미적용, 적용과

탭바 클릭시 페이징

페이징시 탭바포커스를 다룬 전체코드를 보면서 이해해보세요!!

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if collectionView == tabCollectionView {

            guard let cell = tabCollectionView.cellForItem(at: indexPath) as? TabBarCollectionViewCell else {
                NSLayoutConstraint.deactivate(constraints)
                constraints = [
                    highlightView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                    highlightView.widthAnchor.constraint(equalToConstant: 80)
                ]
                NSLayoutConstraint.activate(constraints)
                return
            }
           
            NSLayoutConstraint.deactivate(constraints)
            highlightView.translatesAutoresizingMaskIntoConstraints = false
            constraints = [
                highlightView.leadingAnchor.constraint(equalTo: cell.leadingAnchor),
                highlightView.trailingAnchor.constraint(equalTo: cell.trailingAnchor)
            ]
            NSLayoutConstraint.activate(constraints)
            
            UIView.animate(withDuration: 0.3) {
                self.view.layoutIfNeeded()
            }
            tabPageCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        }
    }

guard let 구문의 안쪽코드는

처음에 뷰컨이 생성될 때 클릭되는 디폴트효과를 주기위해서

함수를 실행했더니

cell타입을 못찾더라구요...?

그래서 찾다가 해결하지못해서 임시로

레이아웃을 적용시켜줬어요

 

 

애니메이션 재밌어요...!

 

 

반응형