iOS

[iOS] TabBar + Navigation의 모든것

yevdev 2022. 10. 19. 11:23

1️⃣ Navigation, TabBar Embeded!

- UINavigationContoller : Cocoa Touch 파일 → UINavigationController

- UITabBarController : Cocoa Touch 파일 → UITabBarController

 

2️⃣ Navigation을 TabBar 안에 속하게!

- Navigation View를 initial로

이렇게 연결

- TabBar안에 여러개의 view 연결

 

 


 

 

TabBar에 따라 " Navigation Bar Custom " 하기

import UIKit

class MainTabBarViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 네비게이션 좌측
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "헬로", style: .plain, target: nil, action: nil)
        // 네비게이션 우측
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
    }

}

구현 이미지

 

Delegate
- 언제 무엇이 탭되었는지 알 수 있음

 

 

 

1️⃣ 탭이 눌릴때마다 네비게이션 바 아이콘 바꾸기

//  MainTabBarViewController.swift

import UIKit

// 탭이 눌릴때마다, 그에 맞는 네비게이션 바를 구성
// - 탭이 눌리는 것을 감지
// - 감지 후에, 그 탭에 맞게 네비게이션 바 구성을 업데이트

class MainTabBarViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 네비게이션 좌측
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "헬로", style: .plain, target: nil, action: nil)
        // 네비게이션 우측
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
        
        // 탭이 눌리는 것을 감지
        delegate = self
    }
}

// 어떤 Controller가 탭 되었는지 알 수 있음
extension MainTabBarViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        
        print("--> 어떤 \(viewController)")
        
        switch viewController {
        case is HomeViewController:
            
            let titleItem = UIBarButtonItem(title: "헬로", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is MyTownViewController:
            
            let titleItem = UIBarButtonItem(title: "공릉동", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is ChatViewController:
            
            let titleItem = UIBarButtonItem(title: "채팅", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is MyProfileViewController:
            
            let titleItem = UIBarButtonItem(title: "나의 당근", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        default:
            
            let titleItem = UIBarButtonItem(title: "당근당근", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
        }
    }
}

 

2️⃣ UIBarButtonItem을 CustomView로 만들기 ( 원하는 폰트, 이미지, 액션 세팅 )

1. 원하는 폰트

//  CustomBarItem.swift
//  -> CustomView

import Foundation
import UIKit


struct CustomBarItemConfiguration {
    
    // 표현이 복잡하거나 한눈에 보기 어려울 때 typealias 사용
    // closer 타입을 "Handler" 라는 이름의 타입으로 재설정
    typealias Handler = () -> Void
    
    let title: String?
    let image: UIImage?
    let handler: Handler
    
    init(title: String? = nil, image: UIImage? = nil, handler: @escaping Handler) {
        self.title = title
        self.image = image
        self.handler = handler
    }
}


// CustomBarButton을 위한 UIButton을 상속한 CustomBarItem
final class CustomBarItem: UIButton {
    // title, image, target action 필요
        
    var customBarItemConfig: CustomBarItemConfiguration
    
    init(config: CustomBarItemConfiguration) {
        self.customBarItemConfig = config
        super.init(frame: .zero)
        setupStyle()
        updateConfig()

    }
    
    private func setupStyle() {
        self.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
        self.titleLabel?.textColor = .white
        self.imageView?.tintColor = .white
        
    }
    
    private func updateConfig() {
        self.setTitle(customBarItemConfig.title, for: .normal)
        self.setImage(customBarItemConfig.image, for: .normal)
    }
    
    @objc func buttonTapped() {
        customBarItemConfig.handler()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

2. 원하는 이미지

//
//  MainTabBarViewController.swift

import UIKit

// 탭이 눌릴때마다, 그에 맞는 네비게이션 바를 구성
// - 탭이 눌리는 것을 감지
// - 감지 후에, 그 탭에 맞게 네비게이션 바 구성을 업데이트

class MainTabBarViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 네비게이션 좌측
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "헬로", style: .plain, target: nil, action: nil)
        // 네비게이션 우측
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
        
        // UIBarButtonItem을 CustomView로 만들기
        // - 사용자 클릭(interaction이 가능하게)
        
        
        // 탭이 눌리는 것을 감지
        delegate = self
    }
}

// 각 탭에 맞게 네비게이션 아이템 구성하기
// - 홈: 타이틀, 피드, 서치
// - 동네활동: 타이틀, 피드
// - 내 근처 : 타이틀
// - 채팅: 타이틀, 피드
// - 나의 당근: 타이틀, 설정

// 어떤 Controller가 탭 되었는지 알 수 있음
extension MainTabBarViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        
        print("--> 어떤 \(viewController)")
        
        switch viewController {
        case is HomeViewController:
            
            let titleConfig = CustomBarItemConfiguration(title: "공릉동", handler: { })
            let customTitleView = CustomBarItem(config: titleConfig)
            let titleItem = UIBarButtonItem(customView: customTitleView)
            
            let searchConfig = CustomBarItemConfiguration( image:UIImage(systemName: "magnifyingglass"), handler: {print("--> search tapped")})
            let searchView = CustomBarItem(config: searchConfig)
            let searchItem = UIBarButtonItem(customView: searchView)
            
            let feedConfig = CustomBarItemConfiguration( image:UIImage(systemName: "bell"), handler: {print("--> feed tapped")})
            let feedView = CustomBarItem(config: feedConfig)
            let feedItem = UIBarButtonItem(customView: feedView)


            navigationItem.leftBarButtonItem = titleItem
            // 아이템 여러개 설정할 때는 Items로 하고 배열로 넣어주기
            navigationItem.rightBarButtonItems = [feedItem, searchItem]
            
        case is MyTownViewController:
            
            let titleItem = UIBarButtonItem(title: "공릉동", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is ChatViewController:
            
            let titleItem = UIBarButtonItem(title: "채팅", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is MyProfileViewController:
            
            let titleItem = UIBarButtonItem(title: "나의 당근", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        default:
            
            let titleItem = UIBarButtonItem(title: "당근당근", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
        }
    }
}

구현 이미지

 

3. 돋보기랑 아이콘 사이 간격 벌리기

NSLayoutConstraint.activate([
    searchView.widthAnchor.constraint(equalToConstant: 30)
])
NSLayoutConstraint.activate([
    feedView.widthAnchor.constraint(equalToConstant: 30)
])

 

 

3️⃣ UIBarButtonItem → extension으로 중복 코드 줄이기

let searchView = CustomBarItem(config: searchConfig)
NSLayoutConstraint.activate([
    searchView.widthAnchor.constraint(equalToConstant: 30)
])
let searchItem = UIBarButtonItem(customView: searchView)

↑ 코드를 줄여보자!

 

extension 할 파일 하나 더 만들기 → UIBarButtonItem + CustomView

// UIBarButtonItem+CustomView

import Foundation
import UIKit

extension UIBarButtonItem {
    static func generate(with config: CustomBarItemConfiguration, width: CGFloat?) -> UIBarButtonItem {
        let customView = CustomBarItem(config: config)
        
        if let width = width {
            NSLayoutConstraint.activate([
                customView.widthAnchor.constraint(equalToConstant: width)
            ])
        }
        
        let barButtonItem = UIBarButtonItem(customView: customView)
        return barButtonItem
    }
}
case is HomeViewController:
	
	let titleConfig = CustomBarItemConfiguration(
	    title: "공릉동",
	    handler: { }
	)
	let titleItem = UIBarButtonItem.generate(with: titleConfig, width: nil)
	
	let searchConfig = CustomBarItemConfiguration(
	    image:UIImage(systemName: "magnifyingglass"),
	    handler: {print("--> search tapped")}
	)
	let searchItem = UIBarButtonItem.generate(with: searchConfig, width: 30)
	
	let feedConfig = CustomBarItemConfiguration(
	    image:UIImage(systemName: "bell"),
	    handler: {print("--> feed tapped")}
	)
	let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
	
	
	navigationItem.leftBarButtonItem = titleItem
	navigationItem.rightBarButtonItems = [feedItem, searchItem] // 아이템 여러개 설정할 때는 Items로 하고 배열로 넣어주기

↑ 이렇게 줄어듦!

 

 

 

4️⃣ viewController에 따라서 navigation 업데이트 : 앱이 시작할 때, 네비게이션 아이템 설정을 완료

- viewWillAppear

//  MainTabBarViewController.swift

import UIKit

// 앱이 시작할 때, 네비게이션 아이템 설정을 완료하고 싶다.
class MainTabBarViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        
    }
    
    // TabBarContoller가 보여지기 시작하는 시점에
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        updateNavigationItem(vc: self.selectedViewController!)
    }
    
    // viewController에 따라서 navigation 업데이트
    private func updateNavigationItem(vc: UIViewController) {
        switch vc {
        case is HomeViewController:
            
            let titleConfig = CustomBarItemConfiguration(
                title: "공릉동",
                handler: { }
            )
            let titleItem = UIBarButtonItem.generate(with: titleConfig, width: nil)
            
            let searchConfig = CustomBarItemConfiguration(
                image:UIImage(systemName: "magnifyingglass"),
                handler: {print("--> search tapped")}
            )
            let searchItem = UIBarButtonItem.generate(with: searchConfig, width: 30)

            let feedConfig = CustomBarItemConfiguration(
                image:UIImage(systemName: "bell"),
                handler: {print("--> feed tapped")}
            )
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)


            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem, searchItem] // 아이템 여러개 설정할 때는 Items로 하고 배열로 넣어주기
            
        case is MyTownViewController:
            
            let titleConfig = CustomBarItemConfiguration(
                title: "공릉동",
                handler: { }
            )
            let titleItem = UIBarButtonItem.generate(with: titleConfig, width: nil)
            
            let feedConfig = CustomBarItemConfiguration(
                image:UIImage(systemName: "bell"),
                handler: {print("--> feed tapped")}
            )
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)


            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem] // 아이템 여러개 설정할 때는 Items로 하고 배열로 넣어주기
            
        case is ChatViewController:
            
            let titleConfig = CustomBarItemConfiguration(
                title: "채팅",
                handler: { }
            )
            let titleItem = UIBarButtonItem.generate(with: titleConfig, width: nil)
            let feedConfig = CustomBarItemConfiguration(
                image:UIImage(systemName: "bell"),
                handler: {print("--> feed tapped")}
            )
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)


            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem] // 아이템 여러개 설정할 때는 Items로 하고 배열로 넣어주기
            
        case is MyProfileViewController:
            
            let titleConfig = CustomBarItemConfiguration(
                title: "나의 당근",
                handler: { }
            )
            let titleItem = UIBarButtonItem.generate(with: titleConfig, width: nil)
            let settingConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "gearshape"),
                handler: { print("--> setting tapped") }
            )
            let settingItem = UIBarButtonItem.generate(with: settingConfig, width: 30)

            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [settingItem]

        default:
            
            let titleConfig = CustomBarItemConfiguration(
                title: "나의 당근",
                handler: { }
            )
            let titleItem = UIBarButtonItem.generate(with: titleConfig, width: nil)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = []
            
        }
    }
}

// 어떤 Controller가 탭 되었는지 알 수 있음
extension MainTabBarViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        updateNavigationItem(vc: viewController)
    }
}

 

5️⃣ 상세뷰로 들어갔을 때 navigation style 바꾸기

- Back 이라는 타이틀 삭제

navigationItem.backButtonDisplayMode = .minimal // 타이틀 말고 아이콘만 보이게 됨.

- 화살표 아이콘 커스텀

// MainNavigationViewController.swift

import UIKit

class MainNavigationViewController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let backImage = UIImage(systemName: "arrow.backward")
        navigationBar.backIndicatorImage = backImage
        navigationBar.backIndicatorTransitionMaskImage = backImage
        navigationBar.tintColor = .white
    }
}

구현 이미지

 

 

🚀 구현 완료 화면