UITabBarItem Icon Animation - iphone

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.

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:
Create an UIImageView that will be used for animation.
Add it to your TabBar view using the addSubview: method.
Frame it down to the size of UITabBarItem (use UITabBar frame size and the number of tab bar items to calculate the frame size).
Adjust the imageView's frame.origin.x value to place the Image right above the tab bat item you want to animate.
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...

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
}
}

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)
}

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

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

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
}()

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;
}];
}];
}];
}

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

Related

UISlider will not move if two transform are made

I have a UISlider that was implemented through Storyboard. I added a Keypath layer.transform.rotation.z with a value of 1.57 in order to make the UISlider vertical.
I also have a MoveRight UIButton that controls 3 elements (Two UIImageViews and One UISlider) to move in unison. The code for the transform is shown below
#IBAction func MoveRight(_ sender: Any) {
// Used to Show Reset Button for Right
ResetOrigin.isHidden = false
let yPosition1 = myslider.frame.origin.y
let yPosition2 = ImageView.frame.origin.y
let yPosition3 = ImageView2.frame.origin.y
UIView.animate(withDuration: 0.0, delay: 0.0, options: [], animations: {
self.myslider.transform = CGAffineTransform(translationX: 0.001, y: yPosition1)
self.ImageView.transform = CGAffineTransform(translationX: 0.001, y: yPosition2)
self.ImageView2.transform = CGAffineTransform(translationX: 0.001, y: yPosition3)
}, completion: nil)
}
When the code is like this the movement works great although the UISlider is no longer Vertical after the transform. And when I try to add this line of code to the animation
self.myslider.transform = CGAffineTransform(rotationAngle: (CGFloat.pi / 2))
The slider will no longer move while the other 2 Elements are affected by the transform. I tried doing a separate .animate to see if that would resolve the problem but the same result is given. Any ideas on how to solve this issue would be greatly appreciated

How to properly animate UIView animation?

I have an UIScrollView which has 7 UIViews as subviews.
Using UIView.animate method, I am making subviews transition from left to end of the content size of UIScrollView.
func animateScrollView(){
if stopAnimation {
print("Scroll View Animation Stopped")
} else {
print("Animating Scroll View")
featureScrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
featureScrollView.delegate = nil
UIView.animate(withDuration: 10.0, delay: 0, options: [.curveLinear, .repeat], animations: {
self.featureScrollView.contentOffset = CGPoint(x: self.subViewWidth * CGFloat(7) - CGFloat(0), y: 0.0)
}, completion: nil)
}
}
I want this animation to repeat right after last-UIView of previous loop starts to move. So, the sliding UIViews will be always sliding without blank spaces at the end.
Any ideas how to achieve this?

Animate video UIView to go full screen like YouTube App

I have a view controller that has a UIView that I call playerView. In playerView, I use an AVLayer and AVPlayerLayer to show the user a video. Image the Youtube app when you first click on any video.
How do I manipulate the frame of this playerView so that it can take up the entire screen. (Go full screen)
This is the current frame:
//16 x 9 is the aspect ratio of all HD videos
let height = view.frame.width * 9 / 16
let playerFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: height)
playerView.frame = videoPlayerFrame
I was testing around in the YouTube app and their app only supports Portrait orientation (just like mine) due to how the animation of a video going full screen looks. How can I do this at the the tap of a button and even further when the user holds the device horizontally?
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.playerView.frame = ?
}, completion: nil)
Edit: Tried this but it doesn't really work how I want it... it doesn't take up the whole screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.playerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width)
self.playerView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
}, completion: nil)
I figured it out.
I create a global variable called isExpanded that will be set to true when we're in full screen mode. Also, I create a CGPoint variable called videoPlayerViewCenter so I can save the center before going full screen so I can set it again when going back to small screen.
This is the code that gets called when the user wants to go full screen
func fullScreenTapped(sender: vVideoPlayer) {
if !isExpanded {
//Expand the video
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.videoPlayerViewCenter = self.videoPlayerView.center
self.videoPlayerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width)
self.videoPlayerView.center = self.view.center
self.videoPlayerView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
self.videoPlayerView.layoutSubviews()
}, completion: nil)
} else {
//Shrink the video again
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
//16 x 9 is the aspect ratio of all HD videos
self.videoPlayerView.transform = CGAffineTransform.identity
self.videoPlayerView.center = self.videoPlayerViewCenter
let height = self.view.frame.width * 9 / 16
let videoPlayerFrame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: height)
self.videoPlayerView.frame = videoPlayerFrame
self.videoPlayerView.layoutSubviews()
}, completion: nil)
}
isExpanded = !isExpanded
}
To expand the video, I save the center in my global CGPoint variable and then set the frame. Then, I set the center and do a 90 degree turn using CGAffineTransform.
To shrink the video again, I basically set the settings to how they were before. (The order in which things are done is very important)
Another thing that's also very important is to override your layoutSubviews method in your videoPlayerView to be like this:
override func layoutSubviews() {
super.layoutSubviews()
self.playerLayer?.frame = self.bounds
}
This will make it so your playerLayer adjusts its frame as the view is getting bigger.
I know is too late but maybe someone else can find this code helpful.
I use to use Auto layout constraints in my views so animating must have a different approach.
func setMapFullScreen(_ fullscreen: Bool) {
if fullscreen {
// Set the full screen constraints
self.setFullScreenLayoutConstraints()
// Hide the navigation bar
self.navigationController?.setNavigationBarHidden(true, animated: true)
} else {
// Set small screen constraints
self.setSmallScreenLayoutConstraints()
// Show the navigation bar
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
// Animate to full screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}) { (finished) in
// ...
}
In my case a have I MKMapView and I want to tap on it in order to see the map fullscreen (and back).
This is the function that toogle the state.
As you can see, in the UIView.animate call, I animate only the self.view.layoutIfNeeded()
I also hide the navigation bar cause I have one ☺
Here my setSmallScreenLayoutConstraints and setFullScreenLayoutConstraints functions using SnapKit framework
func setSmallScreenLayoutConstraints() {
self.mapView.snp.remakeConstraints { (make) -> Void in
make.top.equalTo(self.mapLabel.snp.bottom).offset(8)
make.left.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.height.equalTo(150)
}
}
func setFullScreenLayoutConstraints() {
self.mapView.snp.remakeConstraints { (make) -> Void in
make.top.equalTo(self.view).offset(0)
make.left.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.bottom.equalTo(self.view).offset(0)
}
}
This is the result
Enjoy!!
As I said it is not an answer for your question. It is an idea and hope it helps you:
var rectangleView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Tap Gesture Recognizer for growing the rectangleView
let tapForward = UITapGestureRecognizer.init(target: self, action: #selector(self.tapForward(tap:)))
tapForward.numberOfTapsRequired = 1
// Configure the rectangleView
self.rectangleView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
self.rectangleView.transform = CGAffineTransform.init(scaleX: 0.65, y: 0.25)
self.rectangleView.backgroundColor = UIColor.red
self.rectangleView.addGestureRecognizer(tapForward)
self.view.addSubview(rectangleView)
}
And this going to be our tapForward(tap:) function:
func tapForward(tap:UITapGestureRecognizer) {
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
self.rectangleView.transform = CGAffineTransform.identity
}) { (completed:Bool) in
// Completion block
}
}

Animation to scale and move UIView (swift)

I have a UIView, placed in the middle of the screen. When the user presses a button, I want it to move up near top of the screen as it shrinks to about a fifth of its size.
I have tried this:
UIView.animateWithDuration(0.7) { () -> Void in
self.main.transform = CGAffineTransformMakeScale(0.2, 0.2)
self.main.transform = CGAffineTransformMakeTranslation(0, -250)
}
But for some reason, that only scales the view. I have also tried to put this in the animateWithDuration:
self.main.frame = CGRectMake(self.view.frame.width/2 - 10, 50, 50, 50)
How can I get both animations to work?
Joe's answer above does exactly as his GIF describes but it doesn't really answer your question since it translates then scales the view (as opposed to both translating and scaling at the same time). Your issue is that you're setting the view's transform in your animation block then immediately overwritting that value with another transform. To achieve both translation and scale at the same time, you'll want something like this:
#IBAction func animateButton(_ sender: UIButton) {
let originalTransform = self.main.transform
let scaledTransform = originalTransform.scaledBy(x: 0.2, y: 0.2)
let scaledAndTranslatedTransform = scaledTransform.translatedBy(x: 0.0, y: -250.0)
UIView.animate(withDuration: 0.7, animations: {
self.main.transform = scaledAndTranslatedTransform
})
}
You can achieve basic UIView animation in several ways in Swift. Below code is just a simple approach...
class ViewController: UIViewController {
#IBOutlet weak var animationButton: UIButton!
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// I added seperate colour to UIView when animation move from one animation block to another.So, You can get better understanding how the sequence of animation works.
self.myView.backgroundColor = .red
}
#IBAction func animateButton(_ sender: UIButton) {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
//Frame Option 1:
self.myView.frame = CGRect(x: self.myView.frame.origin.x, y: 20, width: self.myView.frame.width, height: self.myView.frame.height)
//Frame Option 2:
//self.myView.center = CGPoint(x: self.view.frame.width / 2, y: self.view.frame.height / 4)
self.myView.backgroundColor = .blue
},completion: { finish in
UIView.animate(withDuration: 1, delay: 0.25,options: UIViewAnimationOptions.curveEaseOut,animations: {
self.myView.backgroundColor = .orange
self.myView.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.animationButton.isEnabled = false // If you want to restrict the button not to repeat animation..You can enable by setting into true
},completion: nil)})
}
}
Output:

Animating button border in swift

I recently learned how to animate a button using alpha so that it fades in/out in Swift. However, if I would like to only have the alpha of the border itself change, the animation does not seem to be working. Instead, it "jumps" from state to state.
UIView.animateWithDuration(1.0, delay: 0.0, options: nil, animations: {
var borderColor = UIColor(red: 0.41, green: 1.28, blue: 1.85, alpha: 0.0)
self.startButton.layer.borderColor = borderColor.CGColor
}, completion: nil);
The above code for example does not animate, instead it produces a "jump" between alpha 1.0 and 0.0 of the border.
However, this would work fine (changing the alpha of the entire button):
UIView.animateWithDuration(1.0, delay: 0.0, options: nil, animations: {
self.startButton.alpha = 1;
}, completion: nil);
Is there any way to get around this issue?
Here's a simple solution:
let borderWidth:CABasicAnimation = CABasicAnimation(keyPath: "borderWidth")
borderWidth.fromValue = 0
borderWidth.toValue = 0.9
borderWidth.duration = 0.5
yourView.layer.borderWidth = 0.5
yourView.layer.borderColor = UIColor.whiteColor().CGColor
yourView.layer.addAnimation(borderWidth, forKey: "Width")
yourView.layer.borderWidth = 0.0
It works for me.
You can't animate parts of the layer using UIView animations. Also, I don't think it's possible to animate the alpha value of the border. To achieve a similar effect you can animate the width of your border:
I made this UIView extension:
extension UIView {
func animateBorderWidth(toValue: CGFloat, duration: Double = 0.3) {
let animation = CABasicAnimation(keyPath: "borderWidth")
animation.fromValue = layer.borderWidth
animation.toValue = toValue
animation.duration = duration
layer.add(animation, forKey: "Width")
layer.borderWidth = toValue
}
}
and then show the border of your view by using:
startButton.animateBorderWidth(toValue: 1, duration: 0.5)
and hide it by using:
startButton.animateBorderWidth(toValue: 0)
Features such as the borderColor and borderWidth are properties of the button's layer. That tells you that you must use core animation of the layer - not view animation, as your code tries to do - to animate such features. It works perfectly well; in this animation, I demonstrate what happens when you use core animation to animation the borderWidth and the cornerRadius together:
Animation of the borderColor would work similarly.