iOS/UIKIT

Custom TabBar 설정

밤새는 탐험가89 2024. 6. 29. 10:03

 

 

  • 오토레이아웃을 통해 플로팅? 약간 떠 있는 탭바를 만들어봤다. 

CustomTabBar.swift

import UIKit

class CustomTabBar: UIView {
    
    // MARK: UI Components
    
    // 버튼을 담을 스택뷰 생성
    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.distribution = .equalSpacing
        stack.alignment = .center
        stack.backgroundColor = .systemBackground
        stack.layer.cornerRadius = 25
        stack.isLayoutMarginsRelativeArrangement = true
        stack.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
        return stack
    }()
    
    
    // 스택뷰에 들어갈 버튼 생성
    private let homeButton = createButton(systemName: "house.fill")
    private let searchButton = createButton(systemName: "magnifyingglass.circle.fill")
    private let addButton = createButton(systemName: "plus.circle.fill")
    private let savedButton = createButton(systemName: "bookmark.fill")
    private let profileButton = createButton(systemName: "person.circle.fill")
    
    // 버튼이 눌렸을 때, 이를 확인하기 위한 변수 생성
    private var selectedButton: UIButton?
    
    
    // 데이터 전달을 위한 클로저 생성
    var onButtonTapped: ((Int) -> Void)?
    
    
    // MARK: Life Cycle
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        // Set initial selected button
        buttonTapped(homeButton)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    
    
    private func setupView() {
        addSubview(stackView)
        stackView.addArrangedSubview(homeButton)
        stackView.addArrangedSubview(searchButton)
        stackView.addArrangedSubview(addButton)
        stackView.addArrangedSubview(savedButton)
        stackView.addArrangedSubview(profileButton)
        
        homeButton.tag = 0
        searchButton.tag = 1
        addButton.tag = 2
        savedButton.tag = 3
        profileButton.tag = 4
        
        [homeButton, searchButton, addButton, savedButton, profileButton].forEach { button in
            button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        }
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: topAnchor),
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
    
    @objc private func buttonTapped(_ sender: UIButton) {
        // Deselect the previous selected button
        if let selectedButton = selectedButton {
            selectedButton.tintColor = .label
        }
        
        // Select the new button
        sender.tintColor = .systemTeal

        selectedButton = sender
        
        // Trigger the callback
        onButtonTapped?(sender.tag)
    }
    
    
    // 버튼을 생성하는 함수
    private static func createButton(systemName: String) -> UIButton {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: systemName), for: .normal)
        button.tintColor = .label

        return button
    }
}

 

 

buttonTapped 함수

    @objc private func buttonTapped(_ sender: UIButton) {
        // Deselect the previous selected button
        if let selectedButton = selectedButton {
            selectedButton.tintColor = .label
        }
        
        // Select the new button
        sender.tintColor = .systemTeal

        selectedButton = sender
        
        // Trigger the callback
        onButtonTapped?(sender.tag)
    }

 

 

  • @objc 키워드:
    • Objective-C 런타임에 이 메소드를 노출시킵니다.
    • UIButton의 target-action 메커니즘을 사용하기 위해 필요합니다.
  • private func:
    • 이 메소드는 클래스 내부에서만 사용됨을 나타냅니다.
  • if let selectedButton = selectedButton { ... }:
    • 이전에 선택된 버튼이 있다면 그 버튼의 색상을 기본값(.label)으로 되돌립니다.
    • 이는 이전에 선택된 버튼의 하이라이트를 제거하는 역할을 합니다.
  • sender.tintColor = .systemTeal:
    • 현재 탭된 버튼의 색상을 .systemTeal로 변경합니다.
    • 이는 현재 선택된 탭을 시각적으로 표시합니다.
  • selectedButton = sender:
    • 현재 선택된 버튼을 selectedButton 프로퍼티에 저장합니다.
    • 이는 다음 탭 선택 시 이전 선택을 해제하기 위해 사용됩니다.
  • onButtonTapped?(sender.tag):
    • onButtonTapped 클로저가 설정되어 있다면 호출합니다.
    • sender.tag를 인자로 전달하여 어떤 버튼이 탭되었는지 알려줍니다.
    • 이 클로저는 일반적으로 CustomTabBarController에서 탭 전환 로직을 처리하는 데 사용됩니다.

 

CustomTabBarController.swift

import UIKit

class CustomTabBarController: UITabBarController {
    
    private let customTabBar = CustomTabBar()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCustomTabBar()
        setupViewControllers()
        
        view.backgroundColor = .systemTeal
    }
    
    private func setupCustomTabBar() {
        // 기본 탭바 숨기기
        tabBar.isHidden = true
        
        // 커스텀 탭바 추가
        view.addSubview(customTabBar)
        customTabBar.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            customTabBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            customTabBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            customTabBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -15),
            customTabBar.heightAnchor.constraint(equalToConstant: 60)
        ])
        
        // 커스텀 탭바 버튼에 액션 추가
        customTabBar.onButtonTapped = { [weak self] index in
            self?.selectedIndex = index
            
        }
    }
    
    private func setupViewControllers() {
        // 각 탭에 해당하는 뷰 컨트롤러 설정
        let homeVC = UINavigationController(rootViewController: HomeViewController())
        //homeVC.view.backgroundColor = .white
        
        let searchVC = UINavigationController(rootViewController: SearchViewController())
        //searchVC.view.backgroundColor = .lightGray
        
        let addPlanVC = UINavigationController(rootViewController: AddPlanViewController())
        //addPlanVC.view.backgroundColor = .gray
        
        let bookmarkVC = UINavigationController(rootViewController: BookmarkViewController())
        //bookmarkVC.view.backgroundColor = .darkGray
        
        let profileVC = UINavigationController(rootViewController: ProfileViewController())
        //profileVC.view.backgroundColor = .black
        
        viewControllers = [homeVC, searchVC, addPlanVC, bookmarkVC, profileVC]
    }
}

 

 

 

클로저 호출 

// 커스텀 탭바 버튼에 액션 추가
customTabBar.onButtonTapped = { [weak self] index in
    self?.selectedIndex = index

}

 

  1. customTabBar.onButtonTapped = { ... }:
    • 이는 CustomTabBar 클래스의 onButtonTapped 속성에 클로저를 할당하는 것입니다.
    • 이 클로저는 탭바의 버튼이 탭될 때마다 호출됩니다.
  2. [weak self]:
    • 이는 클로저 내에서 self에 대한 약한 참조를 생성합니다.
    • 메모리 순환 참조(retain cycle)를 방지하기 위해 사용됩니다.
  3. index in:
    • 이는 클로저가 받는 매개변수입니다.
    • index는 탭된 버튼의 인덱스(또는 태그)를 나타냅니다.
  4. self?.selectedIndex = index:
    • self는 CustomTabBarController를 가리킵니다.
    • selectedIndex는 UITabBarController의 속성입니다.

selectedIndex에 대해 자세히 설명하겠습니다:

  • selectedIndex는 UITabBarController의 속성입니다.
  • 이 속성은 현재 선택된 탭의 인덱스를 나타냅니다.
  • 이 값을 변경하면 해당 인덱스의 뷰 컨트롤러로 화면이 전환됩니다.
  • 인덱스는 0부터 시작합니다. (예: 첫 번째 탭은 0, 두 번째 탭은 1, ...)

이 코드의 전체적인 동작은 다음과 같습니다:

  1. 사용자가 커스텀 탭바의 버튼을 탭합니다.
  2. CustomTabBar에서 해당 버튼의 인덱스(또는 태그)를 이 클로저로 전달합니다.
  3. 클로저는 전달받은 인덱스를 UITabBarController의 selectedIndex에 할당합니다.
  4. 결과적으로, 탭된 버튼에 해당하는 뷰 컨트롤러로 화면이 전환됩니다.