在iOS开发中,有时候我们需要自动隐藏或显示UITabBar,本文将用一个完整的案例,演示如何用动画的方式,来切换UITabBar的隐藏和显示!—— 请看️
最终效果:
如果直接设置isHidden属性,效果会显得很生硬!——如果自己实现切换显示和隐藏的效果,应该怎么做呢?
实现思路:
- 在需要【显示】的时候,将TabBar中心点的Y设置成:屏幕高度 - TabBar高度
- 在需要【隐藏】的时候,将TabBar中心点的Y设置成:屏幕高度
最简单的做法,可以给UITabBar加上以下代码:
func changeTabBar(hidden: Bool){
let ScreenHeight = UIScreen.main.bounds.height // 屏幕高度
let tabBarHeight: CGFloat = self.frame.height // TabBar自身的高度
UIView.animate(withDuration: 0.5) {
if hidden {
var frame = self.frame
frame.origin.y = ScreenHeight // TabBar中心点的Y:屏幕高度
self.frame = frame
} else {
var frame = self.frame
frame.origin.y = ScreenHeight - tabBarHeight // TabBar中心点的Y:屏幕高度 - TabBar高度
self.frame = frame
}
}
}
但是,以上这段代码会有一个问题:
当TabBar在隐藏的状态下,屏幕切换横屏或竖屏,TabBar会显示出来!
因此,还需要做横屏和竖屏的判断,下面我们对代码进行一些改进 ……
具体的操作,我们直接看代码(注释已经写的很详细了):
// 显示|隐藏 TabBar 方法
func changeTabBar(hidden:Bool, animated: Bool){
if isAnimating { return } // 如果动画正在执行,则不执行!
if self.isHidden == hidden { return } // 如果已经是指定隐藏状态,则不执行!
/*=========================================*/
// 修复在TabBar隐藏的情况下,旋转屏幕动画错乱的Bug!
if self.isHidden {
self.frame = CGRect(
x: frame.minX,
y: UIScreen.main.bounds.height,
width: frame.width,
height: frame.height
) // 强行修改tabBar的Frame
}
/*=========================================*/
let frame = self.frame // 获取自身的frame
let isOutScreen = Int(UIScreen.main.bounds.height - self.frame.minY) == 0 // 是否在屏幕外面,以此为判断动画方向的依据!
let a: CGFloat = isOutScreen ? -1 : 1 // 定义动画方向 | 上下
let offset = a * frame.size.height // 设置动画偏移量
let duration: TimeInterval = (animated ? 0.5 : 0.0) // 定义动画持续时间
self.isHidden = false // 开始动画之前,先开启tabBar的显示!
isAnimating = true // 标记动画【正在执行】状态
UIView.animate(
withDuration: duration,
animations: {
self.center.y += offset // 改变【中心点】偏移量!
}) { _ in
self.isHidden = !isOutScreen // 设置tabBar的显示状态
isAnimating = false // 取消动画【正在执行】状态
}
}
如何使用:
首先,将以上方法,直接写到UITabBar的扩展里面,这样方便复用!
然后,在需要控制TabBar隐藏和显示的页面,调用changeTabBar方法
一般情况下,我们是需要在详情页面隐藏TabBar,在返回主页,显示TabBar,因此,我们可以在detailVC页面,重写以下两个方法:
// 页面即将显示的时候,隐藏TabBar!!!
override func viewWillAppear(_ animated: Bool) {
parent?.tabBarController?.tabBar.changeTabBar(hidden: true, animated: true)
}
// 页面即将消失的时候,显示TabBar!!!
override func viewWillDisappear(_ animated: Bool) {
parent?.tabBarController?.tabBar.changeTabBar(hidden: false, animated: true)
}
使用起来,还算简单!
接下来,我们上完整案例代码(似乎有点多,主要都是一些页面布局的东西,但都很简单!-)……
完整代码:
- SceneDelegate.swift
//
// SceneDelegate.swift
// UIKit-basic
//
// Created by Qire_er on 2022/1/11.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let myScene = (scene as? UIWindowScene) else { return }
let windon = UIWindow(windowScene: myScene)
windon.rootViewController = MainTabVC() // 设置根控制器,否则就白忙活了!
self.window = windon
windon.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene) {}
func sceneDidBecomeActive(_ scene: UIScene) {}
func sceneWillResignActive(_ scene: UIScene) {}
func sceneWillEnterForeground(_ scene: UIScene) {}
func sceneDidEnterBackground(_ scene: UIScene) {}
}
- MainTabVC.swift【主TabBar控制器】
//
// MainTabVC.swift
// UIKit-basic
//
// Created by Qire_er on 2022/1/11.
//
import UIKit
class MainTabVC: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let title = ["Chat", "Movie", "Read", "debug"]
// 创建 tab01_VC 控制器
let tab01_VC = TabVC()
tab01_VC.tabBarItem.image = UIImage(systemName: "bubble.left.and.bubble.right")
tab01_VC.tabBarItem.selectedImage = UIImage(systemName: "bubble.left.and.bubble.right.fill")
tab01_VC.tabBarItem.title = title[0]
// 创建 tab02_VC 控制器
let tab02_VC = TabVC()
tab02_VC.tabBarItem.image = UIImage(systemName: "play.rectangle")
tab02_VC.tabBarItem.selectedImage = UIImage(systemName: "play.rectangle.fill")
tab02_VC.tabBarItem.title = title[1]
// 创建 tab03_VC 控制器
let tab03_VC = TabVC()
tab03_VC.tabBarItem.image = UIImage(systemName: "book")
tab03_VC.tabBarItem.selectedImage = UIImage(systemName: "book.fill")
tab03_VC.tabBarItem.title = title[2]
// 创建 tab04_VC 控制器
let tab04_VC = TabVC()
tab04_VC.tabBarItem.image = UIImage(systemName: "ladybug")
tab04_VC.tabBarItem.selectedImage = UIImage(systemName: "ladybug.fill")
tab04_VC.tabBarItem.title = title[3]
let tab01_NC = UINavigationController(rootViewController: tab01_VC) // 创建 tab01_NC 导航控制器
let tab02_NC = UINavigationController(rootViewController: tab02_VC) // 创建 tab02_NC 导航控制器
let tab03_NC = UINavigationController(rootViewController: tab03_VC) // 创建 tab03_NC 导航控制器
let tab04_NC = UINavigationController(rootViewController: tab04_VC) // 创建 tab04_NC 导航控制器
tab01_NC.title = title[0]
tab02_NC.title = title[1]
tab03_NC.title = title[2]
tab04_NC.title = title[3]
self.tabBar.tintColor = .red // 选中颜色
self.tabBar.unselectedItemTintColor = .systemGray2 // 未选中颜色
self.viewControllers = [tab01_NC, tab02_NC, tab03_NC, tab04_NC]
}
}
private var isAnimating = false
extension UITabBar {
// 显示|隐藏 TabBar 方法
func changeTabBar(hidden:Bool, animated: Bool){
if isAnimating { return } // 如果动画正在执行,则不执行!
if self.isHidden == hidden { return } // 如果已经是指定隐藏状态,则不执行!
/*=========================================*/
// 修复在TabBar隐藏的情况下,旋转屏幕动画错乱的Bug!
if self.isHidden {
self.frame = CGRect(
x: frame.minX,
y: UIScreen.main.bounds.height,
width: frame.width,
height: frame.height
) // 强行修改tabBar的Frame
}
/*=========================================*/
let frame = self.frame // 获取自身的frame
let isOutScreen = Int(UIScreen.main.bounds.height - self.frame.minY) == 0 // 是否在屏幕外面,以此为判断动画方向的依据!
let a: CGFloat = isOutScreen ? -1 : 1 // 定义动画方向 | 上下
let offset = a * frame.size.height // 设置动画偏移量
let duration: TimeInterval = (animated ? 0.5 : 0.0) // 定义动画持续时间
self.isHidden = false // 开始动画之前,先开启tabBar的显示!
isAnimating = true // 标记动画【正在执行】状态
UIView.animate(
withDuration: duration,
animations: {
self.center.y += offset // 改变【中心点】偏移量!
}) { _ in
self.isHidden = !isOutScreen // 设置tabBar的显示状态
isAnimating = false // 取消动画【正在执行】状态
}
}
}
- TabVC.swift【Tab子页】
//
// TabVC.swift
// UIKit-basic
//
// Created by Qire_er on 2022/1/11.
//
import UIKit
class TabVC: UIViewController {
// 一些系统图标的名称
let imgs = ["bubble.left.and.bubble.right.fill", "play.rectangle.fill", "book.fill", "ladybug.fill"]
override func viewDidLoad() {
super.viewDidLoad()
title = tabBarController?.selectedViewController?.title // 设置TabVC标题
self.navigationController?.navigationBar.tintColor = .red // 设置返回按钮颜色
let vStack = UIStackView()
vStack.translatesAutoresizingMaskIntoConstraints = false
vStack.axis = .vertical
vStack.distribution = .fillEqually
vStack.spacing = 8
let radius: CGFloat = 8
for (index, item) in imgs.enumerated() {
let isSelectedIndex = (index == tabBarController!.selectedIndex)
let btn = UIButton()
btn.setImage(UIImage(systemName: item), for: .normal)
btn.tintColor = .white
btn.backgroundColor = isSelectedIndex ? .red : .red.withAlphaComponent(0.05)
// 给按钮添加一点点阴影效果(^-^)
if isSelectedIndex {
btn.setTitle(title, for: .normal)
btn.layer.shadowColor = UIColor.red.cgColor
btn.layer.shadowOpacity = 0.25
btn.layer.shadowOffset = CGSize(width: 5, height: 5)
btn.layer.shadowRadius = 10
}
btn.layer.cornerRadius = radius
vStack.addArrangedSubview(btn)
if isSelectedIndex {
btn.addTarget(self, action: #selector(showDetial), for: .touchUpInside)
}
}
view.addSubview(vStack)
NSLayoutConstraint.activate([
vStack.widthAnchor.constraint(equalToConstant: 100),
vStack.heightAnchor.constraint(equalToConstant: 400),
vStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
vStack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
let btnWidth: CGFloat = 60
let tabBtn = UIButton()
tabBtn.setImage(UIImage(systemName: "arrow.up.arrow.down"), for: .normal)
tabBtn.translatesAutoresizingMaskIntoConstraints = false
tabBtn.tintColor = .white
tabBtn.backgroundColor = .blue.withAlphaComponent(0.75)
tabBtn.layer.cornerRadius = btnWidth * 0.5
tabBtn.addTarget(self, action: #selector(tabBarToggle), for: .touchUpInside)
// 也给它添加一点点阴影效果!
tabBtn.layer.shadowColor = UIColor.blue.cgColor
tabBtn.layer.shadowOpacity = 0.25
tabBtn.layer.shadowOffset = CGSize(width: 5, height: 5)
tabBtn.layer.shadowRadius = 10
view.addSubview(tabBtn)
// 添加约束!
NSLayoutConstraint.activate([
tabBtn.widthAnchor.constraint(equalToConstant: btnWidth),
tabBtn.heightAnchor.constraint(equalToConstant: btnWidth),
tabBtn.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15),
tabBtn.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -120)
])
view.backgroundColor = .white
}
private var isTabBarHidden = false // TabBar是否隐藏
// 切换tabBar【显示|隐藏】按钮handler
@objc private func tabBarToggle() {
isTabBarHidden = !isTabBarHidden
parent?.tabBarController?.tabBar.changeTabBar(hidden: isTabBarHidden, animated: true)
}
// 显示详情页方法
@objc private func showDetial() {
let detailVC = DetailView(image: UIImage(systemName: imgs[tabBarController!.selectedIndex])!) // 顺便把图片信息也传过去~
navigationController?.pushViewController(detailVC, animated: true) // 将detailVC详情页push进去!
}
}
- DetailView.swift【详情页】
//
// DetailView.swift
// UIKit-basic
//
// Created by Qire_er on 2022/1/11.
//
import UIKit
class DetailView: UIViewController {
init(image: UIImage) {
super.init(nibName: nil, bundle: nil)
title = "详情" // 设置页面标题
let imgView = UIImageView(image: image)
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFit
imgView.tintColor = .white
view.addSubview(imgView)
NSLayoutConstraint.activate([
imgView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15),
imgView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15),
imgView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
imgView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
view.backgroundColor = .red
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
// 页面即将显示的时候,隐藏TabBar!!!
override func viewWillAppear(_ animated: Bool) {
parent?.tabBarController?.tabBar.changeTabBar(hidden: true, animated: true)
}
// 页面即将消失的时候,显示TabBar!!!
override func viewWillDisappear(_ animated: Bool) {
parent?.tabBarController?.tabBar.changeTabBar(hidden: false, animated: true)
}
}
以上就是本案例的所有代码,最后,送你一个好东西!(-) ……
(==完==)
ps: 以上仅代表个人浅见,如果你有什么高见,也欢迎讨论交流!-