CustomTabBarController
를 구현하면, 보다 유연한 탭 바 스타일링과 관리가 가능해집니다.
CustomTabBarController
는UIViewController
를 상속받아 정의되어 있습니다.viewControllers
배열은 각 탭에 대응하는 뷰 컨트롤러를 저장합니다.buttons
배열은 탭 바에 표시될 버튼들을 저장합니다.tabBarView
는 탭 바를 표시하는 뷰 입니다.
setupTabBar
함수는tabBarView
를 화면 하단에 위치시키고, 적절한 높이와 폭으로 설정합니다.
setupButtons
함수는 각viewControllers
에 대해 버튼을 생성하고, 이를tabBarView
에 추가합니다.- 각 버튼의 태그는 해당 뷰 컨트롤러의 인덱스로 설정됩니다.
- 버튼이 탭되면
tabButtonTapped
함수가 호출되어,selectedInedx
가 갱신됩니다.
updateView
함수는 현재 선택된 인덱스에 따라 해당 뷰 컨트롤러를 활성화하고, 다른 모든 뷰 컨트롤러를 비활성화합니다.- 선택된 뷰 컨트롤러의 뷰는
tabBarView
아래에 위치합니다.
selectedInedx
의 값이 변경되면,updateView
함수가 호출되어 뷰가 갱신됩니다.
import UIKit
final class CustomTabBarController: UIViewController {
private lazy var viewControllers: [UIViewController] = []
private lazy var buttons: [UIButton] = []
private lazy var tabBarView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 35
return view
}()
var selectedIndex = 0 {
willSet {
previewsIndex = selectedIndex
}
didSet {
updateView()
}
}
private var previewsIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
setupTabBar()
}
func setViewControllers(_ viewControllers: [UIViewController]) {
self.viewControllers = viewControllers
setupButtons()
updateView()
}
private func setupTabBar() {
view.addSubview(tabBarView)
tabBarView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tabBarView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tabBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tabBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tabBarView.heightAnchor.constraint(equalToConstant: 90)
])
}
private func setupButtons() {
// 버튼의 넓이는 tab 개수에 맞춰서 유동적으로 변함
let buttonWidth = view.bounds.width / CGFloat(viewControllers.count)
for (index, viewController) in viewControllers.enumerated() {
let button = UIButton()
button.tag = index
button.setTitle(viewController.title, for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
tabBarView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.bottomAnchor.constraint(equalTo: tabBarView.bottomAnchor),
button.leadingAnchor.constraint(equalTo: tabBarView.leadingAnchor, constant: CGFloat(index) * buttonWidth),
button.widthAnchor.constraint(equalToConstant: buttonWidth),
button.heightAnchor.constraint(equalTo: tabBarView.heightAnchor)
])
buttons.append(button)
}
}
private func updateView() {
deleteView()
setupView()
buttons.forEach { $0.isSelected = ($0.tag == selectedIndex) }
}
private func deleteView() {
let previousVC = viewControllers[previewsIndex]
previousVC.willMove(toParent: nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParent()
}
private func setupView() {
let selectedVC = viewControllers[selectedIndex]
self.addChild(selectedVC)
view.insertSubview(selectedVC.view, belowSubview: tabBarView)
selectedVC.view.frame = view.bounds
selectedVC.didMove(toParent: self)
}
@objc private func tabButtonTapped(_ sender: UIButton) {
selectedIndex = sender.tag
}
}
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
let firstViewController = TestViewController("1")
firstViewController.view.backgroundColor = .gray
firstViewController.title = "First"
let firstNavi = UINavigationController(rootViewController: firstViewController)
let secondViewController = TestViewController("2")
secondViewController.view.backgroundColor = .darkGray
secondViewController.title = "Second"
let secondNavi = UINavigationController(rootViewController: secondViewController)
let thirdViewController = TestViewController("3")
thirdViewController.view.backgroundColor = .lightGray
thirdViewController.title = "Third"
let thirdNavi = UINavigationController(rootViewController: thirdViewController)
let customTabBarController = CustomTabBarController()
customTabBarController.setViewControllers([firstNavi, secondNavi, thirdNavi])
window?.rootViewController = customTabBarController
window?.makeKeyAndVisible()
}
}
import UIKit
final class TestViewController: UIViewController {
var number = ""
override func viewDidLoad() {
super.viewDidLoad()
print("\(number): ViewDidLoad")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("\(number): ViewWillAppear")
}
init(_ number: String) {
super.init(nibName: nil, bundle: nil)
self.number = number
print("\(number): init")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("\(number): Deinit")
}
}
1: init
1: ViewDidLoad
2: init
2: ViewDidLoad
3: init
3: ViewDidLoad
1: ViewWillAppear
2: ViewWillAppear
3: ViewWillAppear
1: ViewWillAppear
TabBar Button을 클릭 할 때를 보면
ViewWillAppear
만 호출되는 것을 확인할 수 있다.
CustomTabBarController
내부에 존재하는 UINavigationController
에서 TabBar
를 hidden
처리 한다던가 색상을 변경하는 등 TabBar
의 속성을 변경하려고 하면 Delegate
를 생성해서 해당 ViewController
나 flow를 담당하는 부분에서 채택하면 된다. protocol TabBarDelegate: AnyObject {
func shouldHideTabBar(_ hide: Bool)
}
final class CustomTabBarController: UIViewController, TabBarDelegate {
func shouldHideTabBar(_ hide: Bool) {
tabBarView.isHidden = hide
}
}
final class TextViewController: UIViewController {
weak var tabBarViewController: TabBarDelegate?
}
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.