Alternating animation loop - swift

I have a repeating animation that fades a UILabel and UIImage asynchronously but I cant figure out how to make the UIImage appear for longer than the UILabel, i want the animation to alternate between the label and image so the image appears for a duration of 5 seconds and the label appears for a duration of 2 seconds:
override func viewDidLoad() {
super.viewDidLoad()
UIView.animate(withDuration: 5, delay: 2.0, options:[UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse], animations: {
self.fadeIn()
self.fadeOut()
}, completion: nil)
}
func fadeIn () {
self.label.alpha = 0.0
self.image.alpha = 1.0
}
func fadeOut () {
self.label.alpha = 1.0
self.image.alpha = 0.0
}

You can achieve the effect you are after is multiple ways.
In the viewDidLoad instead of creating an animation block and calling your fade methods from within, just simply have separate animation blocks within each of the methods with different delay value.
So remove the UIView.animate from the ViewDidLoad and simply jus call fadeIn() and fadeOut(), and inside your fade methods you would add the UIView.animate.... and set ur desired duration and timing. This way you have a lot more control over you animation and you can tweak the values until you achieve the effect you desire.
Second option would be as previously mentioned to use key frame animation.

Use a repeating keyframe animation with a duration of 7 seconds, consisting of two keyframes:
The first keyframe starts at the start and has a duration that is 5/7 of the total duration, and fades one way.
The second keyframe starts 5/7 of the way through and has a duration that is 2/7 of the total duration, and fades the other way.

Related

UIView.animate() issue

I have a code, where UI elements are changed after user interaction. I use UIView.animate() to refresh UI. The code is pretty simple:
UIView.animate(withDuration: 1.0) {
self.fadeIn()
} completion: { _ in
UIView.animate(withDuration: 1.0) {
self.fadeOut()
}
}
Very important point is that total animation duration from beginning to the end should be 2 seconds. But sometimes, there are no UI changes in fadeIn(), and UIView.animate() just get immediately to completion block. But in that option I want to have a delay for duration time (1 sec), and only after that get to completion block to fadeOut. How I can force animate with duration even if it's nothing to animate in animation block? Thanks in advance!
Maybe don't call fadeOut() in the completion block, and just make sure that it's called with one second delay after the fadeIn() is called?
UIView.animate(withDuration: 1.0) {
self.fadeIn()
}
UIView.animate(withDuration: 1.0, delay: 1.0) {
self.fadeOut()
}

How to (repeat) an animation for a WKInterfaceGroup not using images

I have a very simple animation so naturally I'm trying to avoid image sequences and SpriteKit.
I have simply set a WKInterfaceGroup background to green. I want it to fade in and out alpha 1.0 to 0.2 and 0.2 to 1.0 repeatedly. Call it a pulse or whatever you want.
I can fade it out using:
self.animate(withDuration: 1.0) { row.circleBG.setAlpha(0.2) }.
But how to I fade it back in and repeat the entire animation infinitely?
Even if I create separate fade out and fade in functions only the first call gets executed anyway so that doesn't work either. Even if it did work it won't repeat.
The only repeat method I can find requires an image sequence range like this. row.circleBG.startAnimatingWithImages(in: (need image range), duration: 1.0, repeatCount: 100) But I am not using images.
Of course I could use a green circle image and black circle image but that seems so unnecessary and hacky.
Attempted something like this but it actually wouldn't repeat and I need a fadein + fadeout to repeat together as one animation. The pulse fades in/fades out and repeats.
Also I can only access row.ringStatus. buy creating for (index, value) in. I would like to avoid this code repetition.
var pulseTimer: Timer!
pulseTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(pulse), userInfo: nil, repeats: true)
#objc func pulse() {
for (index, value) in self.messageObject.enumerated() {
guard let row = messagesTable.rowController(at: index) as? MessageListRowController else { continue }
DispatchQueue.main.async {
row.ringStatus.setBackgroundColor(.green)
row.ringStatus.setAlpha(1.0)
row.ringStatus.setAlpha(0.2)
}
}
}

How can I increment a value while UIViewPropertyAnimator is going through the animation process?

How can I increment a value while UIViewPropertyAnimator is going through the animation process? As I have set my object in motion I would like to increment its value as it drops on my screen but I am not sure how to link that with my Animator. Any suggestions? Here is how I have initiated the
let toTravel = CGFloat(blockSize * newInstance.distanceToTravel()) //checks how far it needs to go.
let frame = CGRect(x: x, y: startY, width: width, height: height)
super.init(frame: frame)
backgroundColor = UIColor.clear
addSubBlocksToView(grid: grid, blockSize: blockSize)
animator = UIViewPropertyAnimator(duration: 10.0, curve: .linear) { [unowned self] in
self.center.y += toTravel // need to calculate CODE this it is a static right now
}
As you can see it takes 10 seconds to travel the distance I have calculated. I would like to keep track of each few pixels (30) it has traveled with a counter.
Set up a CADisplayLink when you start the animation. During the animation, you can check the current position (given that it is a linear animation, you could do this easily by initialPosition + time/10 * distance). Once the animation has completed, you can invalidate the display link. The display link provides you with a callback for every frame, which you can use to update your onscreen counter.
Alternatively you could consider using a CADisplayLink to drive the entire animation (and use a custom animation curve driven by your own code.)

Animate Auto Layout Constraints and Alpha on NSView

Swift 3, iOS 10, macOS 10.12.4
I am building an app that runs on both iOS and Mac. On the iOS side, I have successfully animated a UIView. When the user taps on something, a popup appears and animates into position. Here's my code inside the tap event:
self.popupConstraintX.constant = x
self.popupConstraintY.constant = y
UIView.animate(withDuration: 0.25 , delay: 0.0, options: .curveLinear, animations: {
self.graphPopup.alpha = 1.0
self.layoutIfNeeded()
}, completion:nil)
In this case, self refers to a UITableViewCell that holds the graphPopup.
I have built the same thing on the Mac side, but I'm trying to animate graphPopup which is now an NSView. Here's what I have so far inside my click event:
self.popupConstraintX.constant = x
self.popupConstraintY.constant = y
self.view.layoutSubtreeIfNeeded()
NSAnimationContext.runAnimationGroup({_ in
self.graphPopup.alphaValue = 1.0
//Indicate the duration of the animation
NSAnimationContext.current().duration = 0.25
NSAnimationContext.current().allowsImplicitAnimation = true
self.view.updateConstraints()
self.view.layoutSubtreeIfNeeded()
}, completionHandler:nil)
Here self refers to the containing NSViewController. Nothing animates--not the position or the alpha of graphPopup. It just appears and disappears like it's on an Atari in 1985.
Any idea what I'm doing wrong with my NSView animation?
Update
For posterity's sake, here is the working code as suggested by BJ (with a slight tweak to use the implicit animation context):
self.popupConstraintX.constant = x
self.popupConstraintY.constant = y
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.graphPopup.alphaValue = 1.0
self.view.layoutSubtreeIfNeeded()
}, completionHandler:nil)
The modification of alphaValue is happening before you have turned on implicit animation, so the alpha will not be animated. It's unclear to me whether that was intentional or not.
The view is not animating to the position given by the popupConstraints, because you're not actually doing anything inside the animation block that would cause the view's frame to change. In order to trigger an implicit animation, you must not only update constraints; you must also ensure that the view's frame changes within the animation block. If you're using AutoLayout, this is usually done by calling layoutSubtreeIfNeeded.
However, because you updated the constraints and called layoutSubtreeIfNeeded() prior to the animation block, there are no additional frame changes left to make inside the block (unless there's something happening down in updateConstraints() which you haven't shown us.)
You should remove the first call to layoutSubtreeIfNeeded(), or if it's still necessary, put it above the popupConstraint modifications. Then, when you call layoutSubtreeIfNeeded() within the animation block, a new frame will be set based on those changed constraints, and you should see animation happening.
Swift 5
animator() is the key.
You can simply do like this:
NSAnimationContext.runAnimationGroup({ context in
//Indicate the duration of the animation
context.duration = 0.25
self.popupConstraintX.animator().constant = x
self.popupConstraintY.animator().constant = y
self.graphPopup.animator().alphaValue = 1.0
}, completionHandler:nil)

Implementing basic animations with tvOS

I'm hoping someone may be able to help me understand why my tvOS animations are not running.
In my code I have a function like this:
func testAnimation() {
clockLabel.alpha = 0.0
UIView.animateWithDuration(1.0, animations: {
self.clockLabel.alpha = 1.0
})
}
I am calling testAnimation() from within my viewDidLoad(), but no animation ever seems to happen.
I've tested with a few different types of animations, from things like position to opacity, but it seems that no animation ever actually runs in the Simulator.
At this time, my app does not have a focus. All I'm trying to do is load a blank UIView with a label in the middle that fades in.
You're trying to animate your UILabel before it has been displayed. Move your animation from viewDidLoad() to viewDidAppear().
I can confirm this behaviour as this happens for me as well. What I did was setting a delay of 0.1 (afterDelay:), and it is "good enough"
On the other hand, what DID actually work was setting a transform for my views. E.g. (objc though):
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(0.5,0.5);
[UIView animateWithDuration: 0.5 animations:^{
_myView.transform = scaleTransform;
} completion: nil]
Maybe it's a bug in tvOS
Try using UIView Animation, like this (Swift):
clockLabel.alpha = 0
UIView.animateWithDuration(0.5, delay: 0.0, options: .Linear, animations: {
self.clockLabel.alpha = 1.0
}, completion: { finished in
// you may add some code here to make another action right after the animation end, or just delete this comment :)
})
This works on our side.