如何在自定义UITabBar中用UIButton实现SF Symbols点击动画?
自定义TabBar实现SF Symbols点击动画需求
我用以下代码创建了自定义TabBar:
class TabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() generateTabBar() setupTabBarAppearance() } func generateTabBar() { viewControllers = [ generateVC(controller: UINavigationController(rootViewController: TableViewController()), title: TableViewController().title!, image: TableViewController().image), generateVC(controller: UINavigationController(rootViewController: FavoritesController()), title: FavoritesController().title!, image: FavoritesController().image), generateVC(controller: UINavigationController(rootViewController: SearchViewController()), title: SearchViewController().title!, image: SearchViewController().image)] } func generateVC(controller: UIViewController, title: String, image: UIImage?) -> UIViewController { controller.tabBarItem.title = title controller.tabBarItem.image = image return controller } private func setupTabBarAppearance(){ tabBar.backgroundImage = UIImage() tabBar.backgroundColor = .clear tabBar.shadowImage = UIImage() tabBar.itemWidth = (tabBar.bounds.width - 10) / 5 tabBar.itemPositioning = .centered tabBar.tintColor = .green tabBar.unselectedItemTintColor = .red let tabBarView = UIView() view.addSubview(tabBarView) tabBarView.backgroundColor = .white tabBarView.translatesAutoresizingMaskIntoConstraints = false let scenes = UIApplication.shared.connectedScenes let windowScene = scenes.first as? UIWindowScene let window = windowScene?.windows.first let safeArea = window?.safeAreaInsets.top tabBarView.frame = CGRect(x: 32, y: window!.frame.size.height - tabBar.frame.size.height - SafeArea().bottom() - 5, width: tabBar.frame.size.width - 64, height: tabBar.frame.size.height + 10) view.bringSubviewToFront(tabBar) } }
我的TabBar使用SF Symbols作为tabBarItem的图片,希望点击tab项时实现SF Symbols动画。已知需要用UIImageView替代UIImage,用UIButton替代tabBarItem,请问如何添加可展示SF Symbols点击动画的UIButton来替代tabBarItem?
解决方案
1. 隐藏系统TabBarItem,搭建自定义按钮容器
先修改setupTabBarAppearance方法,隐藏系统默认的tabBarItem,同时创建一个自定义容器视图承载按钮,替代原有tabBarItem的展示区域:
private func setupTabBarAppearance(){ // 清空系统tabBar的视觉元素 tabBar.isTranslucent = true tabBar.backgroundImage = UIImage() tabBar.shadowImage = UIImage() // 隐藏系统自带的tabBarItem控件 tabBar.subviews.forEach { subview in if subview is UIControl { subview.isHidden = true } } // 创建自定义tab按钮的容器视图 let customTabContainer = UIView() customTabContainer.backgroundColor = .white customTabContainer.layer.cornerRadius = (tabBar.frame.height + 10)/2 view.addSubview(customTabContainer) // 用AutoLayout替代硬编码frame,适配不同屏幕 customTabContainer.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ customTabContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32), customTabContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32), customTabContainer.bottomAnchor.constraint(equalTo: tabBar.topAnchor, constant: 5), customTabContainer.heightAnchor.constraint(equalToConstant: tabBar.frame.height + 10) ]) // 添加上自定义按钮 setupCustomTabButtons(in: customTabContainer) }
2. 封装带SF Symbols动画的自定义按钮
创建一个AnimatedTabButton类,把SF Symbols的展示、状态切换和动画逻辑封装进去:
class AnimatedTabButton: UIButton { // 自定义选中/未选中的颜色 var selectedTintColor: UIColor = .green var unselectedTintColor: UIColor = .red init(symbolName: String, title: String) { super.init(frame: .zero) setupButton(symbolName: symbolName, title: title) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupButton(symbolName: String, title: String){ // 配置SF Symbols图标 let symbolConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .regular) let icon = UIImage(systemName: symbolName, withConfiguration: symbolConfig) setImage(icon, for: .normal) // 设置标题和颜色 setTitle(title, for: .normal) setTitleColor(unselectedTintColor, for: .normal) setTitleColor(selectedTintColor, for: .selected) imageView?.tintColor = unselectedTintColor // 调整图标和标题的布局间距 titleLabel?.font = UIFont.systemFont(ofSize: 12) imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0) titleEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0) } // 切换选中状态并触发动画 func setSelected(_ isSelected: Bool, animated: Bool = true){ self.isSelected = isSelected imageView?.tintColor = isSelected ? selectedTintColor : unselectedTintColor if animated { playTapAnimation() } } // SF Symbols点击动画:缩放+旋转组合 private func playTapAnimation(){ guard let imageView = imageView else { return } // 先放大再恢复 UIView.animate(withDuration: 0.15) { imageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) } completion: { _ in UIView.animate(withDuration: 0.15) { imageView.transform = .identity } // 追加旋转动画 UIView.animate(withDuration: 0.3) { imageView.transform = CGAffineTransform(rotationAngle: .pi) } completion: { _ in UIView.animate(withDuration: 0.3) { imageView.transform = .identity } } } } }
3. 在TabBarController中绑定按钮与页面切换
添加setupCustomTabButtons方法,创建按钮并关联页面切换逻辑:
private func setupCustomTabButtons(in container: UIView){ // 定义每个tab的配置:SF Symbols名称、标题、对应控制器 let tabConfigs = [ (symbol: "list.bullet", title: TableViewController().title!, controller: UINavigationController(rootViewController: TableViewController())), (symbol: "star", title: FavoritesController().title!, controller: UINavigationController(rootViewController: FavoritesController())), (symbol: "magnifyingglass", title: SearchViewController().title!, controller: UINavigationController(rootViewController: SearchViewController())) ] var buttons = [AnimatedTabButton]() tabConfigs.forEach { config in let button = AnimatedTabButton(symbolName: config.symbol, title: config.title) buttons.append(button) container.addSubview(button) // 绑定点击事件 button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside) } // 用AutoLayout给按钮做均分布局 buttons.enumerated().forEach { index, button in button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ button.topAnchor.constraint(equalTo: container.topAnchor), button.bottomAnchor.constraint(equalTo: container.bottomAnchor), button.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 1/CGFloat(tabConfigs.count)) ]) if index == 0 { button.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true } else { button.leadingAnchor.constraint(equalTo: buttons[index-1].trailingAnchor).isActive = true } } // 默认选中第一个tab buttons.first?.setSelected(true, animated: false) self.viewControllers = tabConfigs.map({ $0.controller }) } @objc private func tabButtonTapped(_ sender: AnimatedTabButton){ // 找到当前点击按钮的索引 guard let buttons = container.subviews.compactMap({ $0 as? AnimatedTabButton }), let index = buttons.firstIndex(of: sender) else { return } // 更新所有按钮的选中状态 buttons.forEach { button in button.setSelected(button == sender) } // 切换对应页面 selectedIndex = index }
4. 清理原有代码
移除原有的generateTabBar方法,修改viewDidLoad只保留必要调用:
override func viewDidLoad() { super.viewDidLoad() setupTabBarAppearance() }
内容的提问来源于stack exchange,提问作者user25462420
相关产品推荐
相关产品推荐

