5

In my Swift app I have this class that handles a UITabBar.

class CustomTabBar: UITabBar {
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}

How can I animate the items when the user tap their? I mean a CGAffine(scaleX: 1.1, y: 1.1) So how I can animate the tab bar's items?

meaning-matters
  • 21,929
  • 10
  • 82
  • 142
Jack K.
  • 83
  • 1
  • 5

4 Answers4

10

First:

create a custom UITabBarController as follows:
import UIKit

enum TabbarItemTag: Int {
    case firstViewController = 101
    case secondViewConroller = 102
}

class CustomTabBarController: UITabBarController {
    var firstTabbarItemImageView: UIImageView!
    var secondTabbarItemImageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let firstItemView = tabBar.subviews.first!
        firstTabbarItemImageView = firstItemView.subviews.first as? UIImageView
        firstTabbarItemImageView.contentMode = .center

        let secondItemView = self.tabBar.subviews[1]
        self.secondTabbarItemImageView = secondItemView.subviews.first as? UIImageView
        self.secondTabbarItemImageView.contentMode = .center
    }

    private func animate(_ imageView: UIImageView) {
        UIView.animate(withDuration: 0.1, animations: {
            imageView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25)
        }) { _ in
            UIView.animate(withDuration: 0.25, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 3.0, options: .curveEaseInOut, animations: {
                imageView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
            }, completion: nil)
        }
    }

    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        guard let tabbarItemTag = TabbarItemTag(rawValue: item.tag) else {
            return
        }

        switch tabbarItemTag {
        case .firstViewController:
            animate(firstTabbarItemImageView)
        case .secondViewConroller:
            animate(secondTabbarItemImageView)
        }
    }
}

Second:

Set the tag values for the tabBarItem for each view controller:

First ViewController:

import UIKit

class FirstViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tabBarItem.tag = TabbarItemTag.firstViewController.rawValue
    }
}

Second ViewController:

import UIKit

class SecondViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tabBarItem.tag = TabbarItemTag.secondViewConroller.rawValue
    }
}

Make sure that everything has been setup with your storyboard (if you are using one) and that's pretty much it!

Output:

enter image description here

You could check the repo:

https://github.com/AhmadFayyas/Animated-TabbarItem/tree/master

for demonstrating the answer.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
7

This do the trick for me:

class MyCustomTabController: UITabBarController {

    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        guard let barItemView = item.value(forKey: "view") as? UIView else { return }

        let timeInterval: TimeInterval = 0.3
        let propertyAnimator = UIViewPropertyAnimator(duration: timeInterval, dampingRatio: 0.5) {
            barItemView.transform = CGAffineTransform.identity.scaledBy(x: 0.9, y: 0.9)
        }
        propertyAnimator.addAnimations({ barItemView.transform = .identity }, delayFactor: CGFloat(timeInterval))
        propertyAnimator.startAnimation()
    }

}
Val Moratalla
  • 191
  • 1
  • 9
4

As UITabBarItem is not a UIView subclass, but an NSObject subclass instead, there is no direct way to animate an item when tapped.

You either have to dig up the UIView that belongs to the item and animate that, or create a custom tab bar.

Here are some ideas for digging up the UIView. And here for example how to get triggered when an item is tapped. But be very careful with this approach:

  • Apple may change the UITabBar implementation, which could break this.
  • You may interfere with iOS animations and get weird effects.

By the way, there's no need to subclass UITabBar. Implementing UITabBarDelegate is all you'd need.

I would actually advise you to just stick with the standard UITabBar behaviour & skinning options, and figure this out later or not at all. Things like this can burn your time without adding much to the app.

meaning-matters
  • 21,929
  • 10
  • 82
  • 142
0

Here is the more optimized solution with any amount of controllers. See the comments in the code to better understand the logic.

class TabBarViewController: UITabBarController, UITabBarControllerDelegate {

// MARK: - UI Properties

private var firstVC = UIViewController()

private var secondVC = UIViewController()

private let thirdVC = UIViewController()
    
private var tabBarImageViews: [UIImageView] = []

public lazy var tabBarHeight =  tabBarController?.tabBar.frame.size.height

// Tag to set tabBar items and images to animate
private let startTagValue = 100

// MARK: - Lifecycle

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
    setControllers()
    
    // Set images to animate possibility
    let tabBarSubviews = tabBar.subviews
    var imageViewTag = startTagValue
    for subview in tabBarSubviews {
        if let imageView = subview.subviews.first as? UIImageView {
            imageView.contentMode = .center
            imageView.tag = imageViewTag
            imageViewTag += 1
            tabBarImageViews.append(imageView)
        }
    }
}

// MARK: - Methods

private func setControllers() {
    let controllers = [firstVC, secondVC, thirdVC]
    
    viewControllers = controllers.map { UINavigationController(rootViewController: $0)}
    
    // Set tag to each tabBarItem to know what imageView to animate
    var controllerItemTag = startTagValue
    controllers.forEach { controller in
        controller.tabBarItem.tag = controllerItemTag
        controllerItemTag += 1
    }
}

private func animate(_ imageView: UIImageView) {
    UIView.animate(withDuration: 0.1, animations: {
        imageView.transform = CGAffineTransform(scaleX: 1.15, y: 1.15)
    }) { _ in
        UIView.animate(withDuration: 0.25, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 3.0, options: .curveEaseInOut, animations: {
            imageView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
        }, completion: nil)
    }
}

// MARK: - Delegate Methods

override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
    if let imageToAnimate = tabBarImageViews.first(where: { $0.tag == item.tag }) {
        animate(imageToAnimate)
    }
}

}

Ihor Chernysh
  • 446
  • 4
  • 5