Created
April 29, 2024 02:26
-
-
Save J0onYEong/15468c7da861e6f967fedaad5f419006 to your computer and use it in GitHub Desktop.
CustomTabBarView(UIKit)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | |
]) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
//... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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