이번 포스팅에선 Floating TabBar를 구현해보고자 한다.

전체 코드는 여기에서 볼 수 있다.

 

처음에는 UIKit에서 제공하는 UITabBarController를 이용하고자 했다.

https://stackoverflow.com/questions/65771446/how-to-create-floating-tab-bar-swift

 

How to create floating Tab Bar Swift

I'm trying to create a Pinterest style Tab Bar. Is it possible to customise UITabBar in that way or do you have to write a separate view to sit on top of everything if so how would you do that? It's

stackoverflow.com

 

해당 StackOverFlow를 참고하고자 하였는지만, 결국엔 UIBeizerPath를 통해 TabBar위에 그리는 방식이였다.

기본적으로 TabBar의 item은 "TabBar의 영역"에 추가되기 때문에,

item들의 위치도 조정해주어야 한다.

또한, 내부 아이템들의 위치를 미세하게 조정하기에는 어렵다고 판단하여 

Custom하는 방법을 선택했다.

 

 

Custom Floating TabBar UI

UIViewController를 상속받은 ViewController를 준비한다.

class RootTabBarController: UIViewController

 

다음으로, FloatingTabBar 역할을 해줄 View를

안에 아이템들의 커스텀을 더 용이하게 하기 위해서

UIView를 사용했다.

/// Floating TabBar 
private let floatingTabBar: UIView = {
	let view = UIView()
	view.translatesAutoresizingMaskIntoConstraints = false
	view.backgroundColor = .systemGray6
	view.layer.cornerRadius = 20
	
	return view
}()

 

다음 탭바 안에 버튼을 구현한 후 원하는 레이아웃을 잡아준다.

/// 첫번째 TabBar Item
private let firstTabButton: TabBarButton = {
	let image = UIImage(
		systemName: "person.3.sequence",
		withConfiguration: UIImage.SymbolConfiguration(pointSize: 20)
	)
	
	let button = TabBarButton(title: "친구", image: image)
	button.translatesAutoresizingMaskIntoConstraints = false
	
	return button
}()

/// 두번째 TabBar Item
private let secondTabButton: TabBarButton = {
	...
}()

/// 세번째 TabBar Item
private let thirdTabButton: TabBarButton = {
	...
}()

 

 

Custom Floating TabBar Logic

우선, 각 TabBar 버튼에 tag를 붙여주자.

class RootTabBarController: UIViewController {
	
	private var tabBarItems: [TabBarButton] = []
	private var viewControllers: [UIViewController] = []
	...
	
	override func viewDidLoad() {
		...
		self.tabBarItems = [firstTabButton, secondTabButton, thirdTabButton]
		
		for (index, item) in tabBarItems.enumerated() {
			item.tag = index
		}
	}
}

다음으로 자식 ViewController들을 추가해 주자.

let friendsVc =  FriendsViewController()
let chattingVc = ChattingViewController()
let profileVc = ProfileViewController()

self.viewControllers = [friendsVc, chattingVc, profileVc]

 

우선 전체적인 로직은 다음과 같다. 

이전에 선택되었던 Index와 클릭한 버튼의 tag가 다르다면!

이전 Index의 ViewController는 제거하고 새로운 ViewController를 띄운다.

 

기존 자식뷰 제거

func removeViewControllerFromParent(_ index: Int) {
	let previousVC = viewControllers[index]
	previousVC.willMove(toParent: nil)
	previousVC.view.removeFromSuperview()
	previousVC.removeFromParent()
}

 

새로운 자식 뷰 띄우기

func attachViewControllerToParent(_ index: Int) {
	let viewController = viewControllers[index]
	viewController.view.frame = view.frame
	viewController.didMove(toParent: self)
	self.addChild(viewController)
	self.view.addSubview(viewController.view)
	self.view.bringSubviewToFront(floatingTabBar)
}

해당 로직은 다음과 같다. 새로 띄울 뷰의 부모를 RootTabBarController로 설정하고,

뷰를 띄운다. 

이후 bringSubViewToFront()를 통해 floatingTabBar를 맨 앞으로 가져온다.

 

마지막으로 버튼의 액션 로직은 다음과 같다. 

@objc func tabBarItemDidTap(_ button: TabBarButton) {
	guard self.selectedIndex != button.tag else { return }
	
	removeViewControllerFromParent(selectedIndex)
	attachViewControllerToParent(button.tag)
	
	self.selectedIndex = button.tag
}

 

TabBar Hide, Show Action

프로토콜을 이용하여 자식 ViewController들은 

부모 ViewController (RootTabBarController)를 몰라도 

TabBar를 hide하고 show할 수 있도록 구현하고자 했다.

 

public protocol FloatingTabBarPresentable {
	func presentTabBar()
	func dimissTabBar()
}

public extension UIViewController {
	func hideTabBar() {
		guard let parent = self.parent as? FloatingTabBarPresentable else { return }
		parent.dimissTabBar()
	}
	
	func showTabBar() {
		guard let parent = self.parent as? FloatingTabBarPresentable else { return }
		parent.presentTabBar()
	}
}

이후 RootTabBarControllerFloatingTabBarPresentable를 채택하고 해당 메서드를 구현하면 된다.

extension RootTabBarController {
	public func dimissTabBar() {
		UIView.animate(
			withDuration: 0.3,
			delay: 0,
			options: .curveLinear
		) { [weak self] in
			guard let self = self else { return }
			
			self.floatingTabBar.frame = CGRect(
				x: self.floatingTabBar.frame.origin.x,
				y: self.view.frame.height + self.view.safeAreaInsets.bottom + 16,
				width: self.floatingTabBar.frame.width,
				height: self.floatingTabBar.frame.height
			)
			self.view.layoutIfNeeded()
			}
	}
	
	public func presentTabBar() {
		UIView.animate(
			withDuration: 0.3,
			delay: 0,
			options: .curveLinear
		) { [weak self] in
			guard let self = self else { return }
			
			self.floatingTabBar.frame = CGRect(
				x: self.floatingTabBar.frame.origin.x,
				y: self.view.frame.height - floatingTabBar.frame.height - 32,
				width: self.floatingTabBar.frame.width,
				height: self.floatingTabBar.frame.height
			)
			self.view.layoutIfNeeded()
			}
	}
}

 

 

Reference

https://medium.com/@prabtanimehul.86/custom-floating-tab-bar-ios-swift-4-2-24abc6f29a70

복사했습니다!