Title sort of explains it all. I have a SKSpriteNode, called bar. It's assigned an image that is a red longways rectangle. I want it to slowly decrease in length over an interval, while keeping the width the same. In other words, imagine it sort of folding in on itself vertically.
You can use SKActions to do that. SKAction.scaleYTo function can be use to scale the height of the sprite. The anchor point can be shifted to one edge to prevent the rectangle from scaling towards the middle.
var sprite = SKSpriteNode(imageNamed: "redBar.png")
sprite.anchorPoint = CGPointMake(0, 0)
sprite.position = CGPointMake(95, 100)
self.addChild(sprite)
let duration = 10.0
let finalHeightScale:CGFloat = 0.0
let scaleHeightAction = SKAction.scaleYTo(finalHeightScale, duration: duration)
sprite.runAction(scaleHeightAction, completion: { () -> Void in
println("Height is zero")
})
Related
I have a UIView A. I put a icon on view A and try to use pan gesture to scale and move this view A.
I have try many solution, but i can not make it.
Please suggest help me?
More detail:
I will add more detail.
I have view A, add sub on self view. And i want when i draw panGestureRegonizer on view A, view A will move follow draw.
And while moving view A will scale. View A will scale to smaller when view move to top/left/bottom/right of sreen and scale to larger when view move to center of screen.
Let's say you have vc - ViewController and your UIView A is a subview of vc. You can add UIPanGestureRecognizer to A. Then drag the panGestureRegonizer to your vc as an action:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
//your code here
}
From the sender you can check view , location and state of the action. The state might impact your code in some cases, depending on what you are trying to achieve.
Then you need to modify the action to this:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
UIView.animateKeyframes(withDuration: 0.1, delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear, animations: {
let location = sender.location(in: sender.view?.superview)
sender.view?.center = location
})
}
Here sender.view?.superview equals vc.view. This code snippet will detect the pan gesture, and then will move A so A.center is matching the gesture's location. Note that duration 0.1 is giving smooth animation effect to the movement.
This will give you "move" functionality with pan gesture.
EDIT for scaling:
Logic: You have coordinate system(CS) with center, x and y. When the user uses pan gesture, he/she generates sequence of points in the CS. So our task is to measure the distance between the center of the CS and users' points. When we have the furthest distance, we can calculate scale factor for our scaling view.
var center: CGPoint! //center of the CS
let maxSize: CGSize = CGSize.init(width: 100, height: 100) // maximum size of our scaling view
var maxLengthToCenter: CGFloat! //maximum distance from the center of the CS to the furthest point in the CS
private func prepareForScaling() {
self.center = self.view.center //we set the center of our CS to equal the center of the VC's view
let frame = self.view.frame
//the furthest distance in the CS is the diagonal, and we calculate it using pythagoras theorem
self.maxLengthToCenter = (frame.width*frame.width + frame.height*frame.height).squareRoot()
}
Then we need to call our setup functional to have our data ready for scaling functionality - we can do this in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.prepareForScaling()
}
Then we need a helper function to calculates the scaled size of our view, for user's pan gesture current position on the screen.
private func scaledSize(for location: CGPoint) -> CGSize {
//calculate location x,y differences from the center
let xDifference = location.x - self.center.x
let yDifference = location.y - self.center.y
//calculate the scale factor - note that this factor will be between 0.0(center) and 0.5(diagonal - furthest point)
//It is due our measurement - from center to view's edge. Consider multiplying this factor with your custom constant.
let scaleFactor = (xDifference*xDifference + yDifference*yDifference).squareRoot()/maxLengthToCenter
//create scaled size with maxSize and current scale factor
let scaledSize = CGSize.init(width: maxSize.width*(1-scaleFactor), height: maxSize.height*(1-scaleFactor))
return scaledSize
}
And finally, we need to modify our pan gesture action to change the size of A:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
UIView.animateKeyframes(withDuration: 0.1, delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear, animations: {
let location = sender.location(in: sender.view?.superview)
sender.view?.frame = CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: self.scaledSize(for: location))
sender.view?.center = location
})
}
I have view A, add sub on self view. And i want when i draw panGestureRegonizer on view A, view A will move follow draw.
And while moving view A will scale. View A will scale to smaller when view move to top/left/bottom/right of sreen and scale to larger when view move to center of screen.
I have try many solution, but i can not make it.
Please suggest help me?
Logic: You have coordinate system(CS) with center, x and y. When the user uses pan gesture, he/she generates sequence of points in the CS. So our task is to measure the distance between the center of the CS and users' points. When we have the furthest distance, we can calculate scale factor for our scaling view.
var center: CGPoint! //center of the CS
let maxSize: CGSize = CGSize.init(width: 100, height: 100) // maximum size of our scaling view
var maxLengthToCenter: CGFloat! //maximum distance from the center of the CS to the furthest point in the CS
private func prepareForScaling() {
self.center = self.view.center //we set the center of our CS to equal the center of the VC's view
let frame = self.view.frame
//the furthest distance in the CS is the diagonal, and we calculate it using pythagoras theorem
self.maxLengthToCenter = (frame.width*frame.width + frame.height*frame.height).squareRoot()
}
Then we need to call our setup functional to have our data ready for scaling functionality - we can do this in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.prepareForScaling()
}
Then we need a helper function to calculates the scaled size of our view, for user's pan gesture current position on the screen.
private func scaledSize(for location: CGPoint) -> CGSize {
//calculate location x,y differences from the center
let xDifference = location.x - self.center.x
let yDifference = location.y - self.center.y
//calculate the scale factor - note that this factor will be between 0.0(center) and 0.5(diagonal - furthest point)
//It is due our measurement - from center to view's edge. Consider multiplying this factor with your custom constant.
let scaleFactor = (xDifference*xDifference + yDifference*yDifference).squareRoot()/maxLengthToCenter
//create scaled size with maxSize and current scale factor
let scaledSize = CGSize.init(width: maxSize.width*(1-scaleFactor), height: maxSize.height*(1-scaleFactor))
return scaledSize
}
And finally, we need to modify our pan gesture action to change the size of A:
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
UIView.animateKeyframes(withDuration: 0.1, delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear, animations: {
let location = sender.location(in: sender.view?.superview)
sender.view?.frame = CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: self.scaledSize(for: location))
sender.view?.center = location
})
}
I'm learning how to use SpriteKit and would like to have a circle that:
Grows and shrinks smoothly
Pulses with a colour
Is displayed with crisp edges
So far, I've come up with the following code:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
addCircle()
}
func addCircle(){
let circle = SKShapeNode(circleOfRadius: 50 ) // Size of Circle
circle.position = CGPointMake(frame.midX, frame.midY) //Middle of Screen
circle.glowWidth = 0.0 // No border
circle.fillColor = SKColor.yellowColor() // Start as yellow
let actualDuration = 1 // Animate for 1s
// Basic actions for grow, shrink, colour up, colour down
let actionGrow = SKAction.scaleTo(CGFloat(2), duration: NSTimeInterval(actualDuration))
actionGrow.timingMode = SKActionTimingMode.EaseInEaseOut
let actionShrink = SKAction.scaleTo(CGFloat(0.5), duration: NSTimeInterval(actualDuration))
actionShrink.timingMode = SKActionTimingMode.EaseInEaseOut
let actionColorUp = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1.0, duration: NSTimeInterval(actualDuration))
let actionColorDown = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 0.0, duration: NSTimeInterval(actualDuration))
// Combine the actions
let actionGrowWithColor = SKAction.group([actionGrow,actionColorUp])
let actionShrinkWithColor = SKAction.group([actionShrink,actionColorDown])
// Run and repeat
circle.runAction(SKAction.repeatActionForever(SKAction.sequence([actionGrowWithColor,actionShrinkWithColor])))
// Add the circle
self.addChild(circle)
}
}
The first of my three criteria are met, but the other two are not.
As the SKShapeNode is not a vector, as it grows the edges are not crisp. Is there a better way to draw a circle, or should I just start with a circle that is sufficiently large?
Is there a reason why the colorizeWithColor section doesn't appear to have any effect?
Many thanks in advance!
I am creating an animation that I want to use when the app is retrieving some data online. The idea is that I have some dots in a row, they will be scaled smaller that their original sizes then return to their original size and all of this with a small delay between each scaling. The animation is repeated and use auto-reverse mode.
To do that I create some dots using a core graphic method, add them to a view and position them using a CGAffineTransformMakeTranslation transformation. Then I use a loop to animate them one by one with a delay and I use a CGAffineTransformScale transformation for scaling.
Problem: I don't get the expected animation (at least what I'm expecting). When the dots are being scaled, they also move back to their original position.
Can someone enlighten me why there is a translate transformation while in the UIView animation, I'm only specifying a scaling?
Here is the code:
private var dots = [UIImage]()
public init(numberOfDots: Int, hexaColor: Int, dotDiameter: CGFloat = 30, animationDuration: NSTimeInterval = 1) {
self.dotDiameter = dotDiameter
self.animationDuration = animationDuration
for _ in 0 ..< numberOfDots {
dots.append(GraphicHelper.drawDisk(hexaColor, rectForDisk: CGRect(x: 0, y: 0, width: dotDiameter, height: dotDiameter), withStroke: false))
}
let spaceBetweenDisks: CGFloat = dotDiameter / 3
let viewWidth: CGFloat = CGFloat(numberOfDots) * dotDiameter + CGFloat(numberOfDots - 1) * spaceBetweenDisks
super.init(frame: CGRectMake(0, 0, viewWidth, dotDiameter))
setup()
}
private func setup() {
for (i, dot) in dots.enumerate() {
let dotImageView = UIImageView(image: dot)
addSubview(dotImageView)
let spaceBetweenDisks: CGFloat = dotDiameter / 3
let xOffset = CGFloat(i) * (dotDiameter + spaceBetweenDisks)
dotImageView.transform = CGAffineTransformMakeTranslation(xOffset, 0)
}
}
public func startAnimation() {
for i in 0 ..< self.dots.count {
let dotImageView: UIImageView = self.subviews[i] as! UIImageView
let transformBeforeAnimation = dotImageView.transform
let delay: NSTimeInterval = NSTimeInterval(i)/NSTimeInterval(self.dots.count) * animationDuration
UIView.animateWithDuration(animationDuration, delay: delay, options: [UIViewAnimationOptions.Repeat, UIViewAnimationOptions.Autoreverse], animations: {
dotImageView.transform = CGAffineTransformScale(dotImageView.transform, 0.05, 0.05)
}, completion: { finished in
dotImageView.transform = CGAffineTransformIdentity
dotImageView.transform = transformBeforeAnimation
})
}
}
EDIT:
I found a fix but I don't understand how come it's fixing it. So if anyone can explain.
I added these 2 lines:
dotImageView.transform = CGAffineTransformIdentity
dotImageView.transform = transformBeforeAnimation
before this line in startAnimation:
dotImageView.transform = CGAffineTransformScale(dotImageView.transform, 0.05, 0.05)
Combining translate and scale transforms is confusing and hard to get right.
I have to spend far too much time with graph paper and deep thought in order to figure it out, and I'm too tired for that right now.
Don't do that. Place your dot image views by moving their center coordinates, and leave the transform at identity. Then when you scale them they should scale in place like you want.
Note that if you want them to move and scale at the same time you can both alter the view's center property and it's transform scale in the same animateWithDuration call and it works correctly. (Not so with changing the frame by the way. If you change the transform then the frame property doesn't work correctly any more. Apple's docs say that the results of reading/writing the frame property of a view with a non-identity transform are "undefined".)
Are you sure its going back to its original position and not scaling based on the original center point instead? Try changing the order of applying transforms by doing this:
public func startAnimation() {
for i in 0 ..< self.dots.count {
let dotImageView: UIImageView = self.subviews[i] as! UIImageView
let transformBeforeAnimation = dotImageView.transform
let delay: NSTimeInterval = NSTimeInterval(i)/NSTimeInterval(self.dots.count) * animationDuration
UIView.animateWithDuration(animationDuration, delay: delay, options: [UIViewAnimationOptions.Repeat, UIViewAnimationOptions.Autoreverse], animations: {
// make scale transform separately and concat the two
let scaleTransform = CGAffineTransformMakeScale(0.05, 0.05)
dotImageView.transform = CGAffineTransformConcat(transformBeforeAnimation, scaleTransform)
}, completion: { finished in
dotImageView.transform = CGAffineTransformIdentity
dotImageView.transform = transformBeforeAnimation
})
}
}
From apple docs:
Note that matrix operations are not commutative—the order in which you concatenate matrices is important. That is, the result of multiplying matrix t1 by matrix t2 does not necessarily equal the result of multiplying matrix t2 by matrix t1.
So, keep in mind that assigning a transformation creates a new affine transformation matrix, and concatenation will modify the existing matrix with the new one - the order you apply these in can create different results.
To make this work, I also updated the value of your translation on dotImageView. It needs to be requiredTranslation / scale.. if applying the translation before the scale. So in your viewDidLoad:
dotImageViewtransform = CGAffineTransformMakeTranslation(1000, 0)
And then the animation:
// make scale transform separately and concat the two
let scaleTransform = CGAffineTransformMakeScale(0.05, 0.05)
self.card.transform = CGAffineTransformConcat(transformBeforeAnimation, scaleTransform)
I have a UIImageView that I want to scale its outter edges towards the center (the top and bottom edges to be precise). The best I am able to get so far is just one edge collapsing into the other (which doesn't move). I want both edges to collapse towards the middle.
I have tried setting the the anchor point to 0.5,0.5 but that doesnt address the issue (nothing changes).
Is there a way to setup an ImageView such that it will scale inwards towards the middle so the outter edges collapse towards the center?
Here was my best attempt:
override public func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
var uimg = UIImage(named: "img.PNG")!
var img:UIImageView = UIImageView(image: uimg)
img.layer.anchorPoint = CGPointMake(0.5, 0.5)
view.addSubview(img)
UIView.animateWithDuration(5, animations:
{ () -> Void in
img.frame = CGRectMake(img.frame.origin.x,img.frame.origin.y,img.frame.width, 0)
})
{ (finished) -> Void in
//
}
}
You can use scaling using UIView's transform property. It seems like scaling does not allow you to make the value 0 and it goes to the final value without any animation. So, you could basically make the scaling y value negligible like 0.001.
imageView.transform = CGAffineTransformMakeScale(1, 0.001)
You could also use core animation to scale, using CABasicAnimation, this would be more simpler as this,
let animation = CABasicAnimation(keyPath: "transform.scale.y")
animation.toValue = 0
animation.duration = 2.0
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
imageView.layer.addAnimation(animation, forKey: "anim")
Or you can also use the approach you have been using, simply setting the frame,
var finalFrame = imageView.frame
finalFrame.size.height = 0
finalFrame.origin.y = imageView.frame.origin.y + (imageView.frame.size.height / 2)
UIView.animateWithDuration(2.0, animations: { () -> Void in
self.imageView.frame = finalFrame
})
Your new frame has the origin in the same place. You need to move the origin down, at the same time as you reduce the height:
img.frame = CGRectMake(img.frame.origin.x, img.frame.origin.y + (img.frame.size.height/2), img.frame.size.width, 0);