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

당근 마켓에 보면 상단에 탭바가 위치 한 것을 볼수 있다
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