Swift : Color Changing SKSpriteNode on repeat - swift

I want to change the color of my SKSpriteNode similar to a strobe light how it constant changes colors then repeats. I can do it with a UIView like this:
UIView.animateWithDuration(2, delay: 0.0, options:[UIViewAnimationOptions.Repeat, UIViewAnimationOptions.Autoreverse], animations: {
self.view.backgroundColor = UIColor.blackColor()
self.view.backgroundColor = UIColor.greenColor()
self.view.backgroundColor = UIColor.grayColor()
self.view.backgroundColor = UIColor.redColor()
}, completion: nil)
but i want to do it to a sksprite node. I did something like this and created a bunch of them but it was problematic, and i feel there might be a better way.
let changeBlue = SKAction.colorizeWithColor(UIColor.blueColor(), colorBlendFactor: 1.0, duration: 2.0)
self.runAction(changeBlue)
let changeRed = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1.0, duration: 2.0)
self.runAction(changeRed)

You're probably looking for sequence(_:). See Creating Actions That Combine or Repeat Other Actions in the SKAction docs. Also, repeatActionForever(_:) to make the action repeat indefinitely.
let colorBlue = SKAction.colorizeWithColor(UIColor.blueColor(), colorBlendFactor: 1.0, duration: 2.0)
let colorRed = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1.0, duration: 2.0)
let colorizeSequence = SKAction.sequence([colorBlue, colorRed])
runAction(SKAction.repeatActionForever(colorizeSequence))

Related

UISlider will not move if two transform are made

I have a UISlider that was implemented through Storyboard. I added a Keypath layer.transform.rotation.z with a value of 1.57 in order to make the UISlider vertical.
I also have a MoveRight UIButton that controls 3 elements (Two UIImageViews and One UISlider) to move in unison. The code for the transform is shown below
#IBAction func MoveRight(_ sender: Any) {
// Used to Show Reset Button for Right
ResetOrigin.isHidden = false
let yPosition1 = myslider.frame.origin.y
let yPosition2 = ImageView.frame.origin.y
let yPosition3 = ImageView2.frame.origin.y
UIView.animate(withDuration: 0.0, delay: 0.0, options: [], animations: {
self.myslider.transform = CGAffineTransform(translationX: 0.001, y: yPosition1)
self.ImageView.transform = CGAffineTransform(translationX: 0.001, y: yPosition2)
self.ImageView2.transform = CGAffineTransform(translationX: 0.001, y: yPosition3)
}, completion: nil)
}
When the code is like this the movement works great although the UISlider is no longer Vertical after the transform. And when I try to add this line of code to the animation
self.myslider.transform = CGAffineTransform(rotationAngle: (CGFloat.pi / 2))
The slider will no longer move while the other 2 Elements are affected by the transform. I tried doing a separate .animate to see if that would resolve the problem but the same result is given. Any ideas on how to solve this issue would be greatly appreciated

CABasicAnimation delay between repeat

The code below creates a "shimmer" effect and works great. (The look of reflected light moving across a glossy surface.) It's currently set to repeat forever which is fine, however, I'd like there to be a pause between each shimmer. I can delay the start of the effect, but I can't seem to figure out how to delay each run. Bonus points if the delay time is random.
func startShimmering() {
let light = UIColor.white.cgColor
let alpha = UIColor.white.withAlphaComponent(0.0).cgColor
let gradient = CAGradientLayer()
gradient.colors = [alpha, light, alpha]
gradient.frame = CGRect(x: -self.bounds.size.width, y: 0, width: 3 * self.bounds.size.width, height: self.bounds.size.height)
gradient.startPoint = CGPoint(x: 1.0, y: 0.525)
gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
gradient.locations = [0.1, 0.5, 0.9]
self.layer.mask = gradient
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = [0.0, 0.1, 0.2]
animation.toValue = [0.8, 0.9, 1.0]
animation.duration = 1.5
animation.repeatCount = HUGE
gradient.add(animation, forKey: "shimmer")
}
As mentioned in Kevin's answer, you can use a CAAnimationGroup to add a delay to the end of your shimmer animation.
The "shimmer" animation will have a duration longer than the group's animation. Any difference between these two durations will be the delay between animations.
Here is the code to do so, ready for a Swift Playground:
class ShimmerView: UIView {
func startShimmering() {
let light = UIColor.white.cgColor
let alpha = UIColor.white.withAlphaComponent(0.0).cgColor
let gradient = CAGradientLayer()
gradient.colors = [alpha, light, alpha]
gradient.frame = CGRect(x: -self.bounds.size.width, y: 0, width: 3 * self.bounds.size.width, height: self.bounds.size.height)
gradient.startPoint = CGPoint(x: 1.0, y: 0.525)
gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
gradient.locations = [0.1, 0.5, 0.9]
self.layer.mask = gradient
let shimmer = CABasicAnimation(keyPath: "locations")
shimmer.fromValue = [0.0, 0.1, 0.2]
shimmer.toValue = [0.8, 0.9, 1.0]
shimmer.duration = 1.5
shimmer.fillMode = kCAFillModeForwards
shimmer.isRemovedOnCompletion = false
let group = CAAnimationGroup()
group.animations = [shimmer]
group.duration = 2
group.repeatCount = HUGE
gradient.add(group, forKey: "shimmer")
}
}
let view = ShimmerView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
PlaygroundPage.current.liveView = view
view.backgroundColor = .blue
view.startShimmering()
Here is the end result:
(The gif animates the shimmer three times, which is why every third "shimmer" the delay is shorter.)
I haven't tried this myself but I believe you can wrap your animation in an animation group, set the group's duration to longer than the real animation's duration, and then set the group to repeat.
If that doesn't work, you could use a keypath animation instead, where most of the keypath is a reproduction of the normal animation, and the rest of the keypath just keeps the animation value pinned to either the start or end value.
If you want a random delay, your only choice is to actually run a timer (or use a CAAnimationDelegate) to set up a new animation each time.

How to transform a circle into a line in swift?

So i have a circe with a certain pattern. I want to make transform this circle into a line. Any ideas how i could accomplish this?
I did have a look at making a UIBezierPath over the circle for then to transform this into a line without any help. I've also seen a link doing this in opencv (fast Cartesian to Polar to Cartesian in Python) but do prefer to do this in native swift.
If you don't care about the nature of the transition, the easiest way is to do CABasicAnimation, animating the path of a CAShapeLayer from one path to another. And to achieve a patterned path with CAShapeLayer is actually to have two overlapping CAShapeLayer objects, one with a dash pattern on top of one without.
let fromPath = UIBezierPath(arcCenter: view.center, radius: 100, startAngle: 0, endAngle: CGFloat(M_PI) * 2.0, clockwise: true)
let toPath = UIBezierPath()
toPath.moveToPoint(CGPoint(x: view.frame.size.width / 2.0 - CGFloat(M_PI) * 100.0, y:view.center.y))
toPath.addLineToPoint(CGPoint(x: view.frame.size.width / 2.0 + CGFloat(M_PI) * 100.0, y: view.center.y))
let shapeLayer = CAShapeLayer()
shapeLayer.path = fromPath.CGPath
shapeLayer.lineWidth = 5
shapeLayer.strokeColor = UIColor.redColor().CGColor
shapeLayer.fillColor = UIColor.clearColor().CGColor
let shapeLayer2 = CAShapeLayer()
shapeLayer2.path = fromPath.CGPath
shapeLayer2.lineWidth = 5
shapeLayer2.strokeColor = UIColor.blackColor().CGColor
shapeLayer2.fillColor = UIColor.clearColor().CGColor
shapeLayer2.lineDashPattern = [100,50]
view.layer.addSublayer(shapeLayer)
view.layer.addSublayer(shapeLayer2)
And
shapeLayer.path = toPath.CGPath
shapeLayer2.path = toPath.CGPath
CATransaction.begin()
CATransaction.setAnimationDuration(5)
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = fromPath.CGPath
animation.toValue = toPath.CGPath
shapeLayer.addAnimation(animation, forKey: nil)
let animation2 = CABasicAnimation(keyPath: "path")
animation2.fromValue = fromPath.CGPath
animation2.toValue = toPath.CGPath
shapeLayer2.addAnimation(animation, forKey: nil)
CATransaction.commit()
Yielding:
If you want more control over the nature of the transition, then you have to resort to more manual techniques, e.g. a CADisplayLink in which you manually adjust the path. But that's more complicated.
try this code, The animation is not perfect, but you will be able to convert an image to a line this way. I have to say that Rob's solution above is far more superior and if I were give a choice, i would do it his way.
The below code is very self-explanatory, but if in case of any doubt, feel free to ask.
To make the animation happen, tap the yellow button.
class ViewController: UIViewController{
var imageView : UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.layer.cornerRadius = 120
image.backgroundColor = UIColor.clearColor()
image.layer.borderWidth = 3.0
image.layer.borderColor = UIColor.blackColor().CGColor
return image
}()
var animatorButton : UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = UIColor.blueColor()
button.layer.borderWidth = 3.0
button.layer.borderColor = UIColor.blackColor().CGColor
button.backgroundColor = UIColor.yellowColor()
return button
}()
var constraintHeight = [NSLayoutConstraint]()
override func viewDidLoad() {
view.addSubview(imageView)
view.addSubview(animatorButton)
view.addConstraint(view.centerXAnchor.constraintEqualToAnchor(imageView.centerXAnchor))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[imageView(240)]", options: NSLayoutFormatOptions(), metrics: nil, views: ["imageView": imageView]))
self.constraintHeight = NSLayoutConstraint.constraintsWithVisualFormat("V:|-100-[imageView(240)]", options: NSLayoutFormatOptions(), metrics: nil, views: ["imageView": imageView])
view.addConstraints(constraintHeight)
view.addConstraint(animatorButton.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-450-[Button(40)]", options: NSLayoutFormatOptions(), metrics: nil, views: ["Button": animatorButton]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[Button]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["Button": animatorButton]))
animatorButton.addTarget(self, action: #selector(ViewController.animateImageToLine), forControlEvents: .TouchUpInside)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func animateImageToLine() {
view.removeConstraints(constraintHeight)
constraintHeight = NSLayoutConstraint.constraintsWithVisualFormat("V:|-100-[imageView(3)]", options: NSLayoutFormatOptions(), metrics: nil, views: ["imageView": imageView])
view.addConstraints(constraintHeight)
UIView.animateWithDuration(0.3, animations: {
self.imageView.layer.cornerRadius = 0
self.view.layoutIfNeeded()
}, completion: { success in
self.imageView.backgroundColor = UIColor.blackColor()
self.imageView.layer.borderWidth = 3.0
})
}
}

Word by Word UILabel animation from left to right in swift

I have a uilabel with a text suppose " This is a label." I want this label to be displayed one word at a time flying from outside screen to the UILable position..
something like
label.
a label.
is a label.
This is a label.
How can i get such animation
I have found a way to do as i wished.
class ViewController: UIViewController {
#IBOutlet var sampleLabel: UILabel!
var slogan = "This is a slogan."
var xdir = 250
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let sampleLabelFrame = sampleLabel.frame
let ypos = sampleLabelFrame.origin.y
var sloganArray = slogan.componentsSeparatedByString(" ")
sloganArray = sloganArray.reverse()
var i = 0.0
for word in sloganArray{
let label: UILabel = UILabel(frame: CGRect(x: -100, y:ypos, width: 60, height: 20))
label.text = word
view.addSubview(label)
let width = label.intrinsicContentSize().width
var labelFramewidth = label.frame
labelFramewidth.size.width = width
label.frame = labelFramewidth
self.xdir = self.xdir - Int(width)-4
UIView.animateWithDuration(0.7, delay: i, options: .CurveEaseOut, animations: {
var labelframe = label.frame
labelframe.origin.x = CGFloat(self.xdir)
label.frame = labelframe
}, completion: { finished in
})
i+=0.5
}
}
}
hope this helps others in need of something like this.
This might help you on the way to achieve what you want:
http://helpmecodeswift.com/animation/creating-animated-labels
EDIT:
Flying label code:
meme1.text = "Brace yourself!"
meme1.font = UIFont.systemFontOfSize(25)
meme1.textColor = UIColor.whiteColor() // This is all just styling the Label
meme1.sizeToFit()
meme1.center = CGPoint(x: 200, y: -50) // Starting position of the label
view.addSubview(meme1) // Important! Adding the Label to the Subview, so we can actually see it.
//Animation options. Play around with it.
UIView.animateWithDuration(0.9, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:50 ) // Ending position of the Label
}, completion: nil)
UIView.animateWithDuration(0.6, delay: 1.1, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:150+90 )
}, completion: nil)
I have no experience with it myself, but it seems to get your job done. You can eventually se the starting point outside the screen and let it animate over to the screen. That should give the desired effect.

Animating button border in swift

I recently learned how to animate a button using alpha so that it fades in/out in Swift. However, if I would like to only have the alpha of the border itself change, the animation does not seem to be working. Instead, it "jumps" from state to state.
UIView.animateWithDuration(1.0, delay: 0.0, options: nil, animations: {
var borderColor = UIColor(red: 0.41, green: 1.28, blue: 1.85, alpha: 0.0)
self.startButton.layer.borderColor = borderColor.CGColor
}, completion: nil);
The above code for example does not animate, instead it produces a "jump" between alpha 1.0 and 0.0 of the border.
However, this would work fine (changing the alpha of the entire button):
UIView.animateWithDuration(1.0, delay: 0.0, options: nil, animations: {
self.startButton.alpha = 1;
}, completion: nil);
Is there any way to get around this issue?
Here's a simple solution:
let borderWidth:CABasicAnimation = CABasicAnimation(keyPath: "borderWidth")
borderWidth.fromValue = 0
borderWidth.toValue = 0.9
borderWidth.duration = 0.5
yourView.layer.borderWidth = 0.5
yourView.layer.borderColor = UIColor.whiteColor().CGColor
yourView.layer.addAnimation(borderWidth, forKey: "Width")
yourView.layer.borderWidth = 0.0
It works for me.
You can't animate parts of the layer using UIView animations. Also, I don't think it's possible to animate the alpha value of the border. To achieve a similar effect you can animate the width of your border:
I made this UIView extension:
extension UIView {
func animateBorderWidth(toValue: CGFloat, duration: Double = 0.3) {
let animation = CABasicAnimation(keyPath: "borderWidth")
animation.fromValue = layer.borderWidth
animation.toValue = toValue
animation.duration = duration
layer.add(animation, forKey: "Width")
layer.borderWidth = toValue
}
}
and then show the border of your view by using:
startButton.animateBorderWidth(toValue: 1, duration: 0.5)
and hide it by using:
startButton.animateBorderWidth(toValue: 0)
Features such as the borderColor and borderWidth are properties of the button's layer. That tells you that you must use core animation of the layer - not view animation, as your code tries to do - to animate such features. It works perfectly well; in this animation, I demonstrate what happens when you use core animation to animation the borderWidth and the cornerRadius together:
Animation of the borderColor would work similarly.