You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 15:04:52