19

Skype app for iPhone uses animated TabBar icons. For example, during the logging-in the rightmost tab icon shows circulating arrows. While calling the "Call" tab icon softly blinks which is obviously done through animation.

I wonder how is it possible to animate tab bar items' icons.

In my particular case when the user presses the 'Favorite' button it jumps onto the 'Favorites' tab bar item. I have already implemented the jumping animation, but I would like the corresponding tab bar icon to blink at the end of animation to bring the feeling of completeness to it.

Any suggestions about the direction I should look in?

Thanks in advance.

Sergey Lost
  • 2,511
  • 3
  • 19
  • 22

8 Answers8

13

I am surprised how easy the solution was!

Add method to your Application Delegate class .m-file (or any other class that manages your UITabBar) containing the following routine:

  1. Create an UIImageView that will be used for animation.
  2. Add it to your TabBar view using the addSubview: method.
  3. Frame it down to the size of UITabBarItem (use UITabBar frame size and the number of tab bar items to calculate the frame size).
  4. Adjust the imageView's frame.origin.x value to place the Image right above the tab bat item you want to animate.
  5. Add animation you want to the imageView (you can play with opacity, swap several images - anything you want).

Pretty easy, don't you think so?

You can call this method on UIApplicationDelegate instance anywhere you need to animate the tab bar item.

Also it is important to notice that you can tap THROUGH the imageView to select the tab bar item as if there was no image over the tab bar. Many interesting conclusions can be done here on what you can do if you know it...

Sergey Lost
  • 2,511
  • 3
  • 19
  • 22
10

I have found a better solution to this problem. Adding custom image view is not a better approach because in iOS 10.0 and later on changing the orientation the icon frame & text position changed. You just need to set the class of UITabBarController & put the following code.

override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {

    let index = self.tabBar.items?.index(of: item)
    let subView = tabBar.subviews[index!+1].subviews.first as! UIImageView
    self.performSpringAnimation(imgView: subView)
}

//func to perform spring animation on imageview
func performSpringAnimation(imgView: UIImageView) {

    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {

        imgView.transform = CGAffineTransform.init(scaleX: 1.4, y: 1.4)

        //reducing the size
        UIView.animate(withDuration: 0.5, delay: 0.2, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
            imgView.transform = CGAffineTransform.init(scaleX: 1, y: 1)
        }) { (flag) in
        }
    }) { (flag) in

    }
}  
Naresh
  • 869
  • 8
  • 17
  • I don't think this works anymore. The subviews of a UITabBar are _UIBarBackground and UITabBarButton. – ThomasW Jun 28 '23 at 09:36
5

Keep in mind that the order of the subviews in the UITabBar may be unordered so accessing the correct item from it using an index may not work correctly as Naresh answer suggested. Have a look at this post UITabBar subviews change order

The solution I found is to first filter and sort the views and then access it using the correct index of the tabBarItem.

func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {

    let orderedTabBarItemViews: [UIView] = {
        let interactionViews = tabBar.subviews.filter({ $0 is UIControl })
        return interactionViews.sorted(by: { $0.frame.minX < $1.frame.minX })
    }()

    guard
        let index = tabBar.items?.firstIndex(of: item),
        let subview = orderedTabBarItemViews[index].subviews.first
    else {
        return
    }

    performSpringAnimation(for: subview)
}

func performSpringAnimation(for view: UIView) {
    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
        view.transform = CGAffineTransform(scaleX: 1.25, y: 1.25)
        UIView.animate(withDuration: 0.5, delay: 0.2, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
            view.transform = CGAffineTransform(scaleX: 1, y: 1)
        }, completion: nil)
    }, completion: nil)
}
PatrickDotStar
  • 1,654
  • 1
  • 19
  • 20
  • 2
    Its not working. I have added it inside tab bar controller. The code is also running but no action is happening. – Pratyush Pratik Sep 26 '18 at 10:30
  • Same , I also added this to the delegate function but nothing happened – Ali Pasha Jul 30 '21 at 12:43
  • Try creating your own subclass of `UITabBarController` that also conforms to `UITabBarControllerDelegate` and set its delegate to `self`. I just tried it again on iOS 14.5 and it still works. – PatrickDotStar Sep 02 '21 at 07:40
  • Great, but be aware that as for June 2022 your should get last subview in orderedTabBarItemViews so just filter it for being UIImageView – Andrew Adamovitch Jun 14 '22 at 16:41
1

Actually there is much easier way: https://medium.com/@werry_paxman/bring-your-uitabbar-to-life-animating-uitabbaritem-images-with-swift-and-coregraphics-d3be75eb8d4d#.8o1raapyr

Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57
  • This implementation, like many of the others, makes assumptions about the view hierarchy which can potentially change over OS versions. – ThomasW Jun 28 '23 at 10:04
1

You can animate tabbar icons by getting its view, then do whatever animation as you like for the UIView. Below is a simple example with scale transform, cheer!

func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem){
        var tabBarView: [UIView] = []

        for i in tabBar.subviews {
            if i.isKind(of: NSClassFromString("UITabBarButton")! ) {
                tabBarView.append(i)
            }
        }

        if !tabBarView.isEmpty {
            UIView.animate(withDuration: 0.15, animations: {
                tabBarView[item.tag].transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
            }, completion: { _ in
                UIView.animate(withDuration: 0.15) {
                    tabBarView[item.tag].transform = CGAffineTransform.identity
                }
            })
        }
    }

ps: please assign tag for each UITabBarItem in order

YinKiet
  • 479
  • 6
  • 14
1

Index of UIImageView in subviews is not guaranteed as fist.

The index of UIImageView is not guaranteed as fist in related subviews.

So it is better to access it via class type, and also check for index out of bounds.

override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
    guard let idx = tabBar.items?.firstIndex(of: item),
        tabBar.subviews.count > idx + 1,
        let imageView = tabBar.subviews[idx + 1].subviews.compactMap({ $0 as? UIImageView }).first else {
        return
    }
    
    imageView.layer.add(bounceAnimation, forKey: nil)
}

This is a sample basic bounce animation for that using CAKeyframeAnimation:

private var bounceAnimation: CAKeyframeAnimation = {
    let bounceAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
    bounceAnimation.values = [1.0, 1.3, 0.9, 1.0]
    bounceAnimation.duration = TimeInterval(0.3)
    bounceAnimation.calculationMode = CAAnimationCalculationMode.cubic
    return bounceAnimation
}()

My Caption

Omer Faruk Ozturk
  • 1,722
  • 13
  • 25
1

Simple way to animate tab bar item in objective c

In your tabbar controller class

    - (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
    {
        UIVisualEffectView *viv = tabBar.subviews[item.tag].subviews.firstObject;
        UIImageView *img = viv.contentView.subviews.firstObject;
       // If UIImageView not get use this code
       // UIImageView *img  = tabBar.subviews[item.tag].subviews.lastObject;


        [self shakeAnimation:img];
      //[self bounceAnimation:img];

    }



- (void)shakeAnimation:(UIImageView *)img
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setDuration:0.05];
    [animation setRepeatCount:2];
    [animation setAutoreverses:YES];
    [animation setFromValue:[NSValue valueWithCGPoint: CGPointMake([img center].x - 10.0f, [img center].y)]];
    [animation setToValue:[NSValue valueWithCGPoint: CGPointMake([img center].x + 10.0f, [img center].y)]];
    [[img layer] addAnimation:animation forKey:@"position"];
}


- (void)bounceAnimation:(UIImageView *)img
{
    img.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);

    [UIView animateWithDuration:0.3/1.5 animations:^{
        img.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1);
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.3/2 animations:^{
            img.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.9, 0.9);
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:0.3/2 animations:^{
                img.transform = CGAffineTransformIdentity;
            }];
        }];
    }];
}
sohil
  • 818
  • 2
  • 15
  • 38
0

I haven't done that but I would just try to build a CAAnimation e.g. with a CABasicAnimation and add it to the UITabBarItem you want to be animated.

For details about how to set up a CABasicAnimation see the Core Animation Programming Guide: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/AnimatingLayers.html#//apple_ref/doc/uid/TP40006085-SW1

V1ru8
  • 6,139
  • 4
  • 30
  • 46
  • Nah, I am sure this won't work because UITabBarItem is not a kind of view - it originates from NSObjest (basic class) and UIBarItem (nothing interesting there as well). Seems like there's no Layer or even frame to play with. But I've managed to solve this problem by entering the 'backdoor'. Thanks for idea though – Sergey Lost Aug 24 '10 at 00:27