How can I move an image in Swift? - ios

I would like to be able to move an object (in my case, an image "puppy") up 1 pixel every time a button is pressed. I've stumbled upon old Objective-C solutions as well as Swift code that was similar, but none that fit my specific problem. I would love to know an easy way to move my image. This is my code so far from what I could gather(I'm hoping it's unnecessarily long and can be reduced to a line or two):
#IBAction func tapButton() {
UIView.animateWithDuration(0.75, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.puppy.alpha = 1
self.puppy.center.y = 0
}, completion: nil)
var toPoint: CGPoint = CGPointMake(0.0, 1.0)
var fromPoint : CGPoint = CGPointZero
var movement = CABasicAnimation(keyPath: "movement")
movement.additive = true
movement.fromValue = NSValue(CGPoint: fromPoint)
movement.toValue = NSValue(CGPoint: toPoint)
movement.duration = 0.3
view.layer.addAnimation(movement, forKey: "move")
}

This way you can change a position of your imageView
#IBAction func tapButton() {
UIView.animateWithDuration(0.75, delay: 0, options: .CurveLinear, animations: {
// this will change Y position of your imageView center
// by 1 every time you press button
self.puppy.center.y -= 1
}, completion: nil)
}
And remove all other code from your button action.

CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
self.viewBall.layer.position = self.viewBall.layer.presentationLayer().position
}
var animation = CABasicAnimation(keyPath: "position")
animation.duration = ballMoveTime
var currentPosition : CGPoint = viewBall.layer.presentationLayer().position
animation.fromValue = NSValue(currentPosition)
animation.toValue = NSValue(CGPoint: CGPointMake(currentPosition.x, (currentPosition.y + 1)))
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
viewBall.layer.addAnimation(animation, forKey: "transform")
CATransaction.commit()
Replace viewBall to your image object
And also remove completion block if you don't want.

You can do this.
func clickButton() {
UIView.animateWithDuration(animationDuration, animations: {
let buttonFrame = self.button.frame
buttonFrame.origin.y = buttonFrame.origin.y - 1.0
self.button.frame = buttonFrame
}
}

Related

How can i stop a rotate with animation

I've tried some animations but I don't find any solution. I've shared with you a video about my animation below.
I get this following animation with rotation. How can I make the animation stop by slowing down when I say stop?
animation video link
I've tried this code
func stopRotate() {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: { () -> Void in
self.layer.removeAllAnimations()
})
}
my rotate animation code
func rotate() {
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = Double.pi * 2
rotation.duration = 0.75 // or however long you want ...
rotation.isCumulative = true
rotation.repeatCount = 15
self.layer.add(rotation, forKey: "rotationAnimation")
}

Flip view like UIView.transition with Core Animation

I want to create a transition between two views like UIView.transition with .transitionFlipFromLeft. For example:
import UIKit
class ViewController: UIViewController {
static let frame = CGRect(x: 0, y: 0, width: 300, height: 200)
let viewContainer = UIView(frame: frame)
let view1 = UIView(frame: frame)
let view2 = UIView(frame: frame)
var currentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view1.backgroundColor = .blue
self.view2.backgroundColor = .red
self.currentView = self.view1
self.view.addSubview(self.viewContainer)
self.viewContainer.addSubview(self.currentView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if currentView == view1 {
flip(to: view2)
} else {
flip(to: view1)
}
}
func flip(to toView: UIView) {
UIView.transition(from: currentView,
to: toView,
duration: 1,
options: .transitionFlipFromLeft,
completion: { _ in self.currentView = toView })
}
}
It should be possible to reach a similar effect by using Core Animation. I replaced UIView.transition in func flip(to toView: UIView):
func flip(to toView: UIView) {
var transform = CATransform3DIdentity
transform.m34 = -1 / 400
toView.layer.transform = transform
self.currentView.layer.transform = transform
let disappearByRotating = CABasicAnimation(keyPath: "transform.rotation.y")
disappearByRotating.duration = 0.5
disappearByRotating.fromValue = 0
disappearByRotating.toValue = CGFloat.pi / 2
let appearByRotating = CABasicAnimation(keyPath: "transform.rotation.y")
appearByRotating.duration = 0.5
appearByRotating.fromValue = CGFloat.pi / 2
appearByRotating.toValue = CGFloat.pi
CATransaction.begin()
CATransaction.setCompletionBlock {
self.currentView.removeFromSuperview()
self.viewContainer.addSubview(toView) // <= Problem!
toView.layer.add(appearByRotating, forKey: "appear")
self.currentView = toView
}
self.currentView.layer.add(disappearByRotating, forKey: "disappear")
CATransaction.commit()
}
I used two animations: In the first animation, the first view rotates from the left to the middle, in the second animation, the second view rotates from the middle to the right.
When I add the second view to my viewContainer, it is displayed untransformed before the second animation starts. This causes flickering:
How can I prevent the flickering?
One option:
apply a 90-degree rotation to toView.layer before anything else
toView will now be "invisible" so add it as a subview
embed a new CATransaction with new Completion Block in the original Completion Block to "remove" the transform from toView.layer
Try this:
func flip(to toView: UIView) {
var transform = CATransform3DIdentity
transform.m34 = -1 / 400
// start toView rotated 90 degrees
transform = CATransform3DRotate(transform, CGFloat.pi / 2, 0, 1, 0)
toView.layer.transform = transform
// because toView's layer is rotated 90 degrees, we can
// add the subview here and it won't be visible
// really no difference between .addSubview and .insertSubview for our purposes
// but we'll insert it under the current view anyway
self.viewContainer.insertSubview(toView, belowSubview: self.currentView)
transform = CATransform3DIdentity
transform.m34 = -1 / 400
self.currentView.layer.transform = transform
let disappearByRotating = CABasicAnimation(keyPath: "transform.rotation.y")
disappearByRotating.duration = 0.5
disappearByRotating.fromValue = 0
disappearByRotating.toValue = CGFloat.pi / 2
disappearByRotating.fillMode = .forwards
disappearByRotating.isRemovedOnCompletion = false
let appearByRotating = CABasicAnimation(keyPath: "transform.rotation.y")
appearByRotating.duration = 0.5
appearByRotating.fromValue = -CGFloat.pi / 2
appearByRotating.toValue = 0
appearByRotating.fillMode = .forwards
appearByRotating.isRemovedOnCompletion = false
CATransaction.begin()
CATransaction.setCompletionBlock {
self.currentView.removeFromSuperview()
// start a new CATransaction with new Completion Block
CATransaction.begin()
CATransaction.setCompletionBlock {
self.currentView.layer.removeAnimation(forKey: "disappear")
toView.layer.removeAnimation(forKey: "appear")
// remove 90-degree-rotation starting point from toView
toView.layer.transform = CATransform3DIdentity
self.currentView = toView
}
toView.layer.add(appearByRotating, forKey: "appear")
CATransaction.commit()
}
self.currentView.layer.add(disappearByRotating, forKey: "disappear")
CATransaction.commit()
}

CAShapeLayer strange behavior in the first time animation

I've wrote this animation to the CAShapeLayer (pulseLayer) and in the viewDidLoad() :
let pulseLayer = CAShapeLayer()
#IBOutlet weak var btnCart: UIButton!
override func viewDidLoad() {
let longpress = UILongPressGestureRecognizer(target: self, action: #selector(CategoryViewController.longPressGestureRecognized(_:)))
tableView.addGestureRecognizer(longpress)
heartBeatAnimation.duration = 0.75
heartBeatAnimation.repeatCount = Float.infinity
heartBeatAnimation.autoreverses = true
heartBeatAnimation.fromValue = 1.0
heartBeatAnimation.toValue = 1.2
heartBeatAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
btnCart.layer.addSublayer(pulseLayer)
}
func addBtnCartLayerWithAnimation() {
let ovalPath = UIBezierPath(arcCenter: CGPoint(x: btnCart.frame.midX, y: btnCart.frame.midY), radius: btnCart.frame.width * 1.5, startAngle: 0*(CGFloat.pi / 180), endAngle: 360*(CGFloat.pi / 180), clockwise: true)
pulseLayer.path = ovalPath.cgPath
pulseLayer.opacity = 0.15
pulseLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pulseLayer.bounds = ovalPath.cgPath.boundingBox
pulseLayer.add(heartBeatAnimation, forKey: "heartBeatAnimation")
pulseLayer.transform = CATransform3DScale(CATransform3DIdentity, 1.0, 1.0, 1.0)
}
and the removeLayer function is:
func removeLayer() {
pulseLayer.transform = CATransform3DScale(CATransform3DIdentity, 0.1, 0.1, 0.1)
pulseLayer.removeAllAnimations()
}
The problem is when the first animation of the layer is coming from the bottom of the view !
the first animation after viewDidLoad
then after that any invoke for this animation with start from the center(the defined anchor point)
any animation after the first one
Could anyone tell me why this happening ?
the whole class where I defined and used UILongGestureRecognizer on tableView to start/stop the animation :
func longPressGestureRecognized(_ gestureRecognizer: UIGestureRecognizer) {
let longPress = gestureRecognizer as! UILongPressGestureRecognizer
let state = longPress.state
let locationInView = longPress.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: locationInView)
switch state {
case UIGestureRecognizerState.began:
if indexPath != nil {
addBtnCartLayerWithAnimation()
}
case UIGestureRecognizerState.changed:
// some code here not related to the animation
default:
removeLayer()
}
}
When using standalone layers (layers that are not the backing store of a UIView), implicit animations are added for every animatable property change.
You're seeing this effect because the layer is animating its properties from zero to the initial values you're setting in addBtnCartLayerWithAnimation().
What you want to do is to set these initial values without animation (which needs to be done explicitly). You can wrap the change in a transaction in which you disable animations like so:
CATransaction.begin()
CATransaction.setDisableActions(true)
pulseLayer.path = ovalPath.cgPath
pulseLayer.opacity = 0.15
pulseLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pulseLayer.bounds = ovalPath.cgPath.boundingBox
pulseLayer.transform = CATransform3DScale(CATransform3DIdentity, 1.0, 1.0, 1.0)
CATransaction.commit()
pulseLayer.add(heartBeatAnimation, forKey: "heartBeatAnimation")

Rotate a view for 360 degrees indefinitely in Swift?

I want to rotate an image view for 360 degrees indefinitely.
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
self.view.transform = CGAffineTransform(rotationAngle: 6.28318530717959)
}, completion: nil)
How can I do it?
UPDATE Swift 5.x
// duration will helps to control rotation speed
private func rotateView(targetView: UIView, duration: Double = 5) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: .pi)
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
Swift 2.x way to rotate UIView indefinitely, compiled from earlier answers:
// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
targetView.transform = CGAffineTransformRotate(targetView.transform, CGFloat(M_PI))
}) { finished in
self.rotateView(targetView, duration: duration)
}
}
UPDATE Swift 3.x
// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(M_PI))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
Swift 3.0
let imgViewRing = UIImageView(image: UIImage(named: "apple"))
imgViewRing.frame = CGRect(x: 0, y: 0, width: UIImage(named: "apple")!.size.width, height: UIImage(named: "apple")!.size.height)
imgViewRing.center = CGPoint(x: self.view.frame.size.width/2.0, y: self.view.frame.size.height/2.0)
rotateAnimation(imageView: imgViewRing)
self.view.addSubview(imgViewRing)
This is the animation logic
func rotateAnimation(imageView:UIImageView,duration: CFTimeInterval = 2.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(.pi * 2.0)
rotateAnimation.duration = duration
rotateAnimation.repeatCount = .greatestFiniteMagnitude
imageView.layer.add(rotateAnimation, forKey: nil)
}
You can check output in this link
Use this extension to rotate UIImageView 360 degrees.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.duration = duration
if let delegate: CAAnimationDelegate = completionDelegate as! CAAnimationDelegate? {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
Than to rotate UIImageView simply use this method
self.YOUR_SUBVIEW.rotate360Degrees()
This one work for me in Swift 2.2:
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = 360 * CGFloat(M_PI/180)
let innerAnimationDuration : CGFloat = 1.0
rotationAnimation.duration = Double(innerAnimationDuration)
rotationAnimation.repeatCount = HUGE
self.imageView.addAnimation(rotationAnimation, forKey: "rotateInner")
I would stick it in a function like rotateImage() and in the completion code just call rotateImage() again. I think you should use M_PI (or the swift equivalent) for the rotation amount, though.
Updated for Swift 3:
I made an extension to UIView and included the following function within:
func rotate(fromValue: CGFloat, toValue: CGFloat, duration: CFTimeInterval = 1.0, completionDelegate: Any? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: >"transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = duration
if let delegate: Any = completionDelegate {
rotateAnimation.delegate = delegate as? CAAnimationDelegate
}
self.layer.add(rotateAnimation, forKey: nil)
}
You can then call the function via (on a UIView I made into a button for example) :
monitorButton.rotate(fromValue: 0.0, toValue: CGFloat(M_PI * 2), completionDelegate: self)
Hope this helps!
I think what you really want here is to use a CADisplayLink. Reason being that this would be indefinitely smooth versus using completion blocks which may cause slight hiccups and are not as easily cancelable. See the following solution:
var displayLink : CADisplayLink?
var targetView = UIView()
func beginRotation () {
// Setup display link
self.displayLink = CADisplayLink(target: self, selector: #selector(onFrameInterval(displayLink:)))
self.displayLink?.preferredFramesPerSecond = 60
self.displayLink?.add(to: .current, forMode: RunLoop.Mode.default)
}
func stopRotation () {
// Invalidate display link
self.displayLink?.invalidate()
self.displayLink = nil
}
// Called everytime the display is refreshed
#objc func onFrameInterval (displayLink: CADisplayLink) {
// Get frames per second
let framesPerSecond = Double(displayLink.preferredFramesPerSecond)
// Based on fps, calculate how much target view should spin each interval
let rotationsPerSecond = Double(3)
let anglePerSecond = rotationsPerSecond * (2 * Double.pi)
let anglePerInterval = CGFloat(anglePerSecond / framesPerSecond)
// Rotate target view to match the current angle of the interval
self.targetView.layer.transform = CATransform3DRotate(self.targetView.layer.transform, anglePerInterval, 0, 0, 1)
}
Avoiding the completion closure with recursive calls!
Bit late to this party, but using UIView keyFrame animation & varying stages of rotation for each keyFrame, plus setting the animation curve works nicely. Here's an UIView class function -
class func rotate360(_ view: UIView, duration: TimeInterval, repeating: Bool = true) {
let transform1 = CGAffineTransform(rotationAngle: .pi * 0.75)
let transform2 = CGAffineTransform(rotationAngle: .pi * 1.5)
let animationOptions: UInt
if repeating {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue
} else {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue
}
let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [keyFrameAnimationOptions, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.375) {
view.transform = transform1
}
UIView.addKeyframe(withRelativeStartTime: 0.375, relativeDuration: 0.375) {
view.transform = transform2
}
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
view.transform = .identity
}
}, completion: nil)
}
Looks pretty gnarly, with the weird rotation angles, but as the op & others have found, you can't just tell it to rotate 360
Try this one it works for me, i am rotating image for once
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformRotate(self.imgViewReload.transform, CGFloat(M_PI))
}, completion: {
(value: Bool) in
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformIdentity
}, completion: nil)
})
you can use this function ... just give it the view and the duration
it has two animations the the first rotates the view 180° and the other one rotates it to 360° then the function calls itself which allows it to continue the rotation animation infinitely
func infinite360Animation(targetView: UIView, duration: Double) {
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi )
} completion: { (_) in
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi * 2)
} completion: { (_) in
self.infinite360Animation(targetView: targetView, duration: duration)
}
}
}
You can then call the function like this
infinite360Animatio(targetView: yourView, duration: 3)
On your tabBarController, make sure to set your delegate and do the following didSelect method below:
func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
if let selectedItem:UITabBarItem = tabBarController.tabBar.selectedItem {
animated(tabBar: tabBarController.tabBar, selectedItem: selectedItem)
}
}
fileprivate func animated(tabBar: UITabBar, selectedItem:UITabBarItem){
if let view:UIView = selectedItem.value(forKey: "view") as? UIView {
if let currentImageView = view.subviews.first as? UIImageView {
rotate(imageView: currentImageView, completion: { (completed) in
self.restore(imageView: currentImageView, completion: nil)
})
}
}
}
fileprivate func rotate(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
}, completion: {
(value: Bool) in
completion?(value)
})
}
fileprivate func restore(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.identity
}, completion: {
(value: Bool) in
completion?(value)
})
}

Using Facebook Pop to animate view affine transform

I have the following code that will animate some UITableViewCells from the bottom in a spring manner:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.tableView.reloadData()
let cellsNum = self.tableView.visibleCells() as NSArray
// Hide the tableView and move the cells to below the tableView
self.tableView.alpha = 0.0
for cell in cellsNum as [UITableViewCell] {
cell.transform = CGAffineTransformMakeTranslation(0, self.tableView.bounds.size.height)
println("\(NSStringFromCGAffineTransform(cell.transform))")
//cell.layer.transform = CATransform3DRotate(CATransform3DIdentity, CGFloat(90.0) * CGFloat(M_PI) / CGFloat(180.0), 1.0, 0.0, 0.0)
}
// Show the tableView and animate the cells
self.tableView.alpha = 1.0
for cell in cellsNum as [UITableViewCell] {
UIView.animateWithDuration(1.2, delay: 0.05 * Double(cellsNum.indexOfObject(cell)), usingSpringWithDamping: 0.77, initialSpringVelocity: 0, options: nil, animations: { () -> Void in
cell.transform = CGAffineTransformMakeTranslation(0, 0)
//cell.layer.transform = CATransform3DRotate(CATransform3DIdentity, 0, 1.0, 0.0, 0.0)
}, completion: nil);
}
}
How can I do this in Facebook Pop? I've looked in the source but the closest one was kPopLayerTranslationXY but that the code below didn't work:
for cell in cellsNum as [UITableViewCell] {
let trans = POPSpringAnimation(propertyNamed: kPOPLayerTranslationXY)
trans.toValue = NSValue(CGPoint: CGPointMake(0, 0))
trans.fromValue = NSValue(CGPoint: CGPointMake(0, self.tableView.bounds.size.height))
trans.springBounciness = 10
trans.springSpeed = 5
cell.pop_addAnimation(trans, forKey: "Translation")
}
Possibly define a custom animatable property?
You are animating a layer property so you should add the animation to the layer of the cell, not the cell.
cell.layer.pop_addAnimation(trans, forKey: "Translation")
Also as you are only animating y you could use kPOPLayerTranslationY and set the toValue and fromValue to a float.

Resources