ZigZag 클론 코딩을 하면서 상단 탭바 구현이 필요했습니다.
상단 탭바는 직접 구현할 수도 있고, 혹은 다양한 라이브러리를 활용해 구현할 수 있습니다.
아래 영상은 상단 탭바 - 탭맨 라이브러리 활용해 커스텀한 최종 결과물입니다.
탭맨 전체 코드는 최하단에 있으니 확인하시기 바랍니다.
우선 대표적인 상단 탭바 라이브러리 일부를 소개드리겠습니다.
상단탭바 라이브러리 모음
TabMan : https://github.com/uias/Tabman
PagingKit : https://github.com/kazuhiro4949/PagingKit | https://www.youtube.com/watch?v=RtneD_6FrNM
XLPagerTabStrip : https://github.com/xmartlabs/XLPagerTabStrip
커스텀하는 방법을 말씀드리기 전에, 우선 기본적인 설치방법부터 말씀드리겠습니다.
1. 코코아팟을 통해 라이브러리를 설치합니다.
pod 'Tabman', '~> 2.11'
코코아팟을 통해 설치하는 방법을 모르신다면 여기를 참고하시기 바랍니다.
2. 사용할 ViewController에 아래 내용을 import 해줍니다.
import Tabman
import Pageboy
import에 안 뜨신다면, 커맨드 + B를 눌러 빌드한 뒤 진행하면 됩니다.
3. UIViewController 대신 TabmanViewController 를 상속받도록 수정합니다.
(전 이름을 수정해서 ViewController가 아닌 HomeViewController입니다.)
4. 하단에 Extension을 추가합니다.
각 case에 맞게 탭 Title을 설정해주면 됩니다.
extension HomeViewController: PageboyViewControllerDataSource, TMBarDataSource {
func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
// let item = TMBarItem(title: "")
// item.title = "Page \(index)"
// item.image = UIImage(named: "image.png")
//
// return item
// MARK: - Tab 안 글씨들
switch index {
case 0:
return TMBarItem(title: "홈")
case 1:
return TMBarItem(title: "Brand")
case 2:
return TMBarItem(title: "베스트")
case 3:
return TMBarItem(title: "세일")
default:
let title = "Page \(index)"
return TMBarItem(title: title)
}
}
func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
return viewControllers.count
}
func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? {
return viewControllers[index]
}
func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
return nil
}
}
이렇게 하고 실행하면 상단 탭바가 작동은 잘 됩니다.
(만약 작동이 잘 되지 않으셔도 괜찮으니 우선 넘어가세요. 최하단에 전체 코드를 올려놓았습니다.)
하지만 이렇게 예제만 했을 땐 2가지 문제가 있었습니다.
아래 이미지를 보시면,
1. 예제는 왼쪽으로 쏠려 있습니다. 즉, 사진처럼 균등하게 분배되지 않았습니다.
2. (화면 최상단이 아닌) 제가 원하는 곳에 상단 탭바를 넣을 수 없었습니다.
기본 예제는
https://gonslab.tistory.com/17 와 https://velog.io/@iammiori/iOS-opensource-tabman 를 참고했습니다.
원하던 대로 커스텀하는 방법
문제 1번 해결 방법
: 각 버튼을 화면에 딱 맞게 설정하는 방법.
위에 참고 링크들에선, 원하던 이미지처럼 화면에 딱 맞게 설정하는 방법이 안 나와 있었습니다.
다행히 공식문서에서 내용을 찾아 해결했습니다!
bar.layout.contentMode = .fit 을 하면 됩니다!
bar.layout.alignment = .centerDistributed // .center시 선택된 탭이 가운데로 오게 됨.
bar.layout.contentMode = .fit
// bar.layout.interButtonSpacing = 35 // 버튼 사이 간격 (화면 보면서 필요시 조절)
아래는 탭맨 공식 문서 중 일부입니다.
주석 부분에 각 옵션에 대한 상세한 설명이 나와 있습니다.
문제 2번 해결 방법
: 내가 원하는 곳에 Bar를 위치시키는 방법.
문제 2번을 해결하는 게 어려웠습니다. 이 글을 쓰게 된 이유이기도 합니다. 저와 같은 힘듦을 겪으실 분들을 위해...
addBar함수 중, .custom을 통해 가능합니다.
(Bar를 넣을 UIView를 미리 만들어 놓고, UIView의 이름(여기선 tempView)을 넣어주면 됩니다.)
// Add to view
addBar(bar, dataSource: self, at: .custom(view: tempView, layout: nil)) // .custom을 통해 원하는 뷰에 삽입함. BarLocation - https://github.com/uias/Tabman/blob/main/Sources/Tabman/TabmanViewController.swift#L27-L32
아래는 탭맨 공식 문서 중 일부입니다.
주석 부분에 각 옵션에 대한 상세한 설명이 나와 있습니다.
탭맨 BarLocation 관련 공식문서 링크입니다.
https://github.com/uias/Tabman/blob/main/Sources/Tabman/TabmanViewController.swift#L27-L32
지금까지 탭맨 기본 예제 & 커스텀하는 방법에 대해서 알아봤습니다.
저는 위에 설명드린 2가지 방법을 통해, 상단 탭바를 원하던 대로 커스텀했습니다.
아래는 결과 화면에 대한 전체 코드입니다.
도움이 되셨다면 좋겠습니다!
import UIKit
import Tabman
import Pageboy
class HomeViewController: TabmanViewController {
// 상단 탭바 라이브러리 - TabMan 사용
private var viewControllers: Array<UIViewController> = []
@IBOutlet weak var tempView: UIView! // 상단 탭바 들어갈 자리
override func viewDidLoad() {
super.viewDidLoad()
let homeHomeVC = UIStoryboard.init(name: "MainPageStoryboard", bundle: nil).instantiateViewController(withIdentifier: "HomeHomeVC") as! HomeHomeViewController
let brandVC = UIStoryboard.init(name: "MainPageStoryboard", bundle: nil).instantiateViewController(withIdentifier: "BrandVC") as! BrandViewController
let bestVC = UIStoryboard.init(name: "MainPageStoryboard", bundle: nil).instantiateViewController(withIdentifier: "BestVC") as! BestViewController
let saleVC = UIStoryboard.init(name: "MainPageStoryboard", bundle: nil).instantiateViewController(withIdentifier: "SaleVC") as! SaleViewController
viewControllers.append(homeHomeVC)
viewControllers.append(brandVC)
viewControllers.append(bestVC)
viewControllers.append(saleVC)
self.dataSource = self
// Create bar
let bar = TMBar.ButtonBar()
// let bar = TMBar.TabBar()
bar.backgroundView.style = .blur(style: .regular)
bar.layout.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
bar.buttons.customize { (button) in
button.tintColor = .mainGray // 선택 안되어 있을 때
button.selectedTintColor = .black // 선택 되어 있을 때
}
// 인디케이터 조정
bar.indicator.weight = .light
bar.indicator.tintColor = .black
bar.indicator.overscrollBehavior = .compress
bar.layout.alignment = .centerDistributed
bar.layout.contentMode = .fit
bar.layout.interButtonSpacing = 35 // 버튼 사이 간격
bar.layout.transitionStyle = .snap // Customize
// Add to view
addBar(bar, dataSource: self, at: .custom(view: tempView, layout: nil)) // .custom을 통해 원하는 뷰에 삽입함. BarLocation - https://github.com/uias/Tabman/blob/main/Sources/Tabman/TabmanViewController.swift#L27-L32
}
}
extension HomeViewController: PageboyViewControllerDataSource, TMBarDataSource {
func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
// let item = TMBarItem(title: "")
// item.title = "Page \(index)"
// item.image = UIImage(named: "image.png")
//
// return item
// MARK: - Tab 안 글씨들
switch index {
case 0:
return TMBarItem(title: "홈")
case 1:
return TMBarItem(title: "Brand")
case 2:
return TMBarItem(title: "베스트")
case 3:
return TMBarItem(title: "세일")
default:
let title = "Page \(index)"
return TMBarItem(title: title)
}
}
func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
return viewControllers.count
}
func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? {
return viewControllers[index]
}
func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
return nil
}
}
22.01.11 추가
func defaultPage(...) 함수에서 return nil로 해도 되지만, return .at(index: )를 써도 됩니다.
func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
//return nil
return .at(index: 0) // https://github.com/uias/Tabman/issues/259
}
22.02.24 추가
이해를 돕기 위해 스토리보드 화면을 캡처 및 편집해서 올립니다.
상세한 내용은 댓글창을 참고해주세요.
댓글 질문 : [홈, 브랜드, 베스트, 세일] 탭을 터치할때마다 내용을 바꾸고 싶은데, 현재는 배열에 추가한 뷰컨이 통째로 전환되고 있습니다. 어떻게 해결해야 하나요?
답변 : 뷰 컨트롤러를 "백그라운드가 될 뷰컨 - HomeViewController" 까지 총 5개 만드시고,
"백그라운드가 될 뷰컨 - HomeViewController" 에다가 본문의 코드를 추가하시면 됩니다.
최근댓글