SpriteKit bad FPS performance with really simple scene - performance

I'm new to SpriteKit and I'm trying to simulate a population of 1000 moving individuals each being represented by a simple circle. I have an awful frame rate (around 6/7 fps) so I reduced to 100 and reached 35 fps...
My nodes are the simplest in the world, I'm using the entity component system from GameplayKit, but nothing fancy.
So what can I do to improve and reach 60 fps?
First my shape entity, later in the process the color and filling will change for some individuals, but here there's only a simple circle node:
class ShapeComponent : GKComponent {
let node = SKShapeNode(ellipseOf: CGSize(width: 10, height: 10))
override func didAddToEntity() {
node.entity = entity
}
override func willRemoveFromEntity() {
node.entity = nil
}
}
Then my movement component, having just a speed and an angle. I have even stored sine and cosine to limit computation:
class MovementComponent : GKComponent {
var speed : CGFloat = 25
var direction : CGFloat = .random(in: 0...(2 * .pi)) {
didSet { updateSinCos() }
}
var sinDir : CGFloat = 0
var cosDir : CGFloat = 0
override init() {
super.init()
updateSinCos()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shapeComponent: ShapeComponent {
guard let shapeComponent = entity?.component(ofType: ShapeComponent.self)
else { fatalError("A MovementComponent's entity must have a ShapeComponent") }
return shapeComponent
}
func updateSinCos() {
sinDir = sin(direction)
cosDir = cos(direction)
}
override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)
// Declare local versions of computed properties so we don't compute them multiple times.
let delta = speed * CGFloat(deltaTime)
let vector = CGVector(dx: delta * sinDir, dy: delta * cosDir)
let node = shapeComponent.node
let currentPosition = node.position
node.position = CGPoint(x: currentPosition.x + vector.dx, y: currentPosition.y + vector.dy)
}
}
And at last my entity, with movement and shape components:
class IndividualEntity : GKEntity {
var shapeComponent: ShapeComponent {
guard let shapeComponent = component(ofType: ShapeComponent.self)
else { fatalError("A IndividualEntity must have a ShapeComponent") }
return shapeComponent
}
var movementComponent: MovementComponent {
guard let movementComponent = component(ofType: MovementComponent.self)
else { fatalError("A IndividualEntity must have a MovementComponent") }
return movementComponent
}
override init() {
super.init()
addComponent(ShapeComponent())
addComponent(MovementComponent())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In my scene:
func setUpScene() {
for _ in 1...100 {
let position = CGPoint(x: .random(in: 0..<size.width) - 0.5*size.width, y: .random(in: 0..<size.height) - 0.5*size.height)
addIndividual(at: position)
}
}
var componentSystem = GKComponentSystem(componentClass: MovementComponent.self)
var individuals = [IndividualEntity]()
func addIndividual(at pos: CGPoint) {
let individual = IndividualEntity()
individual.shapeComponent.node.position = pos
addChild(individual.shapeComponent.node)
componentSystem.addComponent(foundIn: individual)
individuals.append(individual)
}
var lastUpdateTimeInterval: TimeInterval = 0
let maximumUpdateDeltaTime: TimeInterval = 1.0 / 60.0
override func update(_ currentTime: TimeInterval) {
guard view != nil else { return }
var deltaTime = currentTime - lastUpdateTimeInterval
deltaTime = deltaTime > maximumUpdateDeltaTime ? maximumUpdateDeltaTime : deltaTime
lastUpdateTimeInterval = currentTime
componentSystem.update(deltaTime: deltaTime)
}
So if someone can help me with this problem... I just want those circles to wander, and later respond to collision by changing direction and color.
Thanks in advance.

Related

SwiftUI DragGesture list content Leaks memory

I am trying to implement Uber trip like screen, and I have used the code below
public struct OverlappingPanView<BGView, PanContentView>: View
where BGView: View, PanContentView: View {
public var backgroundView: () -> BGView
public var panContentView: () -> PanContentView
public init(backgroundView: #escaping () -> BGView, panContentView: #escaping () -> PanContentView) {
self.backgroundView = backgroundView
self.panContentView = panContentView
}
enum OffsetError : Error{
case TopBoundExceded
case BottomBoundExceded
}
//MARK: - State Property Variables
#GestureState private var dragOffset: CGFloat = 0
#State private var lastEndedOffset: CGFloat = 0
#State private var panFrame: CGSize = .zero
//MARK: - Getters
var staticTopPadding: CGFloat{
let screenPadding = UIScreen.main.bounds.height * 0.4
let actualFrame = (panFrame.height - screenPadding)
let extraCorrection = actualFrame * 0.5
return screenPadding + extraCorrection
}
private var currentPaddingValue: CGFloat{
return dragOffset + lastEndedOffset + staticTopPadding
}
private var difference: CGFloat{
return 200
}
//MARK: - Body
public var body: some View {
ZStack {
backgroundView()
.edgesIgnoringSafeArea(.all)
panControllerView()
}
}
func panControllerView() -> some View{
panContentView()
.background(
GeometryReader { geo -> Color in
DispatchQueue.main.async {
self.panFrame = geo.size
}
return Color.clear
}
)
.frame(minHeight: 0, maxHeight: .infinity)
.offset(y: dragOffset + lastEndedOffset + staticTopPadding)
.animation(.interactiveSpring())
.highPriorityGesture(
DragGesture(coordinateSpace: CoordinateSpace.global)
.updating($dragOffset) { (value, state, _) in
if let endValue = try? self.canDrag(value.translation.height){
state = endValue
}
}
.onEnded { value in
self.onDragEnd(value.translation.height)
}
)
}
func onDragEnd(_ value: CGFloat){
withAnimation {
do{
let endValue = try self.canDrag(value)
self.lastEndedOffset = lastEndedOffset + endValue
}catch OffsetError.BottomBoundExceded{
self.lastEndedOffset = 0
}catch OffsetError.TopBoundExceded{
self.lastEndedOffset = -(self.panFrame.height - difference)
}catch{
self.lastEndedOffset = 0
}
}
}
func canDrag(_ value: CGFloat) throws -> CGFloat{
let newValue = value + lastEndedOffset + staticTopPadding
// stop going below
guard newValue <= staticTopPadding else{
throw OffsetError.BottomBoundExceded
}
let topCalculation = (self.panFrame.height + (value + self.lastEndedOffset)) - difference
// stop going top
guard topCalculation >= 0 else{
throw OffsetError.TopBoundExceded
}
return value
}
}
and Used the above code to show mapview in the background and ForEach content on the front.
OverlappingPanView {
GoogleMapView(delegate: presenter)
} panContentView: {
contentView() // ForEach looped data Views
}
The list that I have provided in content View is leaking memory and uses high CPU (above 100%) when dragging leading to UI Performance issues.

Animating the letters in a word w the app opens - SwiftUI

I'm trying to get a dancing letters effect when my app first opens.
I'm close. The coding below almost does what I want. I use a ForEach loop to loop through the letters of the word and apply an animation to each letter. And I use the onAppear function to set the drag amount when the app opens.
With this coding I can get the 'forward' motion but I can't get the animation to reverse so that the letters end up in their original position. I've tried adding a repeat with reverse, but, again, the letters never return to their original position
Does anyone have any idea how to do this?
struct ContentView: View {
let letters = Array("Math Fun!")
#State private var enabled = false
#State private var dragAmount = CGSize.zero
var body: some View {
HStack(spacing: 0) {
ForEach(0..<letters.count) { num in
Text(String(self.letters[num]))
.padding(5)
.font(.title)
.background(self.enabled ? Color.blue : Color.red)
.offset(self.dragAmount)
.animation(Animation.default.delay(Double(num)/20).repeatCount(3, autoreverses: true))
}
}
.onAppear {
self.dragAmount = CGSize(width: 0, height: 80)
self.enabled.toggle()
}
}
}
Update: with Xcode 13.4 / iOS 15.5
Animation is based on changed states, we switched states and view animated to the new states, so to rollback we need to switch the states back.
Here is the possible approach (might still require tuning, but is ok for demo)
struct ContentView: View {
let letters = Array("Math Fun!")
#State private var enabled = false
#State private var dragAmount = CGSize.zero
var body: some View {
HStack(spacing: 0) {
ForEach(0..<letters.count, id: \.self) { num in
Text(String(self.letters[num]))
.padding(5)
.font(.title)
.background(self.enabled ? Color.blue : Color.red)
.offset(self.dragAmount)
.animation(Animation.default.delay(Double(num)/20), value: enabled)
}
}
.onAppear {
self.dragAmount = CGSize(width: 0, height: 80)
self.enabled.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.dragAmount = .zero
self.enabled.toggle()
}
}
}
}
You can use AnimatableModifier to achieve this effect. here is a sample code:
extension Double {
var rad: Double { return self * .pi / 180 }
var deg: Double { return self * 180 / .pi }
}
struct ContentView: View {
#State private var flag = false
var body: some View {
VStack {
Spacer()
Color.clear.overlay(WaveText("Your Text That Need Animate", waveWidth: 6, pct: flag ? 1.0 : 0.0).foregroundColor(.blue)).frame(height: 40)
Spacer()
}.onAppear {
withAnimation(Animation.easeInOut(duration: 2.0).repeatForever()) {
self.flag.toggle()
}
}
}
}
struct WaveText: View {
let text: String
let pct: Double
let waveWidth: Int
var size: CGFloat
init(_ text: String, waveWidth: Int, pct: Double, size: CGFloat = 34) {
self.text = text
self.waveWidth = waveWidth
self.pct = pct
self.size = size
}
var body: some View {
Text(text).foregroundColor(Color.clear).modifier(WaveTextModifier(text: text, waveWidth: waveWidth, pct: pct, size: size))
}
struct WaveTextModifier: AnimatableModifier {
let text: String
let waveWidth: Int
var pct: Double
var size: CGFloat
var animatableData: Double {
get { pct }
set { pct = newValue }
}
func body(content: Content) -> some View {
HStack(spacing: 0) {
ForEach(Array(text.enumerated()), id: \.0) { (n, ch) in
Text(String(ch))
.scaleEffect(self.effect(self.pct, n, self.text.count, Double(self.waveWidth)))
}
}
}
func effect(_ pct: Double, _ n: Int, _ total: Int, _ waveWidth: Double) -> CGFloat {
let n = Double(n)
let total = Double(total)
return CGFloat(1 + valueInCurve(pct: pct, total: total, x: n/total, waveWidth: waveWidth))
}
func valueInCurve(pct: Double, total: Double, x: Double, waveWidth: Double) -> Double {
let chunk = waveWidth / total
let m = 1 / chunk
let offset = (chunk - (1 / total)) * pct
let lowerLimit = (pct - chunk) + offset
let upperLimit = (pct) + offset
guard x >= lowerLimit && x < upperLimit else { return 0 }
let angle = ((x - pct - offset) * m)*360-90
return (sin(angle.rad) + 1) / 2
}
}
}
You can find refrence and compelete answer here

Not able to Rotate my 3D objects

I had created one project using ARKit and SceneKit framework. In which I am working with file extension .dae, the files are locally available in my project as shown in below screenshot.
Here I had applied many gestures on this virtual object such as Tap Gesture(When I tap on camera screen, it places the virtual object there), same way Pinch Gesture and Pan Gesture. All of these gestures are working perfectly fine. Now I wanted to apply rotation gesture, for which I got stuck how to do that, also I am not getting any such available sources to achieve this.
Below is my working code so far,
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
private var movedObject: SCNNode?
private var hud :MBProgressHUD!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.autoenablesDefaultLighting = true
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
registerGestureRecognizers()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
private func registerGestureRecognizers() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(recognizer:)))
tapGestureRecognizer.numberOfTapsRequired = 1
self.sceneView.addGestureRecognizer(tapGestureRecognizer)
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinched(recognizer:)))
self.sceneView.addGestureRecognizer(pinchGestureRecognizer)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveObject(recognizer:)))
panGestureRecognizer.maximumNumberOfTouches = 1
panGestureRecognizer.minimumNumberOfTouches = 1
self.sceneView.addGestureRecognizer(panGestureRecognizer)
let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotateObject(recognizer:)))
self.sceneView.addGestureRecognizer(rotationGestureRecognizer)
}
#objc func pinched(recognizer :UIPinchGestureRecognizer) {
if recognizer.state == .changed {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = self.sceneView.hitTest(touch, options: nil)
if let hitTest = hitTestResults.first {
let chairNode = hitTest.node
let pinchScaleX = Float(recognizer.scale) * chairNode.scale.x
let pinchScaleY = Float(recognizer.scale) * chairNode.scale.y
let pinchScaleZ = Float(recognizer.scale) * chairNode.scale.z
chairNode.scale = SCNVector3(pinchScaleX,pinchScaleY,pinchScaleZ)
recognizer.scale = 1
}
}
}
#objc func moveObject(recognizer: UIPanGestureRecognizer) {
print("Move object")
if recognizer.state == .began {
print("Pan state began")
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let result = sceneView.hitTest(tapPoint ?? CGPoint.zero, options: nil)
if result.count == 0 {
return
}
let hitResult: SCNHitTestResult? = result.first
if (hitResult?.node.name == "free_car_1") {
movedObject = hitResult?.node
} else if (hitResult?.node.parent?.name == "free_car_1") {
movedObject = hitResult?.node.parent
}
if (movedObject != nil) {
print("Holding an Object")
}
}
if recognizer.state == .changed {
print("Pan State Changed")
if (movedObject != nil) {
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(tapPoint ?? CGPoint.zero, types: .featurePoint)
let result: ARHitTestResult? = hitResults.last
let matrix: SCNMatrix4 = SCNMatrix4((result?.worldTransform)!)
//SCNMatrix4FromMat4((result?.worldTransform)!)
let vector: SCNVector3 = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
movedObject?.position = vector
print("Moving object position")
}
}
if recognizer.state == .ended {
print("Done moving object homeie")
movedObject = nil
}
}
#objc func tapped(recognizer :UITapGestureRecognizer) {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = sceneView.hitTest(touch)
guard let hitTest = hitTestResults.first?.node else {
let hitTestResultsWithExistingPlane = sceneView.hitTest(touch, types: .existingPlane)
let chairScene = SCNScene(named: "ShelbyWD.dae")!
guard let chairNode = chairScene.rootNode.childNode(withName: "ShelbyWD", recursively: true) else {
return
}
if let hitTestAvailable = hitTestResultsWithExistingPlane.first {
chairNode.position = SCNVector3(hitTestAvailable.worldTransform.columns.3.x,hitTestAvailable.worldTransform.columns.3.y,hitTestAvailable.worldTransform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(chairNode)
return
}
return
}
hitTest.removeFromParentNode()
}
#objc func rotateObject(recognizer :UIRotationGestureRecognizer)
{
}
}
Can anyone help me out to apply rotation gesture on my object?
Thank you!
In order to rotate an SCNNode, the 1st thing you need to do, is create a variable to store the rotationAngle around the YAxis or any other that you wish to perform the rotation on e.g:
var currentAngleY: Float = 0.0
Then have some way to have detected to node you wish to rotate, which in my example I am calling currentNode e.g.
var currentNode: SCNNode!
In my example I will just rotate around the YAxis.
You can use a UIPanGestureRecognizer like so:
/// Rotates An Object On It's YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateObject(_ gesture: UIPanGestureRecognizer) {
guard let nodeToRotate = currentNode else { return }
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
nodeToRotate.eulerAngles.y = newAngleY
if(gesture.state == .ended) { currentAngleY = newAngleY }
print(nodeToRotate.eulerAngles)
}
Or if you wish to use a UIRotationGesture you can do something like this:
/// Rotates An SCNNode Around It's YAxis
///
/// - Parameter gesture: UIRotationGestureRecognizer
#objc func rotateNode(_ gesture: UIRotationGestureRecognizer){
//1. Get The Current Rotation From The Gesture
let rotation = Float(gesture.rotation)
//2. If The Gesture State Has Changed Set The Nodes EulerAngles.y
if gesture.state == .changed{
isRotating = true
currentNode.eulerAngles.y = currentAngleY + rotation
}
//3. If The Gesture Has Ended Store The Last Angle Of The Cube
if(gesture.state == .ended) {
currentAngleY = currentNode.eulerAngles.y
isRotating = false
}
}
Hope it helps...

Drag views in NSStackView to rearrange the sequence

Is it possible to change the order of views in NSStackView by dragging the subviews, just like we do it in NSTableView ?
Here's an implementation of an NSStackView subclass whose contents can be reordered via dragging:
//
// DraggingStackView.swift
// Analysis
//
// Created by Mark Onyschuk on 2017-02-02.
// Copyright © 2017 Mark Onyschuk. All rights reserved.
//
import Cocoa
class DraggingStackView: NSStackView {
var isEnabled = true
// MARK: -
// MARK: Update Function
var update: (NSStackView, Array<NSView>)->Void = { stack, views in
stack.views.forEach {
stack.removeView($0)
}
views.forEach {
stack.addView($0, in: .leading)
switch stack.orientation {
case .horizontal:
$0.topAnchor.constraint(equalTo: stack.topAnchor).isActive = true
$0.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true
case .vertical:
$0.leadingAnchor.constraint(equalTo: stack.leadingAnchor).isActive = true
$0.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true
}
}
}
// MARK: -
// MARK: Event Handling
override func mouseDragged(with event: NSEvent) {
if isEnabled {
let location = convert(event.locationInWindow, from: nil)
if let dragged = views.first(where: { $0.hitTest(location) != nil }) {
reorder(view: dragged, event: event)
}
} else {
super.mouseDragged(with: event)
}
}
private func reorder(view: NSView, event: NSEvent) {
guard let layer = self.layer else { return }
guard let cached = try? self.cacheViews() else { return }
let container = CALayer()
container.frame = layer.bounds
container.zPosition = 1
container.backgroundColor = NSColor.underPageBackgroundColor.cgColor
cached
.filter { $0.view !== view }
.forEach { container.addSublayer($0) }
layer.addSublayer(container)
defer { container.removeFromSuperlayer() }
let dragged = cached.first(where: { $0.view === view })!
dragged.zPosition = 2
layer.addSublayer(dragged)
defer { dragged.removeFromSuperlayer() }
let d0 = view.frame.origin
let p0 = convert(event.locationInWindow, from: nil)
window!.trackEvents(matching: [.leftMouseDragged, .leftMouseUp], timeout: 1e6, mode: .eventTrackingRunLoopMode) { event, stop in
if event.type == .leftMouseDragged {
let p1 = self.convert(event.locationInWindow, from: nil)
let dx = (self.orientation == .horizontal) ? p1.x - p0.x : 0
let dy = (self.orientation == .vertical) ? p1.y - p0.y : 0
CATransaction.begin()
CATransaction.setDisableActions(true)
dragged.frame.origin.x = d0.x + dx
dragged.frame.origin.y = d0.y + dy
CATransaction.commit()
let reordered = self.views.map {
(view: $0,
position: $0 !== view
? NSPoint(x: $0.frame.midX, y: $0.frame.midY)
: NSPoint(x: dragged.frame.midX, y: dragged.frame.midY))
}
.sorted {
switch self.orientation {
case .vertical: return $0.position.y < $1.position.y
case .horizontal: return $0.position.x < $1.position.x
}
}
.map { $0.view }
let nextIndex = reordered.index(of: view)!
let prevIndex = self.views.index(of: view)!
if nextIndex != prevIndex {
self.update(self, reordered)
self.layoutSubtreeIfNeeded()
CATransaction.begin()
CATransaction.setAnimationDuration(0.15)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
for layer in cached {
layer.position = NSPoint(x: layer.view.frame.midX, y: layer.view.frame.midY)
}
CATransaction.commit()
}
} else {
view.mouseUp(with: event)
stop.pointee = true
}
}
}
// MARK: -
// MARK: View Caching
private class CachedViewLayer: CALayer {
let view: NSView!
enum CacheError: Error {
case bitmapCreationFailed
}
override init(layer: Any) {
self.view = (layer as! CachedViewLayer).view
super.init(layer: layer)
}
init(view: NSView) throws {
self.view = view
super.init()
guard let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { throw CacheError.bitmapCreationFailed }
view.cacheDisplay(in: view.bounds, to: bitmap)
frame = view.frame
contents = bitmap.cgImage
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private func cacheViews() throws -> [CachedViewLayer] {
return try views.map { try cacheView(view: $0) }
}
private func cacheView(view: NSView) throws -> CachedViewLayer {
return try CachedViewLayer(view: view)
}
}
The code requires your stack to be layer backed, and uses sublayers to simulate and animate its content views during drag handling. Dragging is detected by an override of mouseDragged(with:) so will not be initiated if the stack's contents consume this event.
There's no built-in support for re-ordering NSStackView subviews.

CALayer 'loses' values set from outside class during implicit animation

I have a CLLayer with an implicit animation on a custom property in Swift.
From inside the class I have access to the interpolated values of my property during the animation. But when I use other properties that I set from outside the class, they don't have a value during the animation. When the animation has ended there original value is available again.
So in the following example people has five random values which are printed to the console in drawInContext, but when the animation is running people is empty
This is my custom CALayer
class BackgroundLayer: CALayer {
#NSManaged var range: Double
var people: []
override class func needsDisplayForKey(key: String) -> Bool {
if key == "range" {
return true
}
return super.needsDisplayForKey(key)
}
override func drawInContext(ctx: CGContext!) {
let center = CGPoint(x: Double(bounds.width)/2, y: Double(bounds.height)-bottomOffset)
let xDist = Double(bounds.width)
let yDist = Double(bounds.height)-bottomOffset-minRadius
let maxDistance = sqrt(pow(xDist,2) + pow(yDist,2))
let radius = CGFloat(minRadius + range * maxDistance)
CGContextSetFillColorWithColor(ctx, UIColor(red: 208/255, green: 2/255, blue: 27/255, alpha: 1).CGColor)
CGContextFillEllipseInRect(ctx, CGRectMake(center.x-radius, center.y-radius, radius*2, radius*2))
println("are there people? \(people)")
}
override func actionForKey(event: String!) -> CAAction! {
if event == "range" {
var currentValue = self.presentationLayer().valueForKey(event) as Double
if currentValue == 0 {
currentValue = self.range
}
var rangeAnim = CABasicAnimation(keyPath: event)
rangeAnim?.fromValue = currentValue
rangeAnim?.duration = 0.5
return rangeAnim
}
return super.actionForKey(event)
}
}
And this is how I use it in my UIViewController that sets the five random values
class ViewController: UIViewController {
private let backgroundLayer = BackgroundLayer()
override func viewDidLoad() {
super.viewDidLoad()
for index in 0..<5 {
println()
let point = (x: Double(arc4random_uniform(UInt32(view.frame.width))), y: Double(arc4random_uniform(UInt32(view.frame.height))))
backgroundLayer.people.append(point)
}
println(backgroundLayer.people)
}
override func viewWillLayoutSubviews() {
backgroundLayer.contentsScale = UIScreen.mainScreen().scale
backgroundLayer.frame = self.view.frame
self.view.layer.addSublayer(backgroundLayer)
backgroundLayer.setNeedsDisplay()
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
backgroundLayer.range = 1
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
backgroundLayer.range = 0
}
}

Resources