old swift code is not working (animating a UIView) - ios

i have found this code which is responsible for animating a UIView but unfortunately the code does not work and i can not figure the reason (maybe an older version of swift)
this is the code :
(this is helper function according to the creator)
func moveView(#view:UIView, toPoint destination:CGPoint, completion☹()->())?) {
//Always animate on main thread
dispatch_async(dispatch_get_main_queue(), { () Void in
//Use UIView animation API
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping:
0.6, initialSpringVelocity: 0.3, options:
UIViewAnimationOptions.AllowAnimatedContent, animations: { () ->
Void in
//do actual move
view.center = destination
}, completion: { (complete) -> Void in
//when animation completes, activate block if not nil
if complete {
if let c = completion {
c()
}
}
})
})
}
and this is the animation
//Create your face object (Just a UIImageView with a face as the image
var face = Face();
face.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
//find our trajectory points
var center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
var left = CGPointMake(center.x *-0.3, center.y)
var right = CGPointMake(center.x *2.2, center.y)
//place our view off screen
face.center = right
self.view.addSubview(face)
//move to center
moveView(view: face, toPoint: center) { () -> () in
//Do your Pop
face.pop()
// Move to left
moveView(view: face, toPoint: left, completion: { () -> () in
}
}
and i quote from the creator of the code
General Steps: Create a new face on the right edge of the screen. Make
the face visible. Move the face to the middle of the screen. Pop the
face Start the process with the next face. Move the first face to the
left as soon as the new face gets to the middle.
Actual slide animation Once again, we will do the following here: Move
view off screen on the right Move to center Pop Move to left
To get the repeating effect, just call this method on a timer
and a summary :
UIView’s animation API is very powerful. Both the pop and movement
animations use depend on this API. If you’re stuck with trying to
create an animation, UIView animation block is usually a good place to
start.
NOTE : im a beginner in IOS development if anyone can please explain the code for me

Indeed this moveView method had a few issues, one being it was written for Swift 1 (but there were also some typos, faulty characters and useless operations).
Here's the fixed version:
func moveView(view view:UIView, toPoint destination: CGPoint, afterAnim: ()->()) {
//Always animate on main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Use UIView animation API
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping:
0.6, initialSpringVelocity: 0.3, options:
UIViewAnimationOptions.AllowAnimatedContent, animations: { () -> Void in
//do actual move
view.center = destination
}, completion: { (complete) -> Void in
//if and when animation completes, callback
if complete {
afterAnim()
}
})
})
}
You can use it like this now:
moveView(view: face, toPoint: center) {
//Do your Pop
face.pop()
// Move to left
moveView(view: face, toPoint: left) {
// Do stuff when the move is finished
}
}
Observe the differences between your version and mine to understand what was obsolete/wrong and how I fixed it. I'll help if you're stuck.

Related

UIViewPropertyAnimator repeat

I've taken a look at the answers in the following StackOverflow question but none seem to work for me: on the execution of the completion block, instead of performing the animation again, the program spews out "complete" ad infinitum without animating the view at all.
How can I repeat animation (using UIViewPropertyAnimator) certain number of times?
This is my AnimatorFactory class:
class AnimatorFactory {
#discardableResult
static func rotateRepeat(view: UIView) -> UIViewPropertyAnimator {
let rotate = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0.0, options: [.curveLinear], animations: {
view.transform = CGAffineTransform(rotationAngle: .pi)
}, completion: { _ in
print("complete")
self.rotateRepeat(view: view)
})
return rotate
}
}
It is called as you'd expect with AnimatorFactory.rotateRepeat(view: <someView>)
However, the problem as mentioned above occurs. What I'd expect is that the view would rotate repeatedly until some time that I decide to change or stop it; this is exactly the reason that I have chosen to use UIViewPropertyAnimator instead of UIView.animate(withDuration:animations).
What's the best way then to create interactive, repeatable UIView animations? Much appreciated.
Your code is working fine. The trouble is that your animation does nothing after the first time. You say:
view.transform = CGAffineTransform(rotationAngle: .pi)
The first time, we change the rotation from 0 to pi. That is a change, so there is animation. But after that we just keep saying “stay at pi” over and over. We are at pi and you say to stay there, so there is no change to animate.
What you want each animation to do is add pi, not be pi.
As #matt suggested, I was merely setting the rotation of the view to .pi over and over. So in the completion block I have now set the transform to .identity before kicking the animation off again.
class AnimatorFactory {
#discardableResult
static func rotateRepeat(view: UIView) -> UIViewPropertyAnimator {
let rotate = UIViewPropertyAnimator(duration: 1.0, curve: .linear)
rotate.addAnimations {
view.transform = CGAffineTransform(rotationAngle: .pi)
}
rotate.addCompletion{ _ in
view.transform = .identity
self.rotateRepeat(view: view)
}
rotate.startAnimation()
return rotate
}
}

How to implement the rubber band effect?

I found this code online that implements the rubber band effect when panning a view:
#IBAction func viewDragged(sender: UIPanGestureRecognizer) {
let yTranslation = sender.translationInView(view).y
if (hasExceededVerticalLimit(topViewConstraint.constant)){
totalTranslation += yTranslation
topViewConstraint.constant = logConstraintValueForYPoisition(totalTranslation)
if(sender.state == UIGestureRecognizerState.Ended ){
animateViewBackToLimit()
}
} else {
topViewConstraint.constant += yTranslation
}
sender.setTranslation(CGPointZero, inView: view)
}
func logConstraintValueForYPoisition(yPosition : CGFloat) -> CGFloat {
return verticalLimit * (1 + log10(yPosition/verticalLimit))
}
The resulting effect is shown in the gif below:
However, I have trouble understanding how this code works, and reproducing this effect in my own projects. For instance, one of the things I do not understand is, when panning the green view upwards yTransition is going to be negative and negative numbers do not have logarithms (in the logConstraintValueForYPoisition(:) method). I would really appreciate it if someone could explain to me how this code works step by step.
The original post can be found here.
The log is not what you're thinking of. In fact, the snippet is incomplete. The repo can be found here.
The bouncing animation is here:
func animateViewBackToLimit() {
self.topViewConstraint.constant = self.verticalLimit
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 10, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in
self.view.layoutIfNeeded()
self.totalTranslation = -200
}, completion: nil)
}
The log portion is for moving the green rectangle up. Once you reach an upward threshold (hasExceededVerticalLimit(topViewConstraint.constant)) you want the rectangle to stop moving as quick as you don't want it to keep up with your finger, you do this by calling logConstraintValueForYPoisition.
Note that if you have a positive value x, log(x) < x.

Swift animation for periscope-style comments

I am using this code for periscope-style comments in my iOS app (where the comment bubbles slide up from the bottom): https://github.com/yoavlt/PeriscommentView
And this is the code that actually animates the comments in and out:
public func addCell(cell: PeriscommentCell) {
cell.frame = CGRect(origin: CGPoint(x: 0, y: self.frame.height), size: cell.frame.size)
visibleCells.append(cell)
self.addSubview(cell)
UIView.animateWithDuration(self.config.appearDuration, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
let dy = cell.frame.height + self.config.layout.cellSpace
for c in self.visibleCells {
let origin = c.transform
let transform = CGAffineTransformMakeTranslation(0, -dy)
c.transform = CGAffineTransformConcat(origin, transform)
}
}, completion: nil)
UIView.animateWithDuration(self.config.disappearDuration, delay: self.config.stayDuration, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -> Void in
cell.alpha = 0.0
}) { (Bool) -> Void in
self.visibleCells.removeLast()
cell.removeFromSuperview()
}
}
The problem with the above code is that sometimes when a new comment is added, it shows up overlapping the previous comment. The expected behavior is that the previous comment slides up and the new comment takes its place. I noticed that this mainly happens when you add a new comment after the previous comment starts to fade out, but still has not disappeared.
I tried putting a breakpoint in the self.visibleCells.removeLast(), and it does seem like this gets called only when the last comments completed disappears, so I would expect this to work correctly (because the for loop moves up all the visible cells, and even when a comment is fading out, it is still visible).
Any help with this would be appreciated.
Thanks!
I just got a clone of that repository, run on my device and your problem is trying to rewrite the functionality. Do not do that. Instead, just add this line:
#IBAction func addNewCell(sender: UIButton) {
self.periscommentView.addCell(UIImage(named: "twitterProfile")!, name: "Your_Name_Here", comment: "Your_Comment_Here")
}
That's all! I just checked it and it works perfectly!
Do not try to change alpha or moving up. The library does all of these stuffs! Good luck! ;)

iOS stop animateWithDuration before completion

I have a CollectionView and I want to create an animation inside the CollectionViewCell selected by the user. I chose to use animateKeyframesWithDuration because I want to create a custom animation step by step. My code looks like this:
func animate() {
UIView.animateKeyframesWithDuration(1.0, delay: 0.0, options: .AllowUserInteraction, animations: { () -> Void in
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.5, animations: { () -> Void in
// First step
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: { () -> Void in
// Second step
})
}) { (finished: Bool) -> Void in
if self.shouldStopAnimating {
self.loadingView.layer.removeAllAnimations()
} else {
self.animate()
}
}
}
This is executed inside the custom CollectionViewCell when it is selected.
The problem is that I want to force stop the animation immediately at some certain point. But when I do that, the animation doesn't fully stop, it just moves the remaining animation on a different cell (probably the last reused cell?)
I can't understand why this is happening. I have tried different approaches but none of them successfully stop the animation before normally entering the completion block
Does anyone have any idea about this?
Instead of removing the animations from the layer you could try adding another animation with a very short duration that sets the view properties that you want to stop animating.
Something like this:
if self.shouldStopAnimating {
UIView.animate(withDuration: 0.01, delay: 0.0, options: UIView.AnimationOptions.beginFromCurrentState, animations: { () -> Void in
//set any relevant properties on self.loadingView or anything else you're animating
//you can either set them to the final animation values
//or set them as they currently are to cancel the animation
}) { (completed) -> Void in
}
}
This answer may also be helpful.

How to access radius dynamically during animation? (possible during CGAffineTransform)

I'm using two simple CGAffineTransforms to scale a circular UIView up and back down again, but I'd like to be able to dynamically access the radius in touchesBegan() while the animation is happening and check if it equals a certain value at that point.
The difficulty is that I need to check the radius while the animation is still happening. I'm not completely sold on the CGAffineTransforms approach, so I'm more than happy to animate the circle in another way if it would mean being able to access the radius. Thank you!
I'm at a loss on how to do that. Here is my code below (tips welcome!):
func grow() {
UIView.animateWithDuration(2.3, delay: 0.0,
options: UIViewAnimationOptions.CurveEaseIn,
animations: {
self.circle2.transform = CGAffineTransformMakeScale(1.5, 1.5)
},
completion: ({ finished in
if (finished) {
self.shrink()
}
}))
}
func shrink() {
UIView.animateWithDuration(2.3, delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
self.circle2.transform = CGAffineTransformMakeScale(0.5, 0.5)
},
completion: ({ finished in
if (finished) {
self.grow()
}
}))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/*
if radius == certain value
{
//do something
}
*/
}
First, I would recommend doing this animation using a plain CABasicAnimation or CAKeyframeAnimation, because you can use anim.repeatCount = .infinity rather than completion handlers that call each other back and forth.
The current state of the animation can be accessed by circle2.layer.presentationLayer. As mentioned in this question, you can use its frame which will take the transform into account.

Resources