//
// MainViewController.swift
// BottomTapMemoryAndLifeCycleOptimization
//
// Created by 도학태 on 2023/04/18.
//
import Foundation
import UIKit
import RxSwift
import RxCocoa
import PanModal
class MainTapBarViewController : UITabBarController {
static let KOTLIN = 0
static let SWIFT = 1
static let JAVA = 2
static let META = 3
static let CSHARP = 4
var isSelectConrifm = true
var translatePageLastIndex = -1
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
attribute()
bind()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
launchScreen()
}
func bind() {
let emptyVc = UIViewController()
let kotlinVc = KotlinViewController()
let swiftVc = SwiftViewController()
let cSharpVc = CSharpViewController()
viewControllers = [
generateNavController(kotlinVc, "Kotil", UIImage(systemName: "circle.square")),
generateNavController(swiftVc, "Swift", UIImage(systemName: "flag.2.crossed.circle.fill")),
generateNavController(emptyVc, "Meta", UIImage(systemName: "person.fill")),
generateNavController(emptyVc, "Java", UIImage(systemName: "train.side.front.car")),
generateNavController(cSharpVc, "CSharp", UIImage(systemName: "lock.display")),
]
}
/*
초기 화면을 정한다.
UITabbarViewController가 viewDidLoad에서 초기화가 완료된 이후에
lanchScreen 호출 -> viewWillAppear에서 호출
*/
func launchScreen() {
if isSelectConrifm {
/*
초기 화면 SWIFT 설정
*/
self.selectedIndex = MainTapBarViewController.SWIFT
isSelectConrifm.toggle()
self.translatePageLastIndex = MainTapBarViewController.SWIFT
}
}
/*
TabBar에 ViewController 등록하는 함수
*/
func generateNavController(
_ vc : UIViewController,
_ title : String,
_ image : UIImage?
) -> UINavigationController {
vc.view.backgroundColor = .white
let navController = UINavigationController(rootViewController: vc)
let tabBarItem = UITabBarItem(title: title, image: image, tag: 0)
navController.tabBarItem = tabBarItem
return navController
}
func attribute() {
self.delegate = self
}
}
extension MainTapBarViewController : UITabBarControllerDelegate {
/*
Page 전환 로직이 담겨 있는 함수
Bottom Tap 할때 이 함수가 호출
*/
func translatePage(_ selectIndex : Int) {
/*
정상적으로 페이지 전환해야 되는 Page에는 LastIndex를 넣어준다
Page전환이 되면 안되는 페이지는 LastIndex를 넣어주지 않고 LastIndex에 있는 값으로 페이지 유지하고
새로운 페이지를 띄운다.
*/
switch selectIndex {
case MainTapBarViewController.META:
self.selectedIndex = translatePageLastIndex
let metaVc = MetaViewController()
metaVc.view.backgroundColor = .white
let navVc = UINavigationController(rootViewController: metaVc)
navVc.modalTransitionStyle = .coverVertical
navVc.modalPresentationStyle = .fullScreen
self.present(navVc, animated: true)
case MainTapBarViewController.JAVA:
self.selectedIndex = translatePageLastIndex
let javaVc = JavaViewController()
javaVc.view.backgroundColor = .white
self.presentPanModal(javaVc)
default:
self.translatePageLastIndex = selectIndex
}
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
translatePage(self.selectedIndex)
}
}
이러한 이유는 self.selectedIndex에 tanslatePageLastIndex를 넣어주고 화면을 띄어서 발생하는 문제이다.
위와 같은 방식으로 했을때 요구사항은 충족한다. 그러나 효율적이지 못하고 LifeCycle이 내가 원하지 않는 동작을 한다는것은
적합하고 생각하지 않았다.
어떻게 하면 이러한 문제를 해결 할 수 있을까?
첫번째 문제의 경우 미리 등록을 해서 문제였다. 그렇다면 사용자가 탭을 했을때 생성을 하도록 시점을 달리하면 되지 않을까?
두번째 문제는 selectedIndex에 LastIndex를 부여하고 새로운 페이지를 띄어서 문제가 되는것이다. 즉 페이지 전환이 되고
또 페이지 전환이 되어 안되는것 처럼 보이는 것이다. 그렇다면 애초에 페이지 전환을 안하도록 막으면 되지 않을까?
첫번째 문제의 해결은 초기에 빈 ViewController를 넣어준다. 그리고 사용자가 버튼을 탭하는 시점에 맞게 ViewControlelr를 생성하고 다시
화면에 등록시켜 줬다. 코드는 아래와 같다.
*** 이와 같은 방법으로 했을때 아래와 같이 해결이 된다. 보는 바와 같이 SwiftPage를 보고 있다면 초기에 SwiftPage만 생성된다.
또한 전환을 할때 그에 맞게 생성이 된다.
두번째 문제 해결 : UITabBarControllerDelegate에서 제공해주는 아래의 함수는 페이지 전환에 대해서 어떻게 할지 정하는 함수인데
아래의 함수를 통해서 문제를 해결했다 해결한 코드는 아래와 같다.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool
*** 이와 같은 방법으로 했을때 아래와 같이 해결이 된다. 보는바와 같이 다시 viewWillAppear이 호출되지 않는다. 라이프 사이클 관리를
내가 원하는 동작대로 관리 할 수 있다.
//
// MainViewController.swift
// BottomTapMemoryAndLifeCycleOptimization
//
// Created by 도학태 on 2023/04/18.
//
import Foundation
import UIKit
import RxSwift
import RxCocoa
import PanModal
class MainTapBarViewController : UITabBarController {
static let KOTLIN = 0
static let SWIFT = 1
static let META = 2
static let JAVA = 3
static let CSHARP = 4
var isSelectConrifm = true
var mViewControllers : [String : UIViewController] = [:]
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
attribute()
bind()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
launchScreen()
}
func bind() {
let emptyVc = UIViewController()
viewControllers = [
generateNavController(emptyVc, "Kotil", UIImage(systemName: "circle.square")),
generateNavController(emptyVc, "Swift", UIImage(systemName: "flag.2.crossed.circle.fill")),
generateNavController(emptyVc, "Meta", UIImage(systemName: "person.fill")),
generateNavController(emptyVc, "Java", UIImage(systemName: "train.side.front.car")),
generateNavController(emptyVc, "CSharp", UIImage(systemName: "lock.display")),
]
}
/*
초기 화면을 정한다.
UITabbarViewController가 viewDidLoad에서 초기화가 완료된 이후에
lanchScreen 호출 -> viewWillAppear에서 호출
*/
func launchScreen() {
if isSelectConrifm {
/*
초기 화면 SWIFT 설정
*/
self.selectedIndex = MainTapBarViewController.SWIFT
self.viewControllers?[MainTapBarViewController.SWIFT] = createViewController(MainTapBarViewController.SWIFT)
isSelectConrifm.toggle()
}
}
/*
TabBar에 ViewController 등록하는 함수
*/
func generateNavController(
_ vc : UIViewController,
_ title : String,
_ image : UIImage?
) -> UINavigationController {
vc.view.backgroundColor = .white
let navController = UINavigationController(rootViewController: vc)
let tabBarItem = UITabBarItem(title: title, image: image, tag: 0)
navController.tabBarItem = tabBarItem
return navController
}
func attribute() {
self.delegate = self
}
}
extension MainTapBarViewController : UITabBarControllerDelegate {
/*
ViewController 생성
*/
func createViewController(_ index : Int) -> UIViewController {
switch index {
case MainTapBarViewController.KOTLIN:
if mViewControllers["KOTLIN"] == nil {
let vc = KotlinViewController()
vc.view.backgroundColor = .white
vc.tabBarItem = UITabBarItem(title: "Kotlin", image: UIImage(systemName: "circle.square"), selectedImage: nil)
mViewControllers["KOTLIN"] = UINavigationController(rootViewController: vc)
}
return mViewControllers["KOTLIN"] ?? UIViewController()
case MainTapBarViewController.SWIFT:
if mViewControllers["SWIFT"] == nil {
let vc = SwiftViewController()
vc.view.backgroundColor = .white
vc.tabBarItem = UITabBarItem(title: "Swift", image: UIImage(systemName: "flag.2.crossed.circle.fill"), selectedImage: nil)
mViewControllers["SWIFT"] = UINavigationController(rootViewController: vc)
}
return mViewControllers["SWIFT"] ?? UIViewController()
case MainTapBarViewController.CSHARP:
if mViewControllers["CSHARP"] == nil {
let vc = CSharpViewController()
vc.view.backgroundColor = .white
vc.tabBarItem = UITabBarItem(title: "CSharp", image: UIImage(systemName: "lock.display"), selectedImage: nil)
mViewControllers["CSHARP"] = UINavigationController(rootViewController: vc)
}
return mViewControllers["CSHARP"] ?? UIViewController()
default:
return UIViewController()
}
}
/*
Page 전환 로직이 담겨 있는 함수
Bottom Tap 할때 이 함수가 호출
*/
func translatePage(_ selectIndex : Int) {
/*
정상적으로 페이지 전환해야 되는 Page에는 LastIndex를 넣어준다
Page전환이 되면 안되는 페이지는 LastIndex를 넣어주지 않고 LastIndex에 있는 값으로 페이지 유지하고
새로운 페이지를 띄운다.
*/
switch selectIndex {
case MainTapBarViewController.META:
let metaVc = MetaViewController()
metaVc.view.backgroundColor = .white
let navVc = UINavigationController(rootViewController: metaVc)
navVc.modalTransitionStyle = .coverVertical
navVc.modalPresentationStyle = .fullScreen
self.present(navVc, animated: true)
case MainTapBarViewController.JAVA:
let javaVc = JavaViewController()
javaVc.view.backgroundColor = .white
self.presentPanModal(javaVc)
default:
break
}
}
/*
Button Tap 관련 함수
*/
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
/*
META와 JAVA는 화면 전환을 막고 나머지는 화면전환을 해준다.
또한 META와 JAVA에서는 화면 전환을 막기 전 전환 처리를 해줬다.
*/
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
switch index {
case MainTapBarViewController.META, MainTapBarViewController.JAVA:
self.translatePage(index ?? 0)
return false
default:
return true
}
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
/*
Kotlin, SWIFT, CSHARP는 버튼탭을 할때 emptyViewController에서 각각의 ViewController로 바꿔준다
즉 Create함수에서 보다 싶이 특정 버튼을 탭했을때 생성한다.
*/
if selectedIndex == MainTapBarViewController.KOTLIN || selectedIndex == MainTapBarViewController.SWIFT || selectedIndex == MainTapBarViewController.CSHARP {
viewControllers?[selectedIndex] = createViewController(selectedIndex)
}
}
}
오늘은 BottoTap에서 어떻게 메모리를 효율적으로 관리할것이가, 라이프 사이클은 특정 상황에서 내가 원하는 동작으로 어떻게 동작하게 만들 수 있을까에 대해서 글을 작성해봤다. 내가 가장 중요하게 생각하는 부분은 판단 부분이다. 나에게 개발을 가르켜주신 우리 교수님께서는 나에게 항상
강조한 부분이 개발자는 코딩을 하는 사람이기 이전에 생각을 하는 사람이라고 하셨다. 결국 이러한 관점에서 봤을때 언어나 다른 모든 수단은
도구에 불가하다고.. 아직도 그 말씀을 기억하면서 개발을 하고 있다. 아직 부족하지만 생각의 사고를 넓혀 문제를 해결하고 이를 코드로 어떻게
구현할수 있을까를 끊임없이 고민하고 있다.