Skip to content

Instantly share code, notes, and snippets.

@J0onYEong
Created April 29, 2024 02:26
Show Gist options
  • Save J0onYEong/15468c7da861e6f967fedaad5f419006 to your computer and use it in GitHub Desktop.
Save J0onYEong/15468c7da861e6f967fedaad5f419006 to your computer and use it in GitHub Desktop.
CustomTabBarView(UIKit)
import UIKit
extension UINavigationController {
open override func willMove(toParent parent: UIViewController?) {
// 같은 container에 대해 호출된다.
print("wm, \(title!) | parent: \(String(describing: parent))")
}
open override func didMove(toParent parent: UIViewController?) {
print("dm, \(title!) | parent: \(String(describing: parent))")
}
}
class CustomTabBarContainer: UIViewController {
private var viewControllers: [UIViewController] = []
private var buttons: [UIButton] = []
private lazy var tabBarView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
return view
}()
var selectedIndex = 0 {
willSet {
// 세팅되기 전에 previewsIndex에 저장
previewsIndex = selectedIndex
}
didSet {
updateView()
}
}
private var previewsIndex: Int?
override func viewDidLoad() {
super.viewDidLoad()
setupTabBar()
}
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)
])
}
func setViewControllers(_ viewControllers: [UIViewController]) {
self.viewControllers = viewControllers
// 🐼🐼
setupButtons()
// 🙈🙈
updateView()
}
// 🐼🐼
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)
}
}
@objc private func tabButtonTapped(_ sender: UIButton) {
selectedIndex = sender.tag
}
// 🙈🙈
func updateView() {
if selectedIndex == previewsIndex { return }
// 🦄🦄 이전뷰 삭제
deleteView()
// 🦅🦅 새로운 뷰 삽입
setupView()
buttons.forEach { $0.isSelected = ($0.tag == selectedIndex) }
}
// 🦄🦄 이전뷰 삭제
private func deleteView() {
guard let prev = previewsIndex else { return }
let previousVC = viewControllers[prev]
previousVC.view.removeFromSuperview()
// ✅ 커스텀 컨테이너 구현시 removeFromParent호출전 willMove를 반드시 호출해야한다.
previousVC.willMove(toParent: nil)
previousVC.removeFromParent()
// 제거후, didMove를 자동으로 호출
}
// 🦅🦅 새로운 뷰 삽입
private func setupView() {
let selectedVC = viewControllers[selectedIndex]
// willMove를 자동으로 호출한다.
// 현재 뷰컨트롤러의 자식뷰컨트롤러를 지정하는 경우에 호출된다
self.addChild(selectedVC)
// ✅ 커스텀 컨테이너 구현시, addChild이후에 반드시 호출해야한다.
selectedVC.didMove(toParent: self)
view.insertSubview(selectedVC.view, belowSubview: tabBarView)
selectedVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
selectedVC.view.topAnchor.constraint(equalTo: view.topAnchor),
selectedVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -90),
selectedVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
selectedVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
let firstViewController = TestViewController("1")
firstViewController.view.backgroundColor = .red
firstViewController.title = "First"
let firstNavi = UINavigationController(rootViewController: firstViewController)
let secondViewController = TestViewController("2")
secondViewController.view.backgroundColor = .blue
secondViewController.title = "Second"
let secondNavi = UINavigationController(rootViewController: secondViewController)
let thirdViewController = TestViewController("3")
thirdViewController.view.backgroundColor = .yellow
thirdViewController.title = "Third"
let thirdNavi = UINavigationController(rootViewController: thirdViewController)
let customTabBarController = CustomTabBarContainer()
customTabBarController.setViewControllers([firstNavi, secondNavi, thirdNavi])
window?.rootViewController = customTabBarController
window?.makeKeyAndVisible()
}
//...
}
import UIKit
final class TestViewController: UIViewController {
var number = ""
let animator = CustomAnimator()
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
view.layer.borderColor = UIColor.blue.cgColor
view.layer.borderWidth = 2.0
print("\(number): init")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("\(number): Deinit")
}
override func willMove(toParent parent: UIViewController?) {
print("\(number) will Move, parent:\(parent)")
}
override func didMove(toParent parent: UIViewController?) {
print("\(number) did Move, parent:\(parent)")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment