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)
}
}
}
Related
I'm trying to make a simple animation in WatchKit.
I want to blink the specific color, and then come back to black background. It all works fine... once. After this first blink nothing will happen. I've tried searching but couldn't find relevant answer.
My guess it that I should somehow reset the state of the animation, but couldn't find any way to do that.
This is the code I'm using for the animation:
animate(withDuration: 0.2, animations: {
//set first color
//mainGroup is a WKInterfaceGroup
self.mainGroup.setBackgroundColor(color)
//set back black color
self.mainGroup.setBackgroundColor(UIColor.black)
})
I've researched a bit more. There are no options in animate function in WatchKit, and that is what makes it harder (can't use autoreverse). The solution is not perfect, but it more or less works. You need to first set the desired 'blink' color, and after that animate the transition to black.
self.mainGroup.setBackgroundColor(color)
animate(withDuration: 0.2, animations: {
self.mainGroup.setBackgroundColor(UIColor.black)
})
If you want a more complicated timing, e.g. smooth "in" animation, you can use delayed execution:
animate(withDuration: 0.2) {
self.mainGroup.setBackgroundColor(color)
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.21) {
animate(withDuration: 0.2) {
self.mainGroup.setBackgroundColor(.black)
}
}
I'm animating a clock hand that takes a CGFloat value from 0 to 1. While I have the animation, I would like it to be a lot smoother. The total animation takes 5 seconds, as part of an input variable. How can I make this a lot smoother?
Ideally, I'd like to get all the values from 0 to 1 in 5 seconds...
The clock hand does a complete 360 but is a little choppy
#IBAction func start(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(launchTimer), userInfo: nil, repeats: true)
launchTimer()
}
func launchTimer() {
guard seconds < 4.9 else {
timer.invalidate()
seconds = 0
return
}
seconds += 0.1
clockView.currentPressure = CGFloat(seconds / 5)
clockView.setNeedsDisplay()
}
EDIT
import UIKit
class GaugeView: UIView {
var currentPressure : CGFloat = 0.0
override func draw(_ rect: CGRect) {
StyleKitName.drawGauge(pressure: currentPressure)
}
}
Timer is not appropriate for animations on this scale. 100ms isn't a good step in any case, since it's not a multiple of the frame rate (16.67ms). Generally speaking, you shouldn't try to hand-animate unless you have a specialized problem. See UIView.animate(withDuration:...), which is generally how you should animate UI elements, allowing the system to take care of the progress for you.
For a slightly more manual animation, see CABasicAnimation, which will update a property over time. If you need very manual control, see CADisplayLink, but you almost never need this.
In any case, you must never assume that any timer is called precisely when you ask it to be. You cannot add 0.1s to a value just because you asked to be called in 0.1s. You have to look at what time it really is. Even hard-real-time systems can't promise something will be called at a precise moment. The best you can possibly get is a promise it will be within some tolerance (and iOS doesn't even give you that).
To animate this with UIView (which I recommend), it'll probably be something like:
#IBAction func start(_ sender: Any) {
self.clockView.currentPressure = 0
UIView.animate(withDuration: 5, animations: {
self.clockView.currentPressure = 1
})
}
With a CABasicAnimation (which is more complicated) it would be something like:
currentPressure = 1 // You have to set it to where it's going or it'll snap back.
let anim = CABasicAnimation(keyPath: "currentPressure")
anim.fromValue = 0
anim.toValue = 1
anim.duration = 5
clockView.addAnimation(anim)
Make the time interval smaller to make the animation smoother. That way it will seem like it's gliding around instead of jumping between values.
You can also use spritekit:
import SpriteKit
let wait = SKAction.wait(forDuration: 0.01)
let runAnim = SKAction.run {
launchTimer()
}
let n = SKNode()
n.run(SKAction.repeat(SKAction.sequence([wait, runAnim]), count: 500))
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.
I am using a curl up animation when swiping up, which makes a card fly away to the top - it works fine. I'd like to use the same animation, but to swipe down, so that the card flies away down. I guess I'd have to somehow turn the swipe up animation 180 degrees. Is that possible?
let views = (frontView: self.frontView, backView: self.frontView)
// set a transition style
let transitionOptions = UIViewAnimationOptions.TransitionCurlUp
UIView.transitionWithView(self.flashCardView, duration: 0.5, options: transitionOptions, animations: {
views.frontView.removeFromSuperview()
self.flashCardView.addSubview(views.frontView)
}, completion: { finished in
// any code entered here will be applied
})
The UIViewAnimationOptions struct has another option that will solve this for you:
let transitionOptions = UIViewAnimationOptions.TransitionCurlDown
I've developed a slide show using a UIScrollView and I have implemente a NSTimer to change the images automatic in a lapse of time in the following way :
override func viewDidLoad() {
...
isLandscape = UIApplication.sharedApplication().statusBarOrientation.isLandscape
NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("updateScrollView"), userInfo: nil, repeats: true)
}
The method to call in change :
func updateScrollView(){
var offsetX = scrollView.contentOffset.x
if (self.isLandscape!){
scrollView.setContentOffset(CGPoint(x: offsetX + 1024, y: 0), animated: true)
}
else{
scrollView.setContentOffset(CGPoint(x: offsetX + 768, y: 0), animated: true)
}
}
I create a variable named isLandscape to know when the device is in portrait or landscape and in base of this know how much to sum to the contentOffset.
But for some reason if I rotate the device in the precise moment that the timer it's called the image don't advance correctly to the next one, we can see one part of the previous and one part of the next, of course if I move it wiht my finger all it back to work.
How can I avoid the rotation if the timer it's called in this moment?
Any advice?
Stop (invalidate) the timer when rotation begins, and start it again (create a new one) when rotation has ended. Your view controller gets events that tell you when rotation will start and when it has ended.
(Another possibility: simply detect that rotation has ended and call updateScrollView then and there, to force the next slide in case we are in the "stuck" position. But I think my first suggestion is probably better.)