I think this should be a simple question to answer, but I haven't been able to find a solution yet.
All I want is an animation that shrinks a view from its original size to a smaller size.
I have tried this:
UIView.animateWithDuration(10.0, animations: {
self.view.frame = CGRectInset(frame, 0.2 * frame.width, 0.2 * frame.height)
})
This does shrink the view, but it abruptly blows it up before shrinking it then only shrinks it back to its original size.
I have also tried this:
UIView.animateWithDuration(10.0, animations: {
self.view.transform = CGAffineTransformMakeScale(0.01, 0.01)
})
But this has the same problem as the first option, although it does shrink it down to a smaller size than the original size after blowing it up.
How can I create an animation that shrinks a view from its original size to a smaller size?
After looking into CABasicAnimation as recommended by Leo Dabus, I was able to solve my problem. Here is some simple code that shrinks a view (stored in the variable "view") from its original size to a smaller size (100x smaller):
let animation = CABasicAnimation(keyPath: "transform")
var tr = CATransform3DIdentity
tr = CATransform3DScale(tr, 0.01, 0.01, 1);
animation.toValue = NSValue(CATransform3D: tr)
view.layer.addAnimation(animation, forKey: "transform")
Related
I have infinity CABasicAnimation which actually simulate pulsating by increasing and decreasing scale:
scaleAnimation.fromValue = 0.5
scaleAnimation.toValue = 1.0
scaleAnimation.duration = 0.8
scaleAnimation.autoreverses = true
scaleAnimation.repeatCount = .greatestFiniteMagnitude
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
I want to smoothly stop this animation in toValue. In other words, I want to allow current animation cycle finish, but stop repeating. Is there a nice and clean way to do this? I had a few ideas about freezing current animation, removing it and creating a new one with time offset, but maybe there is a better way?
There is a standard way to do this cleanly — though it's actually quite tricky if you don't know about it:
The first thing you do is set the layer's scale to the scale of its presentationLayer.
Then call removeAllAnimations on the layer.
Now do a fast animation where you set the layer's scale to 1.
Here's a possible implementation (for extra credit, I suppose we could adjust the duration of the fast animation to match what the current scale is, but I didn't bother to do that here):
#IBAction func doStop(_ sender: Any) {
let lay = v.layer
lay.transform = lay.presentation()!.transform
lay.removeAllAnimations()
CATransaction.flush()
lay.transform = CATransform3DIdentity
let scaleAnimation = CABasicAnimation(keyPath: "transform")
scaleAnimation.duration = 0.4
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
lay.add(scaleAnimation, forKey: nil)
}
Result:
I am rolling a large-width landscape image to give the impression to the user the whole stuff is a constant spinning cylinder.
To do that, the large image is actually made of two copies of the same image stitched together with constraints and embedded into a UIScrollView. As a consequence, when I animate the contentOfsset.x to make it horizontally scrolling, only the portion inside the UIScrollView is made visible.
When the scrollView appears, the visible portion shows the middle part of the image.
As I want to start with an ease-in curve, I animate it till the end of the image, when completed, I then set the contentOffset at the beginning of the image and run a new repeating animation as per the code follows:
// set content offset at the center of the background
let w = imageViews[0].frame.width
let vw = view.frame.width
backgroundScrollView.contentOffset.x = (w - vw)/2
print ("\(#function): Background image view = \(imageViews[0].frame), start offset = \(backgroundScrollView.contentOffset.x), view = \(view.frame)")
let duration: TimeInterval = 10.0
let speed = TimeInterval(w + vw) / duration
let halfDuration = TimeInterval(w / 2 + vw) / speed
UIView.animate(withDuration: halfDuration, delay: 0, options: [ .curveEaseIn ], animations: {
self.backgroundScrollView.contentOffset.x = w
}){ completed in
self.backgroundScrollView.contentOffset.x = 0
UIView.animate(withDuration: duration, delay: 0, options: [ .repeat, .curveLinear ], animations: {
self.backgroundScrollView.contentOffset.x = w
}) { completed in
}
}
The point here, is that I compute the time for each animation depending on the distance it has to scroll (the first is almost the half of the second). Unfortunately, as the .curveEaseIn is not linear (and I still compute average speed) there is a small glitch in speed between the two animation.
Anyone knows what the curveEaseIn equation is? Or any other suggestions?
I need to resize image using slider, that way that while the sliders value is being changed the image shrinks or becomes larger accordingly.
Is there any way to optimize this and not redraw image each time the value changes?
You can simply scale up/down when the slider value changes as below,
let sliderValue: CGFloat = 0.5
imageView.transform = CGAffineTransform(scaleX: sliderValue, y: sliderValue)
you can use transform
#IBAction func zoomSlider(_ sender: UISlider) {
yourImgView.transform = CGAffineTransform(scaleX: sender.value, y: sender.value)
}
set the value between 0.1 to 2 in slider and test it.Accordingly change the values you want.
to get original size
yourImgView = CGAffineTransform.identity
I'm trying to animate the width of a group in my watchOS2 application by calling animateWithDuration of WKInterfaceController class. The idea is to show the user an horizontal line which decreases its width from right to left over a period of time (something like a timer):
self.timer.setWidth(100)
self.animateWithDuration(NSTimeInterval(duration)) {
self.timer.setWidth(0)
}
However I'm seeing that as soon as the animation starts the speed is very slow and then it increases. When the animation is about to stop (when the timer width is close to 0) the animation slows down again.
I want the speed to be the same over the duration of the animation.
Has anyone had this issue before? Any help is appreciated! Thanks
WatchOS 2 doesn't provide a way to specify a timing function, so the animation is limited to using the EaseInEaseOut curve (which starts out slow, speeds up, then slows down at the end).
You could try using Core Graphics to render the line, or use a series of WKInterfaceImage frames to smoothly animate the line.
I made a simple example of the timer I wanted to animate with Core Graphics in the watchOS2 app.
You can find the project here
UPDATE:
Here's the code that I made:
func configureTimerWithCounter(counter: Double) {
let size = CGSizeMake(self.contentFrame.width, 6)
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
UIGraphicsPushContext(context!)
// Draw line
let path = UIBezierPath()
path.lineWidth = 100
path.moveToPoint(CGPoint(x: 0, y: 0))
let counterPosition = (self.contentFrame.width/30)*CGFloat(counter)
path.addLineToPoint(CGPoint(x: counterPosition, y: 0))
UIColor.greenColor().setStroke()
UIColor.whiteColor().setFill()
path.stroke()
path.fill()
// Convert to UIImage
let cgimage = CGBitmapContextCreateImage(context);
let uiimage = UIImage(CGImage: cgimage!)
// End the graphics context
UIGraphicsPopContext()
UIGraphicsEndImageContext()
self.timerImage.setImage(uiimage)
}
I have added a UIButton to storyboard and then added my image to the button. What I am looking to do is when you tap on the button the height increases slightly and then the button decreases in height by about a quarter.
#IBAction func StopsClick(sender: UIView) {
//Get the y coordinates of Image
let origin = sender.bounds.origin.y
//This decreases the height of Image but the image moved. I want the image to remain stationary and only the top of the image to increase in height.
UIView.animateWithDuration(0.5) {
sender.bounds = CGRectMake(0, origin, sender.bounds.width, sender.bounds.height * 1.2)
}
UIView.animateWithDuration(1.0) {
sender.bounds = CGRectMake(0, orgin, sender.bounds.width, sender.bounds.height * 0.4)
}
}
You should be able to get the effect you're after using transforms.
I would do the following:
Start with the identity transform.
Shift the origin of the transform down to the bottom of the image.
Increase the transform's scale.y as desired.
Shift the transform's origin back to the center of the image
Apply that transform to the button in your animation. Then animate it back to the identity transform.
If you don't shift the transform and only scale it it will grow in all directions from the center. (or only up and down if you only increase the scale.y)
Edit:
The code might look like this:
let halfHeight = button.bounds.height / 2
//Make the center of the grow animation be the bottom center of the button
var transform = CGAffineTransformMakeTranslation(0, -halfHeight)
//Animate the button to 120% of it's normal height.
tranform = CGAffineTransformScale( transform, 1.0, 1.2)
tranform = CGAffineTransformTranslate( transform, 0, halfHeight)
UIView.animateWithDuration(0.5)
{
button.transform = transform
}
The above code animates the button to 120% height, and leaves it there.
You could use one of the longer variants of animateWithDuration that takes options to make the animation auto-reverse. I leave that as an exercise for you.
Edit #2:
I banged out some code to do a 3-step animation:
func animateButton(step: Int)
{
let localStep = step - 1
let halfHeight = aButton.bounds.height / 2
var transform: CGAffineTransform
switch step
{
case 2:
//Make the center of the grow animation be the bottom center of the button
transform = CGAffineTransformMakeTranslation(0, -halfHeight)
//Animate the button to 120% of it's normal height.
transform = CGAffineTransformScale( transform, 1.0, 1.2)
transform = CGAffineTransformTranslate( transform, 0, halfHeight)
UIView.animateWithDuration(0.5, animations:
{
aButton.transform = transform
},
completion:
{
(finshed) in
animateButton(step)
})
case 1:
//In the second step, shrink the height down to .25 of normal
transform = CGAffineTransformMakeTranslation(0, -halfHeight)
//Animate the button to 120% of it's normal height.
transform = CGAffineTransformScale( transform, 1.0, 0.25)
transform = CGAffineTransformTranslate( transform, 0, halfHeight)
UIView.animateWithDuration(0.5, animations:
{
aButton.transform = transform
},
completion:
{
(finshed) in
animateButton(step)
})
case 0:
//in the final step, animate the button back to full height.
UIView.animateWithDuration(0.5)
{
aButton.transform = CGAffineTransformIdentity
}
default:
break
}
}
You'd invoke it with
animateButton(3)
It would be cleaner to use an enum for the step number, and it could use some range checking to make sure the input value is 3, 2, or 1, but you get the idea...