What is wrong with this "Do.. While" loop in Swift? - memory

I am trying to write this in Swift (I am in step 54). In a UICollectionViewLayout class I have a function setup function
func setup() {
var percentage = 0.0
for i in 0...RotationCount - 1 {
var newPercentage = 0.0
do {
newPercentage = Double((arc4random() % 220) - 110) * 0.0001
println(newPercentage)
} while (fabs(percentage - newPercentage) < 0.006)
percentage = newPercentage
var angle = 2 * M_PI * (1 + percentage)
var transform = CATransform3DMakeRotation(CGFloat(angle), 0, 0, 1)
rotations.append(transform)
}
}
Here is how the setup function is described in the tutorial
First we create a temporary mutable array that we add objects to. Then
we run through our loop, creating a rotation each time. We create a
random percentage between -1.1% and 1.1% and then use that to create a
tweaked CATransform3D. I geeked out a bit and added some logic to
ensure that the percentage of rotation we randomly generate is a least
0.6% different than the one generated beforehand. This ensures that photos in a stack don't have the misfortune of all being rotated the
same way. Once we have our transform, we add it to the temporary array
by wrapping it in an NSValue and then rinse and repeat. After all 32
rotations are added we set our private array property. Now we just
need to put it to use.
When I run the app, I get a run time error in the while (fabs(percentage - newPercentage) < 0.006) line.
the setup function is called in prepareLayout()
override func prepareLayout() {
super.prepareLayout()
setup()
...
}
Without the do..while loop, the app runs fine. So I am wondering, why?

Turns out I had to be more type safe
newPercentage = Double(Int((arc4random() % 220)) - 110) * 0.0001

This must be a Swift bug. That code should NOT crash at runtime. It should either give a compiler error on the newPercentage = expression or it should correctly promote the types as C does.

Related

Identify slow code to optimize build time

I'm using these Compilation Swift Flag to identify codes that slow down the compilation time:
-Xfrontend -warn-long-function-bodies=100
-Xfrontend -warn-long-expression-type-checking=100
Then after building, I get warnings like these:
Instance method 'startFadePositionTitle()' took 2702ms to type-check (limit: 500ms)
for this part of the code:
func startFadePositionTitle() -> CGFloat {
let value: CGFloat = ((backgroundImage.frame.height/2 - contentTitle.frame.height/2) - navbarView.frame.height)/2
return value
}
Can someone explains me what is wrong in this method and what could I possibly improve?
You should break it to smaller chunks, then Swift can do type check more easily. Also the more you tell, the less Swift has to think. So you can help compiler and tell it anything you already know:
func beginFadePositionTitle() -> CGFloat {
let n: CGFloat = 2
let a: CGFloat = self.backgroundImage.frame.height/n
let b: CGFloat = self.contentTitle.frame.height/n
let ab: CGFloat = a - b
let c: CGFloat = self.navbarView.frame.height
let abc: CGFloat = ab - c
return abc/n
}
Instance method 'beginFadePositionTitle()' took 1ms to type-check (limit: 1ms)
This is the result when you tell everything to compiler. See the difference?
I recommend to try this one
func startFadePositionTitle() -> CGFloat {
return ((backgroundImage.frame.height - contentTitle.frame.height)/2.0 -
navbarView.frame.height)/2.0
}
with my Xcode 11.2 / Catalina (tested assuming that all those frames are CGRects), there is no such warning with those compiler flags. In the corner case it is possible to use CGFloat(2.0) in corresponding places, but I think it is superfluous.
You are calculating value every time when you call this method startFadePositionTitle
You can specify that once and after than you can use freely.
let value: CGFloat = ((backgroundImage.frame.height/2 - contentTitle.frame.height/2) - navbarView.frame.height)/2
Use that in viewDidLoad method
And optimize your code like that
func startFadePositionTitle() -> CGFloat {
return value
}
Floating point division and square root take considerably longer to compute than addition and multiplication. The latter two are computed directly while the former are usually computed with an iterative algorithm. The most common approach is to use a division-free Newton-Raphson iteration to get an approximation to the reciprocal of the denominator (division) or the reciprocal square root, and then multiply by the numerator (division) or input argument (square root).
Source :
Why is float division slow?
So i have made it to one division:
((x - y)/2 - z)/2 = (x-y-2z)/4
i.e. You can just write something like this
(backgroundImage.frame.height - contentTitle.frame.height - 2.0*navbarView.frame.height)/4.0
So small change to function would be something like this
func startFadePositionTitle() -> CGFloat {
return (backgroundImage.frame.height - contentTitle.frame.height - 2.0*navbarView.frame.height)/4.0
}

SpriteKit stop spinning wheel in a defined angle

I have a spinning wheel rotating at an angular speed ω, no acceleration involved, implemented with SpriteKit.
When the user push a button I need to slowly decelerate the wheel from the current angle ∂0 and end-up in a specified angle (lets call it ∂f).
I created associated to it a mass of 2.
I already tried the angularDamping and the SKAction.rotate(toAngle: duration:) but they do not fit my needs because:
With the angularDamping I cannot specify easy the angle ∂f where I want to end up.
With the SKAction.rotate(toAngle: duration:) I cannot start slowing down from the current rotation speed and it doesn't behave natural.
The only remaining approach I tried is by using the SKAction.applyTorque(duration:).
This sounds interesting but I have problems calculating the formula to obtain the correct torque to apply and especially for the inertia and radius of the wheel.
Here is my approach:
I'm taking the starting angular velocity ω as:
wheelNode.physicsBody?.angularVelocity.
I'm taking the mass from wheelNode.physicsBody?.mass
The time t is a constant of 10 (this means that in 10 seconds I want the wheel decelerating to the final angle ∂f).
The deceleration that I calculated as:
let a = -1 * ω / t
The inertia should be: let I = 1/2 * mass * pow(r, 2)*. (see notes regarding the radius please)
Then, finally, I calculated the final torque to apply as: let t = I * a (taking care that is opposite of the current angular speed of the wheel).
NOTE:
Since I don't have clear how to have the radius of the wheel I tried to grab it both from:
the wheelNode.physicsBody?.area as let r = sqrt(wheelNode.physicsBody?.area ?? 0 / .pi)
by converting from pixel to meters as the area documentation says. Then I have let r = self.wheelNode.radius / 150.
Funny: I obtain 2 different values :(
UNFORTUNATLY something in this approach is not working because so far I have no idea how to end up in the specified angle and the wheel doesn't stop anyway as it should (or the torque is too much and spins in the other direction, or is not enough). So, also the torque applied seems to be wrong.
Do you know a better way to achieve the result I need? Is that the correct approach? If yes, what's wrong with my calculations?
Kinematics makes my head hurt, but here you go. I made it to where you can input the amount of rotations and the wheel will rotate that many times as its slowing down to the angle you specify. The other function and extension are there to keep the code relatively clean/readable. So if you just want one giant mess function go ahead and modify it.
• Make sure the node's angularDampening = 0.0
• Make sure the node has a circular physicsbody
// Stops a spinning SpriteNode at a specified angle within a certain amount of rotations
//NOTE: Node must have a circular physicsbody
// Damping should be from 0.0 to 1.0
func decelerate(node: SKSpriteNode, toAngle: CGFloat, rotations: Int) {
if node.physicsBody == nil { print("Node doesn't have a physicsbody"); return } //Avoid crash incase node's physicsbody is nil
var cw:CGFloat { if node.physicsBody!.angularVelocity < CGFloat(0.0) { return -1.0} else { return 1.0} } //Clockwise - using int to reduce if statments with booleans
let m = node.physicsBody!.mass // Mass
let r = CGFloat.squareRoot(node.physicsBody!.area / CGFloat.pi)() // Radius
let i = 0.5 * m * r.squared // Intertia
let wi = node.physicsBody!.angularVelocity // Initial Angular Velocity
let wf:CGFloat = 0 // Final Angular Velocity
let ti = CGFloat.unitCircle(node.zRotation) // Initial Theta
var tf = CGFloat.unitCircle(toAngle) // Final Theta
//Correction constant based on rate of rotation since there seems to be a delay between when the action is calcuated and when it is run
//Without the correction the node stops a little off from its desired stop angle
tf -= 0.00773889 * wi //Might need to change constn
let dt = deltaTheta(ti, tf, Int(cw), rotations)
let a = -cw * 0.5 * wi.squared / abs(dt) // Angular Acceleration - cw used to determine direction
print("A:\(a)")
let time:Double = Double(abs((wf-wi) / a)) // Time needed to stop
let torque:CGFloat = i * a // Torque needed to stop
node.run(SKAction.applyTorque(torque, duration: time))
}
func deltaTheta(_ ti:CGFloat, _ tf:CGFloat, _ clockwise: Int, _ rotations: Int) -> CGFloat {
let extra = CGFloat(rotations)*2*CGFloat.pi
if clockwise == -1 {
if tf>ti { return tf-ti-2*CGFloat.pi-extra }else{ return tf-ti-extra }
}else{
if tf>ti { return tf-ti+extra }else{ return tf+2*CGFloat.pi+extra-ti }
}
}
}
extension CGFloat {
public var squared:CGFloat { return self * self }
public static func unitCircle(_ value: CGFloat) -> CGFloat {
if value < 0 { return 2 * CGFloat.pi + value }
else{ return value }
}
}

SKEmiterNode with AVAudioPlayer for music visuals

PLEASE SOMEONE HELP!
I want to have my SKEmiterNode's scale(meaning size) get larger and smaller to the music i have built into the application using AVAudioPlayer. Right now this is pretty much all I have for the SKEmiterNode and it looks great:
beatParticle?.position = CGPoint(x: self.size.width * 0.5, y: self.size.height * 0.5)
var beatParticleEffectNode = SKEffectNode()
beatParticleEffectNode.addChild(beatParticle!)
self.addChild(beatParticleEffectNode)
All the looks are done in the .sks file.
Here is where I call the "updateBeatParticle" function in a continual loop so that It can where i will put my code for making the particle's scale(meaning size) larger and smaller to the music.
var dpLink : CADisplayLink?
dpLink = CADisplayLink(target: self, selector: "updateBeatParticle")
dpLink?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
func updateBeatParticle(){
//Put code here
}
Any idea how i can do this? I looked at some tutorials such as this: https://www.raywenderlich.com/36475/how-to-make-a-music-visualizer-in-ios
However, i can't quite get my head around it because they're using an emitterLayer and its in Obj-C and am also interested in any other ideas you wonderful people may have!
WARNING: The following code has not been tested. Please let me know if it works.
Firstly, it looks like you are using SpriteKit, therefore you could put the code needed to alter the emitter scale in the SKScene method update:, which automatically gets called virtually as often as a CADisplayLink.
Essentially all you need to do is update the emitter scale in the update: method based on the volume of the channels of your AVAudioPlayer. Note that the audio player may have multiple channels running, so you need to average out the average power for each.
Firstly...
player.meteringEnabled = true
Set this after you initialise your audio player, so that it will monitor the levels of the channels.
Next, add something like this in your update method.
override func update(currentTime: CFTimeInterval) {
var scale: CGFloat = 0.5
if audioPlayer.playing { // Only do this if the audio is actually playing
audioPlayer.updateMeters() // Tell the audio player to update and fetch the latest readings
let channels = audioPlayer.numberOfChannels
var power: Float = 0
// Loop over each channel and add its average power
for i in 0..<channels {
power += audioPlayer.averagePowerForChannel(i)
}
power /= Float(channels) // This will give the average power across all the channels in decibels
// Convert power in decibels to a more appropriate percentage representation
scale = CGFloat(getIntensityFromPower(power))
}
// Set the particle scale to match
emitterNode.particleScale = scale
}
The method getIntensityFromPower is used to convert the power in decibels, to a more appropriate percentage representation. This method can be declared like so...
// Will return a value between 0.0 ... 1.0, based on the decibels
func getIntensityFromPower(decibels: Float) -> Float {
// The minimum possible decibel returned from an AVAudioPlayer channel
let minDecibels: Float = -160
// The maximum possible decibel returned from an AVAudioPlayer channel
let maxDecibels: Float = 0
// Clamp the decibels value
if decibels < minDecibels {
return 0
}
if decibels >= maxDecibels {
return 1
}
// This value can be adjusted to affect the curve of the intensity
let root: Float = 2
let minAmp = powf(10, 0.05 * minDecibels)
let inverseAmpRange: Float = 1.0 / (1.0 - minAmp)
let amp: Float = powf(10, 0.05 * decibels)
let adjAmp = (amp - minAmp) * inverseAmpRange
return powf(adjAmp, 1.0 / root)
}
The algorithm for this conversion was taken from this StackOverflow response https://stackoverflow.com/a/16192481/3222419.

Understanding random numbers in iOS Swift 2

How do I make a random number continue to change over time in the program (I.E. become a new one within the range everytime I want to use it)?
I'm stumped. I've read more than 20 different posts and articles on how to generate random numbers in this language (which I'm pretty new to) and I just can't seem to get it to work.
I'm basically trying to get a random double from 1.0-3.0. I can do this pretty easily, but once it has selected that number it doesn't change. This is my code that I use:
var randomNumber:Double = (Double(arc4random() % 3) + 1);
Then I use this as a value for the line:
SKAction.waitForDuration(randomNumber)
Every time I run this I want to change the number again, but once the program starts it continues that same number (It's different every time i reset the program)
I understand how to generate the number, but I can't seem to find anything on updating it!
I've tried adding
randomNumber = (Double(arc4random() % 3) + 1);
into the code in a spot where it will be ran many times, but it still gives me the same thing.
I'm very familiar with c++ so if you're trying to explain something you can reference its style and I will most likely understand.
What you need it is a read only computed property that will return a new random every time you try to access it:
var randomNumber: Double {
return Double(arc4random_uniform(3).successor())
}
print(randomNumber) // 2.0
print(randomNumber) // 2.0
print(randomNumber) // 1.0
print(randomNumber) // 3.0
print(randomNumber) // 3.0
Use:
SKAction.waitForDuration(sec: NSTimeInterval, withRange: NSTimeInterval)
where sec is the middle of the range in time you want to use, since range goes in a +- direction.
So in your case you want:
SKAction.waitForDuration(2, withRange: 2), this will get you a range of 1 to 3 (-1 to 1 range)
If for some reason you need a method that will constantly create a new random wait, you can always do:
extension SKAction
{
func waitForRandomDuration() -> SKAction
{
var randomNumber:Double = (Double(arc4random() % 3) + 1);
return SKAction.waitForDuration(randomNumber);
}
}
And then make sure that you add this as a new action onto your sprite every time you need to get it done, if you store it into a variable, your randomness won't change.
Try this code:
func randomNumberBetween1_0And3_0() -> Double {
return 1 + Double(arc4random_uniform(2000)) / 1000.0
}
for index in 1...10 {
print(randomNumberBetween1_0And3_0())
}
Sample output is:
2.087
1.367
1.867
1.32
2.402
1.803
1.325
1.703
2.069
2.335

Swift function that takes in array giving error: '#lvalue $T24' is not identical to 'CGFloat'

So I'm writing a lowpass accelerometer function to moderate the jitters of the accelerometer. I have a CGFloat array to represent the data and i want to damp it with this function:
// Damps the gittery motion with a lowpass filter.
func lowPass(vector:[CGFloat]) -> [CGFloat]
{
let blend:CGFloat = 0.2
// Smoothens out the data input.
vector[0] = vector[0] * blend + lastVector[0] * (1 - blend)
vector[1] = vector[1] * blend + lastVector[1] * (1 - blend)
vector[2] = vector[2] * blend + lastVector[2] * (1 - blend)
// Sets the last vector to be the current one.
lastVector = vector
// Returns the lowpass vector.
return vector
}
In this case, lastVector is defined as follows up at the top of my program:
var lastVector:[CGFloat] = [0.0, 0.0, 0.0]
The three lines in the form vector[a] = ... give me the errors. Any ideas as to why i am getting this error?
That code seems to compile if you pass the array with the inout modifier:
func lowPass(inout vector:[CGFloat]) -> [CGFloat] {
...
}
I'm not sure whether that's a bug or not. Instinctively, if I pass an array to a function I expect to be able to modify it. If I pass with the inout modifier, I'd expect to be able to make the original variable to point to a new array - similar to what the & modifier does in C and C++.
Maybe the reason behind is that in Swift there are mutable and immutable arrays (and dictionaries). Without the inout it's considered immutable, hence the reason why it cannot be modified.
Addendum 1 - It's not a bug
#newacct says that's the intended behavior. After some research I agree with him. But even if not a bug I originally considered it wrong (read up to the end for conclusions).
If I have a class like this:
class WithProp {
var x : Int = 1
func SetX(newVal : Int) {
self.x = newVal
}
}
I can pass an instance of that class to a function, and the function can modify its internal state
var a = WithProp()
func Do1(p : WithProp) {
p.x = 5 // This works
p.SetX(10) // This works too
}
without having to pass the instance as inout.
I can use inout instead to make the a variable to point to another instance:
func Do2(inout p : WithProp) {
p = WithProp()
}
Do2(&a)
With that code, from within Do2 I make the p parameter (i.e. the a variable) point to a newly created instance of WithProp.
The same cannot be done with an array (and I presume a dictionary as well). To change its internal state (modify, add or remove an element) the inout modifier must be used. That was counterintuitive.
But everything gets clarified after reading this excerpt from the swift book:
Swift’s String, Array, and Dictionary types are implemented as structures. This means that strings, arrays, and dictionaries are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.
So when passed to a func, it's not the original array, but a copy of it - Hence any change made to it (even if possible) wouldn't be done on the original array.
So, in the end, my original answer above is correct and the experienced behavior is not a bug
Many thanks to #newacct :)
Since Xcode 6 beta 3, modifying the contents of an Array is a mutating operation. You cannot modify a constant (i.e. let) Array; you can only modify a non-constant (i.e. var) Array.
Parameters to a function are constants by default. Therefore, you cannot modify the contents of vector since it is a constant. Like other parameters, there are two ways to be able to change a parameter:
Declare it var, in which case you can assign to it, but it is still passed by value, so any changes to the parameter has no effect on the calling scope.
Declare it inout, in which case the parameter is passed by reference, and any changes to the parameter is just like you made the changes on the variable in the calling scope.
You can see in the Swift standard library that all the functions that take an Array and mutate it, like sort(), take the Array as inout.
P.S. this is just like how arrays work in PHP by the way
Edit: The following worked for Xcode Beta 2. Apparently, the syntax and behavior of arrays has changed in Beta 3. You can no longer modify the contents of an array with subscripts if it is immutable (a parameter not declared inout or var):
Not valid with the most recent changes to the language
The only way I could get it to work in the play ground was change how you are declaring the arrays. I suggest trying this (works in playground):
import Cocoa
let lastVector: CGFloat[] = [0.0,0.0,0.0]
func lowPass(vector:CGFloat[]) -> CGFloat[] {
let blend: CGFloat = 0.2
vector[0] = vector[0] * blend + lastVector[0] * ( 1 - blend)
vector[1] = vector[1] * blend + lastVector[1] * ( 1 - blend)
vector[2] = vector[2] * blend + lastVector[2] * ( 1 - blend)
return vector
}
var test = lowPass([1.0,2.0,3.0]);
Mainly as a followup for future reference, #newacct's answer is the correct one. Since the original post showed a function that returns an array, the correct answer to this question is to tag the parameter with var:
func lowPass(var vector:[CGFloat]) -> [CGFloat] {
let blend:CGFloat = 0.2
// Smoothens out the data input.
vector[0] = vector[0] * blend + lastVector[0] * (1 - blend)
vector[1] = vector[1] * blend + lastVector[1] * (1 - blend)
vector[2] = vector[2] * blend + lastVector[2] * (1 - blend)
// Sets the last vector to be the current one.
lastVector = vector
// Returns the lowpass vector.
return vector
}

Resources