iOS) 상단 탭바 구현 (2) - 하단 뷰

상단 메뉴 바를 완성했으니, 메뉴바를 클릭하면, 해당 화면으로 화면이 변경되도록 해야 합니다. 

UITabBarController처럼, Controller를 만들어서 메뉴바와 화면을 가지고 있으면서, 사용자가 원하는 배치로 만들 수 있게 하겠습니다.

 

class CustomTabBarViewController: UIViewController {
   lazy var menuBar: CustomMenuBar = {
       let menuBar = CustomMenuBar()
        menuBar.translatesAutoresizingMaskIntoConstraints = false
        return menuBar
    }()
    
    lazy var pageView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        
        return view
    }()
    
    public var viewControllers: [UIViewController] = [] {
        didSet {
            setMenuBarItems(viewControllers)
        }
    }
    
    private func setMenuBarItems(_ viewControllers: [UIViewController]) {
        var tabItems: [UITabBarItem] = []
        viewControllers.forEach { vc in
            
            vc.view.translatesAutoresizingMaskIntoConstraints = false
            tabItems.append(vc.tabBarItem)
        }
        menuBar.setItems(tabBarItems: tabItems)
    }
}

pageView는 바뀔 화면이 위치할 뷰입니다. 해당 화면이 위치한 곳에, viewControllers의 view를 위치시키고, 보여줍니다.

viewControllers에 원하는 ViewController 배열을 넣어 MenuBar의 아이템을 구성하고, 레이아웃을 잡기 위해 translateAutoresizingMaskIntoConstraints를 false로 설정합니다.

 

메뉴바의 delegate를 채택하여, 메뉴바가 클릭되었을때, 화면이 바뀌는 방식을 정의합니다. UIPageViewController를 이용하는 방법도 있지만, 저는 커스텀 컨테이너 뷰를 만들어서 화면을 전환했습니다.

extension CustomTabBarViewController: CustomMenuBarDelegate {
    
    func didSelect(indexNum: Int) {
        let selectViewController = viewControllers[indexNum]
        if !checkIncludeViewController(vc: selectViewController) {
            addChild(selectViewController)
            pageView.addSubview(selectViewController.view)
            configureConstraintsForContainedView(containedView: selectViewController.view)
            selectViewController.didMove(toParent: self)
        }
        hiddenSubViews()
        pageView.subviews.first { $0.isEqual(selectViewController.view) }?.isHidden = false
    }
    
    private func configureConstraintsForContainedView(containedView: UIView) {
        NSLayoutConstraint.activate([
            containedView.topAnchor.constraint(equalTo: pageView.topAnchor),
            containedView.leadingAnchor.constraint(equalTo: pageView.leadingAnchor),
            containedView.trailingAnchor.constraint(equalTo: pageView.trailingAnchor),
            containedView.bottomAnchor.constraint(equalTo: pageView.bottomAnchor)
        ])
    }
    
    private func checkIncludeViewController(vc: UIViewController) -> Bool {
        if pageView.subviews.contains(where: { $0.isEqual(vc.view) }) {
            return true
        } else {
            return false
        }
    }
    
    private func hiddenSubViews() {
        pageView.subviews.forEach { subView in
            subView.isHidden = true
        }
    }
    
}

여기서 주의해야 할 점은, 뷰에 ViewController의 View를 addSubView를 할 때, 각각 제약조건을 걸어야 합니다. pageView에 맞춰 제약조건을 걸었고, pageView의 subView에 선택한 뷰컨트롤러의 뷰가 있는지 체크한 후 뷰가 없을 경우, addSubView를 합니다. 뷰가 있는 경우에는 해당 뷰를 찾아서 isHidden을 false로 설정하여, 화면에 보이도록 합니다.

 

사용 방법

사용방법은 일반 UITabBarController과 유사합니다. 우선은 CustomTabBarViewController를 상속받은 ViewController를 생성합니다. 그리고 자신이 원하는 위치에 메뉴바와 pageView를 위치시킵니다.

class ViewController: CustomTabBarViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setMenuBarView()
        setPageView()
    }
    
    func setMenuBarView() {
        view.addSubview(menuBar)
        NSLayoutConstraint.activate([
            menuBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            menuBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            menuBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            menuBar.heightAnchor.constraint(equalToConstant: 60)
        ])
    }

    func setPageView() {
        view.addSubview(pageView)
        NSLayoutConstraint.activate([
            pageView.topAnchor.constraint(equalTo: menuBar.bottomAnchor),
            pageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            pageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            pageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
}

그리고 해당 뷰 컨트롤러의 viewControllers에 UIViewController 배열을 만들어 넣습니다. 저는 SceneDelegate에서 ViewController를 생성하고, 배열을 넣으면 됩니다.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let rootViewController = ViewController()
        let firstViewController = FirstViewController()
        firstViewController.tabBarItem.title = "첫번째"
        let secondViewController = SecondViewController()
        secondViewController.tabBarItem.title = "두번째"
        let thirdViewController = ThirdViewController()
        thirdViewController.tabBarItem.title = "세번째"
        rootViewController.viewControllers = [firstViewController, secondViewController, thirdViewController]
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = rootViewController
        
        window?.makeKeyAndVisible()
    }

 

실행영상

뷰 계층을 보면 제가 의도한대로 레이아웃이 잡힌것을 볼 수 있습니다.

프로젝트 링크: https://github.com/Kim-Junhwan/CustomTabBar