개발 팀 애들끼리 개발 관련 포스트를 쓸 수 있게 블로그를 만들고 있는데 난 거기서 iOS를 담당한다.
예전부터 한참 애썼던 탭바.. 탭바 자체는 솔직히 그냥 만들수있는데 하단에 같이 나오는 뷰 .. 이 뷰들의 높이가 각각 다르고 각각 스크롤이 필요하다? 골치아파졌음.. 인스타그램 마이페이지 피드 쪽 생각하면 감이 올거다.
우선 탭바를 어떤 형태로 구현할까 고민했음
1. SegmentedControl + a, 일단 상단 탭바는 세그먼트 컨트롤이라는 컴포넌트가 가장 가깝다. 다만 이를 전에 커스텀해서 만들려니 벽 느껴서 일단 보류.
2. UICollectionView + PageView, 이게 커스텀으로 할때 구조가 될 것 같은데, indicator의 애니메이션이랑 각 뷰컨트롤러 간 전환하는 데에 있어 Delegate 관련 구현할 게 많아 빠르게 구현해야 하는 현 상황에서 기각.
3. Tabman. 탭바를 쉽게 구현할 수 있게 해주는 라이브러리다. 이것도 결국 PageViewController를 상속받는 형태에 있어서 2번과 크게 다를 건 없지만 미리 구현되어있는 기능들을 쓸 수 있다는 점이 메리트.
https://github.com/uias/Tabman
GitHub - uias/Tabman: ™️ A powerful paging view controller with interactive indicator bars
™️ A powerful paging view controller with interactive indicator bars - uias/Tabman
github.com
원래 나는 MVVM 형태로 ViewController에 UI 관련 코드를 쓴다기보다 View 파일에 코드를 작성 후 ViewController에서 loadView 메서드를 통해 해당 뷰를 입히는 방향으로 진행해왔다. 다만, Tabman의 경우 자체로 뷰 구조가 잡혀있어서 ViewController에 Tabman을 상속시키고 내가 만든 View를 인스턴스화해 loadView()한다? 화면 구조가 깨짐..
private func configureTabBar(_ bar: TMBar.ButtonBar) {
bar.layout.transitionStyle = .progressive
bar.layout.alignment = .leading
bar.layout.contentMode = .intrinsic
bar.layout.interButtonSpacing = 16
bar.layout.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
bar.backgroundView.style = .flat(color: .white)
bar.indicator.weight = .custom(value: 2)
bar.indicator.tintColor = .black
bar.buttons.customize { button in
button.tintColor = .systemGray
button.selectedTintColor = .black
button.font = .systemFont(ofSize: 16)
button.selectedFont = .systemFont(ofSize: 16, weight: .bold)
}
}
탭바에 대한 설정은 이렇게 해두었다.
contentMode를 intrinsic으로 해서 좌측에 몰려있는 탭바 형태를 구현하였음.
균일하게 뷰를 꽉 채우는 형태로 하고 싶으면 fit으로 설정하면 된다.
private func setupTabman() {
self.dataSource = self
let bar = TMBar.ButtonBar()
configureTabBar(bar)
addBar(bar, dataSource: self, at: .custom(view: tabBarContainer, layout: nil))
scrollToPage(.at(index: 0), animated: false)
}
요렇게 Tabman 관련한 delegate를 설정해주고 저 탭바인 ButtonBar 인스턴스 생성 후 위에 있던 설정을 입혀준다.
addBar 매개변수 중 at에서는 보통 다들 .top으로 하는데 이 경우 화면의 최상단에 붙이고 싶으면 그렇게 하지만, 나는 그보다 위에 헤더뷰를 두어야 할 필요가 있었기에 custom했다. 이 부분엔 구글링해보니 자료가 많이 없어서 클로드한테 많이 도움받음..
tabBarContainer에 붙이는 건데 이 tabBarContainer는 어디에 있느냐
private func setupHeaderView() {
view.addSubview(headerView)
headerView.addSubview(logo)
headerView.addSubview(editButton)
headerView.addSubview(menuButton)
view.addSubview(tabBarContainer)
let safeAreaLayoutHeight = UIApplication.shared.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
.first?.safeAreaInsets.top ?? 0
print("safeAreaHeight = \(safeAreaLayoutHeight)")
headerView.snp.makeConstraints {
$0.top.leading.trailing.equalToSuperview()
$0.height.equalTo(60 + safeAreaLayoutHeight)
}
logo.snp.makeConstraints {
$0.centerY.equalToSuperview().offset(safeAreaLayoutHeight/2)
$0.leading.equalToSuperview().offset(20)
}
menuButton.snp.makeConstraints {
$0.centerY.equalToSuperview().offset(safeAreaLayoutHeight/2)
$0.trailing.equalToSuperview().offset(-20)
$0.size.equalTo(24)
}
editButton.snp.makeConstraints {
$0.trailing.equalTo(menuButton.snp.leading).offset(-16)
$0.centerY.equalToSuperview().offset(safeAreaLayoutHeight/2)
$0.size.equalTo(24)
}
tabBarContainer.snp.makeConstraints {
$0.top.equalTo(headerView.snp.bottom)
$0.leading.trailing.equalToSuperview()
$0.height.equalTo(36)
}
}
headerView 바로 하단에 붙여두었음. 탭바 자체 높이는 36이기에 저렇게 설정해줬고 tabman 특성상 그 뒤에 관련 ViewController를 배치하는 것 같아서 headerView가 safeArea영역까지 커버할 수 있도록 했다. ( 커버 안하면 safeArea자리에 뷰가 뚫려서 보일 수 있음..)
centerY를 기준으로 배치하기 때문에 safeAreaLayoutHeight에 대해서도 반 나누어 적용함.
요런 구조인데 저 safeArea를 커버하지 않으면,,, 파란색이 보인다.. 그래서 결국 실제 뷰는 저 탭 아래부터 있는게 아닌 최상단부터 있지만 상위에 뷰가 가리고 있어 하단부터 시작되는것처럼 보일뿐일거다. 이 부분은 실제 뷰 구현시 참고해서 애초에 컴포넌트를 아래에 배치하던지 아니면 뷰컨트롤러 자체를 저 탭 아래부터 시작할 수 있도록 할 수 있는지 찾아봐야 알 것 같다.
그냥 Tabman을 사용하는 방법은
TMBar.ButtonBar()부분처럼 인스턴스 생성해주고 관련 설정 입혀주고 원하는 곳에 addBar해주면 된다. (+ 각 탭마다 연결될 뷰컨트롤러들 배열에 준비해두기)
이제 위에서 말했듯이 하위에 배치된 ViewController의 영역을 바꿀수 있는지 찾아보고 이 부분 조치 후 각 뷰마다 UI 구성해서 띄워봐야 알듯하다. 그냥 화면 안으로 들어가는 UI면 모르겠지만 세 탭 전부 스크롤할 수 있어야 하기에 UIScrollView, UITableView를 써서 뷰 구성 후 넣어 테스트해봐야 한다.
'iOS > Swift' 카테고리의 다른 글
[TIL / 25.02.11] 사이드메뉴를 구현해보자 . . . ! (0) | 2025.02.11 |
---|---|
[TIL / 25.02.10] 헤더를 이용한 테이블뷰를 구성, 셀과 헤더 높이 자동 계산 (4) | 2025.02.10 |
[TIL / 25.02.07] 라이브러리 사용해서 상단 탭바 구현하기 2 (0) | 2025.02.07 |
[TIL / 24.02.05] 함수의 기초 복습해봅시다 ! (5) | 2025.02.05 |
[TIL / 25.02.04] Swift의 기본 데이터 타입~반복문까지 복습! (0) | 2025.02.04 |