IOS

상단에 위치하는 커스텀 탭바 만들기

훈하 2021. 8. 1. 11:43

 당근 마켓에 보면 상단에 탭바가 위치 한 것을 볼수 있다

 

CollectionView로 구현 한 것을 많이 볼 수 있는데

 

두 장단점이 있다

-장점 : CollectionView로 탭바를 구현한다면 많은 탭이 존재할때 유용할것이다 (예를 들면 쇼핑 앱)

           글자의 길이 만큼 하단 라인을 줄 수 있을것 같다

-단점 : 구현이 조금 복잡? 하지않을까..?

 

- 장점 : CollectionView보다 쉽다, 빠르게 탭바를 그리고 싶을때, 탭이 많이 존재하지 않는 경우 유용 

- 단점:  글자 길이만큼 하단 라인 주기가 쉽지 않을것이다, 탭의 크기가 모두 일정할것이다. 어느것은 짧게,길게 불가능 

 

이 글은 SegmentedControl를 이용해서 간단히 그리는 커스텀 탭바 만든 과정을 보여줄 예정이다.

 

구성은

발그림 이지만 

SegmentedControl를 담을 UIView(ContainerView) 

SegmentedControl 하단에 위치할 underLineView

 

객체 생성 

// SegmentedControl 담을 뷰
    private lazy var containerView: UIView = {
        let container = UIView()
        container.backgroundColor = .clear
        container.translatesAutoresizingMaskIntoConstraints = false
        return container
    }()
 
    private lazy var segmentControl: UISegmentedControl = {
        let segment = UISegmentedControl()
        
        
        segment.selectedSegmentTintColor = .clear
        
        // 배경 색 제거
        segment.setBackgroundImage(UIImage(), for: .normal, barMetrics: .default)
        // Segment 구분 라인 제거
        segment.setDividerImage(UIImage(), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
        
        segment.insertSegment(withTitle: "First Tab", at: 0, animated: true)
        segment.insertSegment(withTitle: "Second Tab", at: 1, animated: true)
        
        segment.selectedSegmentIndex = 0
        
        // 선택 되어 있지 않을때 폰트 및 폰트컬러
        segment.setTitleTextAttributes([
            NSAttributedString.Key.foregroundColor: UIColor.black,
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .regular)
        ], for: .normal)
        
        // 선택 되었을때 폰트 및 폰트컬러
        segment.setTitleTextAttributes([
            NSAttributedString.Key.foregroundColor: UIColor.systemBlue,
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .bold)
        ], for: .selected)
        
        segment.addTarget(self, action: #selector(changeSegmentedControlLinePosition), for: .valueChanged)
        
        segment.translatesAutoresizingMaskIntoConstraints = false
        return segment
    }()
    
    private lazy var underLineView: UIView = {
        let view = UIView()
        view.backgroundColor = .systemBlue
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    // 움직일 underLineView의 leadingAnchor 따로 작성
      private lazy var leadingDistance: NSLayoutConstraint = {
        return underLineView.leadingAnchor.constraint(equalTo: segmentControl.leadingAnchor)
    }()

 

오토레이아웃

   func configure() {
        view.addSubview(containerView)
        containerView.addSubview(segmentControl)
        containerView.addSubview(underLineView)
        
        NSLayoutConstraint.activate([
            containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            containerView.heightAnchor.constraint(equalToConstant: 40),
            
            segmentControl.topAnchor.constraint(equalTo: containerView.topAnchor),
            segmentControl.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            segmentControl.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
            segmentControl.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),

            underLineView.bottomAnchor.constraint(equalTo: segmentControl.bottomAnchor),
            underLineView.heightAnchor.constraint(equalToConstant: 5),
            leadingDistance,
            underLineView.widthAnchor.constraint(equalTo: segmentControl.widthAnchor, multiplier: 1 / CGFloat(segmentControl.numberOfSegments))
        ])
    }

처음 오토레이웃을 잡아줄때 leadingDistance 의 값은 segmentControl의 leading 과 같다

underline width는 segmentControl width와 같지만 비율은  1 / segmentControl 탭의 수 = 0.5 를 곱해 해당 크기만큼 가질수 있다

 

SegmentControl Action

 @objc private func changeUnderLinePosition() {
        let segmentIndex = CGFloat(segmentControl.selectedSegmentIndex)
        let segmentWidth = segmentControl.frame.width / CGFloat(segmentControl.numberOfSegments)
        let leadingDistance = segmentWidth * segmentIndex
        UIView.animate(withDuration: 0.2, animations: { [weak self] in
            self?.leadingDistance.constant = leadingDistance
            self?.view.layoutIfNeeded()
        })
    }

segmentControl 클릭할때 마다 width 값은 width / index를 값을 찾고

underLine leadingAnchor의 constant 값을 추가 후 레이아웃을 즉시 업데이트 시킨다

 

 

결과 화면

 

 

gist

https://gist.github.com/HoonHaChoi/5e83a237b15088cf46c4cdfea8302e4c