Timer animation not starting - swift

I'm trying to animate a circular progress timer animation for a timer app, but the timer counts and the animation never starts.
import UIKit
class TimerPage: UIViewController {
let timeLeftShapeLayer = CAShapeLayer()
let bgShapeLayer = CAShapeLayer()
var timeLeft: TimeInterval = 60
var endTime: Date?
var timeLabel = UILabel()
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.94, alpha: 1.0)
drawBgShape()
drawTimeLeftShape()
addTimeLabel()
// here you define the fromValue, toValue and duration of your animation
strokeIt.fromValue = 0
strokeIt.toValue = 1
strokeIt.duration = 60
// add the animation to your timeLeftShapeLayer
timeLeftShapeLayer.add(strokeIt, forKey: nil)
// define the future end time by adding the timeLeft to now Date()
endTime = Date().addingTimeInterval(timeLeft)
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
// here you create your basic animation object to animate the strokeEnd
let strokeIt = CABasicAnimation(keyPath: "strokeEnd")
func drawBgShape() {
bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius:
150, startAngle: (3*CGFloat.pi) / 2, endAngle: -CGFloat.pi / 2, clockwise: false).cgPath
bgShapeLayer.strokeColor = UIColor.blue.cgColor
bgShapeLayer.fillColor = UIColor.clear.cgColor
bgShapeLayer.lineWidth = 15
view.layer.addSublayer(bgShapeLayer)
}
func drawTimeLeftShape() {
timeLeftShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius:
100, startAngle: (3*CGFloat.pi) / 2, endAngle: -CGFloat.pi / 2, clockwise: false).cgPath
timeLeftShapeLayer.strokeColor = UIColor.white.cgColor
timeLeftShapeLayer.fillColor = UIColor.clear.cgColor
timeLeftShapeLayer.lineWidth = 15
view.layer.addSublayer(timeLeftShapeLayer)
}
func addTimeLabel() {
timeLabel = UILabel(frame: CGRect(x: view.frame.midX-50 ,y: view.frame.midY-25, width: 100, height: 50))
timeLabel.textAlignment = .center
timeLabel.text = timeLeft.time
view.addSubview(timeLabel)
}
#objc func updateTime() {
if timeLeft > 0 {
timeLeft = endTime?.timeIntervalSinceNow ?? 0
timeLabel.text = timeLeft.time
} else {
timeLabel.text = "00:00"
timer.invalidate()
}
}
}
extension TimeInterval {
var time: String {
return String(format:"%02d:%02d", Int(self/60), Int(ceil(truncatingRemainder(dividingBy: 60))) )
}
}
I tested this out on a separate test project by itself, and it works perfectly fine. But when I add it to my actual app as part of a segue, the animation never starts.

Related

Circle Progress Bar for Swift Count down

Inside the circle progress bar there is a constantly changing number (eg 250, 300, 1000). Whenever I click, the number will decrease and circle progress bar will move. I did it with the time counter. But I want to do it with my control. So when I click the button it will move, if I don't click it won't move.
My code :
import UIKit
class ViewController: UIViewController {
let shapeLAyer = CAShapeLayer()
let progressLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
let center = view.center
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi/2, endAngle: 2*CGFloat.pi, clockwise: true)
progressLayer.path = circularPath.cgPath
// ui edits
progressLayer.strokeColor = UIColor.black.cgColor
progressLayer.fillColor = UIColor.clear.cgColor
//progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineCap = .round
progressLayer.lineWidth = 20.0
view.layer.addSublayer(progressLayer)
shapeLAyer.path = circularPath.cgPath
shapeLAyer.fillColor = UIColor.clear.cgColor
shapeLAyer.strokeColor = UIColor.red.cgColor
shapeLAyer.lineWidth = 10
shapeLAyer.lineCap = .round
shapeLAyer.strokeEnd = 0
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handle)))
view.layer.addSublayer(shapeLAyer)
}
#objc func handle(){
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.toValue = 1
basicAnimation.duration = 3
shapeLAyer.add(basicAnimation, forKey: "urSoBasic")
}
}
[![enter image description here][1]][1]
You can achieve this type of progress by setting proper from value, to value and with the mode of CABasicAnimation
Here, I take two var one for total number (for total progress) and one for current progress number.
From these two var, I converted this value to between 0-1.
First, in your code UIBezierPath angle is wrong.
Replace this line by
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi/2, endAngle: 3*CGFloat.pi / 2, clockwise: true)
class ViewController: UIViewController {
// Other code
private let totalNumber: CGFloat = 300.0
private var currentProgressNumber: CGFloat = 0
private let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
// viewDidLoad code
func playAnimation(){
if currentProgressNumber > totalNumber || currentProgressNumber < 0 {
return
}
basicAnimation.fromValue = basicAnimation.toValue
basicAnimation.toValue = currentProgressNumber/totalNumber
basicAnimation.isRemovedOnCompletion = false
basicAnimation.duration = 3
basicAnimation.fillMode = CAMediaTimingFillMode.both
shapeLAyer.add(basicAnimation, forKey: "urSoBasic")
}
#IBAction func onIncrement(_ sender: UIButton) {
currentProgressNumber += 15 //<-- Change your increment interval here
playAnimation()
}
#IBAction func onDecrement(_ sender: UIButton) {
currentProgressNumber -= 15 //<-- Change your increment interval here
playAnimation()
}
}
Note: Replace your animation function with playAnimation() and also change the increment-decrement value as per your requirement for testing I used 15.

Swift: Animate object along animating path

I would like to animate little red dot rotating around circle that is expanding in a pulse manner (go from small to big, then start back from small). It seems that little dot keeps rotating around original shape and does not take into account that circle it's expanding... I have this in code:
// MARK: - Properties
private lazy var containerView = UIView()
let littleCircleRadius: CGFloat = 10
private lazy var littleRedDot: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.red.cgColor
let littleDotSize = CGSize(width: 10, height: 10)
layer.frame = CGRect(x: containerView.bounds.center.x - littleDotSize.width / 2,
y: containerView.bounds.center.y - littleCircleRadius - littleDotSize.width/2 ,
width: littleDotSize.width,
height: littleDotSize.height)
return layer
}()
private lazy var littleCircleLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 1.5
layer.lineCap = .round
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
}()
// MARK: - Setup
func setup() {
view.addSubview(containerView)
containerView.frame = CGRect(x: 40, y: 200, width: 300, height: 300)
containerView.backgroundColor = UIColor.gray.withAlphaComponent(0.2)
littleCircleLayer.path = makeArcPath(arcCenter: containerView.bounds.center, radius: 10)
containerView.layer.addSublayer(littleCircleLayer)
containerView.layer.addSublayer(littleRedDot)
}
// MARK: - Animations
func animate() {
CATransaction.begin()
CATransaction.setAnimationDuration(1.5)
animateLittleRedDotRotation()
animateCircleExpanding()
CATransaction.commit()
}
func animateLittleRedDotRotation() {
let anim = CAKeyframeAnimation(keyPath: "position")
anim.duration = 1.5
anim.rotationMode = .rotateAuto
anim.repeatCount = Float.infinity
anim.path = littleCircleLayer.path
littleRedDot.add(anim, forKey: "rotate")
}
func animateCircleExpanding() {
let maxCircle = makeArcPath(arcCenter: containerView.bounds.center, radius: 100)
let circleExpandingAnim = CABasicAnimation(keyPath: "path")
circleExpandingAnim.fromValue = littleCircleLayer.path
circleExpandingAnim.toValue = maxCircle
circleExpandingAnim.repeatCount = Float.infinity
circleExpandingAnim.duration = 1.5
littleCircleLayer.add(circleExpandingAnim, forKey: "pulseCircuitAnimation")
}
This creates following effect:
However I would like to achieve for little dot to be rotating along the expanding circle path (as it animates from small circle to bigger circle), not the original small circle path. Any ideas ?
Using CoreAnimation to animate the position of the red dot based upon the path assumes that the path isn't changing. You could, theoretically, define a spiral path that mirrors the expanding circle. Personally, I'd just use CADisplayLink, a special timer designed optimally for screen refreshes, and retire the CoreAnimation calls entirely. E.g.
func startDisplayLink() {
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
}
#objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let percent = CGFloat(displayLink.timestamp).truncatingRemainder(dividingBy: duration) / duration
let radius = ...
let center = containerView.bounds.center
circleLayer.path = makeArcPath(arcCenter: center, radius: radius)
let angle = percent * .pi * 2
let dotCenter = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)
redDot.path = makeArcPath(arcCenter: dotCenter, radius: 5)
}
That yields:
The full example:
class ViewController: UIViewController {
private let radiusRange: ClosedRange<CGFloat> = 10...100
private let duration: CGFloat = 1.5
private lazy var containerView: UIView = {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
private lazy var redDot: CAShapeLayer = {
let layer = CAShapeLayer()
layer.fillColor = UIColor.red.cgColor
return layer
}()
private lazy var circleLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 1.5
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
}()
private weak var displayLink: CADisplayLink?
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startDisplayLink()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopDisplayLink()
}
}
// MARK: Private utility methods
private extension ViewController {
func setup() {
addContainer()
containerView.layer.addSublayer(circleLayer)
containerView.layer.addSublayer(redDot)
}
func addContainer() {
view.addSubview(containerView)
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.topAnchor.constraint(equalTo: view.topAnchor),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
func makeArcPath(arcCenter: CGPoint, radius: CGFloat) -> CGPath {
UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true).cgPath
}
}
// MARK: - DisplayLink related methods
private extension ViewController {
func startDisplayLink() {
stopDisplayLink() // stop existing display link, if any
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
func stopDisplayLink() {
displayLink?.invalidate()
}
#objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let percent = CGFloat(displayLink.timestamp).truncatingRemainder(dividingBy: duration) / duration
let radius = radiusRange.percent(percent)
let center = containerView.bounds.center
circleLayer.path = makeArcPath(arcCenter: center, radius: radius)
let angle = percent * .pi * 2
let dotCenter = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)
redDot.path = makeArcPath(arcCenter: dotCenter, radius: 5)
}
}
// MARK: - CGRect extension
extension CGRect {
var center: CGPoint { return CGPoint(x: midX, y: midY) }
}
// MARK: - ClosedRange extension
extension ClosedRange where Bound: FloatingPoint {
func percent(_ percent: Bound) -> Bound {
(upperBound - lowerBound) * percent + lowerBound
}
}

Something's wrong with my scheduledTimer on SpriteKit

I'm experimenting a few things with Swift and SpriteKit.
I have 4 images of coins. (coin1,coin2, ...). I want to spawn a random coin at a random position on the screen and let it fade out. I want to repeat this action every 3 seconds. This is the code and it worked fine.
class GameScene: SKScene {
// creating a playable area
let gameArea: CGRect
override init(size: CGSize) {
let maxAspectRatio: CGFloat = 16.0/9.0
let playableWidth = size.height / maxAspectRatio
let gameAreaMargin = (size.width - playableWidth)/2
gameArea = CGRect(x: gameAreaMargin, y: 270, width: playableWidth, height: size.height - 270)
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max-min) + min
}
func spawnAndFadeCoins() -> SKSpriteNode{
let randNum = arc4random()%4 + 1
let coin = SKSpriteNode(imageNamed: "coin\(randNum)")
if randNum == 4 {
coin.zPosition = 10
}
else {
coin.zPosition = 5
}
coin.name = "coin\(randNum)"
let randomX = random(min: gameArea.minX + coin.size.width/2,
max: gameArea.maxX - coin.size.width/2)
let randomY = random(min: gameArea.minY + coin.size.height/2,
max: gameArea.maxY - coin.size.height/2)
coin.position = CGPoint(x: randomX, y: randomY)
let disappear = SKAction.fadeOut(withDuration: 1.0)
coin.run(SKAction.repeatForever(disappear))
self.addChild(coin)
return(coin)
}
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed:"bg")
background.size = self.size
background.position = CGPoint(x:self.frame.size.width/2, y: self.frame.size.height/2)
background.zPosition = 0
self.addChild(background)
var _ = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(GameScene.spawnAndFadeCoins), userInfo: nil, repeats: true)
}
}
However, when I add a label to each coin specifying its name, something weird happens: After every 3 seconds, 3 coins appear simultaneously on the screen. The label is placed on 1 of those 3 coins, and it doesn't even say the coin's name correctly. This is the new code:
class GameScene: SKScene {
// creating a playable area
let gameArea: CGRect
override init(size: CGSize) {
let maxAspectRatio: CGFloat = 16.0/9.0
let playableWidth = size.height / maxAspectRatio
let gameAreaMargin = (size.width - playableWidth)/2
gameArea = CGRect(x: gameAreaMargin, y: 270, width: playableWidth, height: size.height - 270)
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max-min) + min
}
func spawnAndFadeCoins() -> SKSpriteNode{
let randNum = arc4random()%4 + 1
let coin = SKSpriteNode(imageNamed: "coin\(randNum)")
if randNum == 4 {
coin.zPosition = 10
}
else {
coin.zPosition = 5
}
coin.name = "coin\(randNum)"
let randomX = random(min: gameArea.minX + coin.size.width/2,
max: gameArea.maxX - coin.size.width/2)
let randomY = random(min: gameArea.minY + coin.size.height/2,
max: gameArea.maxY - coin.size.height/2)
coin.position = CGPoint(x: randomX, y: randomY)
let disappear = SKAction.fadeOut(withDuration: 1.0)
coin.run(SKAction.repeatForever(disappear))
self.addChild(coin)
return(coin)
}
func spawnAndFadeLabels() -> SKLabelNode{
let label = SKLabelNode()
label.text = "\(spawnAndFadeCoins().name)"
label.zPosition = 15
label.color = SKColor.white()
label.fontSize = 60
label.position = spawnAndFadeCoins().position
let disappear2 = SKAction.fadeOut(withDuration: 1.0)
label.run(SKAction.repeatForever(disappear2))
self.addChild(label)
return(label)
}
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed:"bg")
background.size = self.size
background.position = CGPoint(x:self.frame.size.width/2, y: self.frame.size.height/2)
background.zPosition = 0
self.addChild(background)
var _ = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(GameScene.spawnAndFadeCoins), userInfo: nil, repeats: true)
var _ = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(GameScene.spawnAndFadeLabels), userInfo: nil, repeats: true)
}
}
Can you help me solve this mystery? Thanks in advance.
The problem is that you called spawnAndFadeCoins two more times in spawnAndFadeLabels:
func spawnAndFadeLabels() -> SKLabelNode{
let label = SKLabelNode()
label.text = "\(spawnAndFadeCoins().name)" <-- here!
label.zPosition = 15
label.color = SKColor.white()
label.fontSize = 60
label.position = spawnAndFadeCoins().position <-- and here!
let disappear2 = SKAction.fadeOut(withDuration: 1.0)
label.run(SKAction.repeatForever(disappear2))
self.addChild(label)
return(label)
}
I think one thing you can do to solve it is this:
Have only one timer:
runAction(SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration: 3), SKAction.run(spawnAndFadeCoins)])))
I used SKActions here because it is not recommended to use Timer in spritekit.
Now only spawnAndFadeCoins will be called once every three seconds. Then, change spawnAndFadeLabels to:
func spawnAndFadeLabels(of node: SKSpriteNode) -> SKLabelNode{
let label = SKLabelNode()
label.text = "\(node.name)"
label.zPosition = 15
label.color = SKColor.white()
label.fontSize = 60
label.position = node.position
let disappear2 = SKAction.fadeOut(withDuration: 1.0)
label.run(SKAction.repeatForever(disappear2))
self.addChild(label)
return(label)
}
Change spawnAndFadeCoins to:
func spawnAndFadeCoins() -> SKSpriteNode{
let randNum = arc4random()%4 + 1
let coin = SKSpriteNode(imageNamed: "coin\(randNum)")
if randNum == 4 {
coin.zPosition = 10
}
else {
coin.zPosition = 5
}
coin.name = "coin\(randNum)"
let randomX = random(min: gameArea.minX + coin.size.width/2,
max: gameArea.maxX - coin.size.width/2)
let randomY = random(min: gameArea.minY + coin.size.height/2,
max: gameArea.maxY - coin.size.height/2)
coin.position = CGPoint(x: randomX, y: randomY)
let disappear = SKAction.fadeOut(withDuration: 1.0)
coin.run(SKAction.repeatForever(disappear))
self.addChild(coin)
spawnAndFadeLabels(of: coin) <-- This line is added!
return(coin)
}
I think this is want you want.

Why doesn't UIView.animateWithDuration affect this custom view?

I designed a custom header view that masks an image and draws a border on the bottom edge, which is an arc. It looks like this:
Here's the code for the class:
class HeaderView: UIView
{
private let imageView = UIImageView()
private let dimmerView = UIView()
private let arcShape = CAShapeLayer()
private let maskShape = CAShapeLayer() // Masks the image and the dimmer
private let titleLabel = UILabel()
#IBInspectable var image: UIImage? { didSet { self.imageView.image = self.image } }
#IBInspectable var title: String? { didSet {self.titleLabel.text = self.title} }
#IBInspectable var arcHeight: CGFloat? { didSet {self.setupLayers()} }
// MARK: Initialization
override init(frame: CGRect)
{
super.init(frame:frame)
initMyStuff()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder:aDecoder)
initMyStuff()
}
override func prepareForInterfaceBuilder()
{
backgroundColor = UIColor.clear()
}
internal func initMyStuff()
{
backgroundColor = UIColor.clear()
titleLabel.font = Font.AvenirNext_Bold(24)
titleLabel.text = "TITLE"
titleLabel.textColor = UIColor.white()
titleLabel.layer.shadowColor = UIColor.black().cgColor
titleLabel.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
titleLabel.layer.shadowRadius = 0.0;
titleLabel.layer.shadowOpacity = 1.0;
titleLabel.layer.masksToBounds = false
titleLabel.layer.shouldRasterize = true
imageView.contentMode = UIViewContentMode.scaleAspectFill
addSubview(imageView)
dimmerView.frame = self.bounds
dimmerView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
addSubview(dimmerView)
addSubview(titleLabel)
// Add the shapes
self.layer.addSublayer(arcShape)
self.layer.addSublayer(maskShape)
self.layer.masksToBounds = true // This seems to be unneeded...test more
// Set constraints
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView .autoPinEdgesToSuperviewEdges()
titleLabel.autoCenterInSuperview()
}
func setupLayers()
{
let aHeight = arcHeight ?? 10
// Create the arc shape
arcShape.path = AppocalypseUI.createHorizontalArcPath(CGPoint(x: 0, y: bounds.size.height), width: bounds.size.width, arcHeight: aHeight)
arcShape.strokeColor = UIColor.white().cgColor
arcShape.lineWidth = 1.0
arcShape.fillColor = UIColor.clear().cgColor
// Create the mask shape
let maskPath = AppocalypseUI.createHorizontalArcPath(CGPoint(x: 0, y: bounds.size.height), width: bounds.size.width, arcHeight: aHeight, closed: true)
maskPath.moveTo(nil, x: bounds.size.width, y: bounds.size.height)
maskPath.addLineTo(nil, x: bounds.size.width, y: 0)
maskPath.addLineTo(nil, x: 0, y: 0)
maskPath.addLineTo(nil, x: 0, y: bounds.size.height)
//let current = CGPathGetCurrentPoint(maskPath);
//print(current)
let mask_Dimmer = CAShapeLayer()
mask_Dimmer.path = maskPath.copy()
maskShape.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor
maskShape.path = maskPath
// Apply the masks
imageView.layer.mask = maskShape
dimmerView.layer.mask = mask_Dimmer
}
override func layoutSubviews()
{
super.layoutSubviews()
// Let's go old school here...
imageView.frame = self.bounds
dimmerView.frame = self.bounds
setupLayers()
}
}
Something like this will cause it to just snap to the new size without gradually changing its frame:
UIView.animate(withDuration: 1.0)
{
self.headerView.arcHeight = self.new_headerView_arcHeight
self.headerView.frame = self.new_headerView_frame
}
I figure it must have something to do with the fact that I'm using CALayers, but I don't really know enough about what's going on behind the scenes.
EDIT:
Here's the function I use to create the arc path:
class func createHorizontalArcPath(_ startPoint:CGPoint, width:CGFloat, arcHeight:CGFloat, closed:Bool = false) -> CGMutablePath
{
// http://www.raywenderlich.com/33193/core-graphics-tutorial-arcs-and-paths
let arcRect = CGRect(x: startPoint.x, y: startPoint.y-arcHeight, width: width, height: arcHeight)
let arcRadius = (arcRect.size.height/2) + (pow(arcRect.size.width, 2) / (8*arcRect.size.height));
let arcCenter = CGPoint(x: arcRect.origin.x + arcRect.size.width/2, y: arcRect.origin.y + arcRadius);
let angle = acos(arcRect.size.width / (2*arcRadius));
let startAngle = CGFloat(M_PI)+angle // (180 degrees + angle)
let endAngle = CGFloat(M_PI*2)-angle // (360 degrees - angle)
// let startAngle = radians(180) + angle;
// let endAngle = radians(360) - angle;
let path = CGMutablePath();
path.addArc(nil, x: arcCenter.x, y: arcCenter.y, radius: arcRadius, startAngle: startAngle, endAngle: endAngle, clockwise: false);
if(closed == true)
{path.addLineTo(nil, x: startPoint.x, y: startPoint.y);}
return path;
}
BONUS:
Setting the arcHeight property to 0 results in no white line being drawn. Why?
The Path property can't be animated. You have to approach the problem differently. You can draw an arc 'instantly', any arc, so that tells us that we need to handle the animation manually. If you expect the entire draw process to take say 3 seconds, then you might want to split the process to 1000 parts, and call the arc drawing function 1000 times every 0.3 miliseconds to draw the arc again from the beginning to the current point.
self.headerView.arcHeight is not a animatable property. It is only UIView own properties are animatable
you can do something like this
let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode
let expectedFramesPerSecond = 60
var diff : CGFloat = 0
func update() {
let diffUpdated = self.headerView.arcHeight - self.new_headerView_arcHeight
let done = (fabs(diffUpdated) < 0.1)
if(!done){
self.headerView.arcHeight -= diffUpdated/(expectedFramesPerSecond*0.5)
self.setNeedsDisplay()
}
}

remove SKSpriteNode after 5 seconds

How can i remove my SKSpriteNode after 5 seconds in my function like this. I have tried with a NSTimer called a func delete my BonusSprite
but after 5 sec my application crash :
let timerApparitionBonus = NSTimer.scheduledTimerWithTimeInterval(13, target: self, selector: Selector("ApparitionBonus"), userInfo: nil, repeats: true)
}
func ApparitionBonus() {
var BonusApparitionX = UInt32(self.frame.size.width)
var BonusApparitionY = UInt32(self.frame.size.height)
BonusApparitionX = arc4random() % BonusApparitionX
BonusApparitionY = arc4random() % BonusApparitionY
BonusSprite.position = CGPointMake(CGFloat(BonusApparitionX),CGFloat(BonusApparitionY))
BonusSprite.setScale(0.8)
self.addChild(BonusSprite)
}
EDIT :
This is my code from DidMoveToView to function who delete my sprite.
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
//BackGround
self.scene?.backgroundColor = UIColor.blackColor()
self.addChild(SKEmitterNode(fileNamed: "MyParticle")!)
self.scene?.size = CGSize(width: 640, height: 1136)
//Placement du Vaisseau :
Vaisseau.setScale(2)
Vaisseau.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
Vaisseau.physicsBody = SKPhysicsBody(rectangleOfSize: Vaisseau.size)
Vaisseau.physicsBody?.affectedByGravity = false
Vaisseau.physicsBody?.categoryBitMask = PhysicsCategories.Vaisseau
Vaisseau.physicsBody?.contactTestBitMask = PhysicsCategories.Meteorites
Vaisseau.physicsBody?.contactTestBitMask = PhysicsCategories.Bonus
Vaisseau.physicsBody?.dynamic = false
self.addChild(Vaisseau)
//Timer créer enemis
CreationEnemisTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("CreationMeteorites"), userInfo: nil, repeats: true)
//Score
timerScore = NSTimer.scheduledTimerWithTimeInterval(0.7, target: self, selector: Selector("ScoreUpper"), userInfo: nil, repeats: true)
ScoreLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.size.width / 3, height: 20))
ScoreLabel.center = CGPoint(x : self.frame.size.width / 2,y : self.frame.size.height / 4)
ScoreLabel.text = "Score : \(Score)"
ScoreLabel.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 0.3)
ScoreLabel.textColor = UIColor.whiteColor()
self.view?.addSubview(ScoreLabel)
//Aparition des Bonus (timer)
let myFunction = SKAction.runBlock({self.ApparitionBonus()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({self.removeBonus()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func ApparitionBonus() {
var BonusApparitionX = UInt32(self.frame.size.width)
var BonusApparitionY = UInt32(self.frame.size.height)
BonusApparitionX = arc4random() % BonusApparitionX
BonusApparitionY = arc4random() % BonusApparitionY
BonusSprite.position = CGPointMake(CGFloat(BonusApparitionX),CGFloat(BonusApparitionY))
BonusSprite.setScale(0.8)
BonusSprite.physicsBody?.categoryBitMask = PhysicsCategories.Bonus
BonusSprite.physicsBody?.contactTestBitMask = PhysicsCategories.Vaisseau
}
func removeBonus() {
BonusSprite.removeFromParent()
}
try this
EDIT: realized it wouldn't remove the sprite, this will work.
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let myFunction = SKAction.runBlock({()in self.ApparitionBonus()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeSprite()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func ApparitionBonus() {
self.addChild(bonusSprite)
}
func removeSprite() {
bonusSprite.removeFromParent()
}
}
// If we create a SKSpriteNode by a function look like that!
func gooseWarnningLabelShow () {
var spriteNodelabel = SKSpriteNode(imageNamed: "labelBack2")
spriteNodelabel.position = CGPoint(x:CGRectGetMidX(frame), y: frame.size.height*0.90)
spriteNodelabel.size = CGSizeMake(frame.size.width*0.70, frame.size.height*0.06)
spriteNodelabel.alpha = 0.2
spriteNodelabel.zPosition = 21.0
self.addChild(spriteNodelabel)
spriteNodelabel.runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.removeFromParent()
])
)
}
Use this function to create a SKSpriteNode! In that function we have a SKAction which is execute after 5 second delay and remove the SKSpriteNode!