Int in swift closure not incrementing - swift

I have a closure in the code below that executes over an array of custom SKShapeNodes (blockNode). The problem I'm having is that completedActionsCount is always 1, no matter how many how many times the closure is executed. Is completedActionsCount just copied at the start of the closure?? Is there something I'm missing here?
for action in blockActionSets[index]
{
var blockActionSet = blockActionSets[index]
var completedActionsCount = 0
let blockNode = getBlockNodeForBlock(action.block)
if blockNode
{
blockNode!.updateWithAction(action, completion:
{ blockAction in
completedActionsCount++
println(completedActionsCount)
println(blockActionSet.count)
if (completedActionsCount == blockActionSet.count)
{
index = index + 1
self.executeBlockActionSetAtIndexRecursive(index, blockActionSets: blockActionSets, completion: completion)
}
})
}
else
{
completedActionsCount++
}
}
The following code is how the block is executed:
func updateWithAction(blockAction : BlockAction, completion : (BlockAction) -> Void)
{
if blockAction.blockActionType == BlockActionType.ChangeColor
{
var startColor = CIColor(CGColor: self.fillColor.CGColor)
var endColor = CIColor(CGColor: UI.getUIColorForBlockType(blockAction.endBlockType).CGColor)
var startRed = startColor.red()
var startGreen = startColor.green()
var startBlue = startColor.blue()
var endRed = endColor.red()
var endGreen = endColor.green()
var endBlue = endColor.blue()
var action = SKAction.customActionWithDuration(CHANGE_COLOR_ANIMATION_DURATION, actionBlock: {(node : SKNode!, elapsedTime : CGFloat)->Void in
var blockNode = node as? BlockNode
if blockNode
{
var ratio : CGFloat = min(1.0, elapsedTime / CGFloat(self.CHANGE_COLOR_ANIMATION_DURATION))
blockNode!.fillColor = UIColor(red: startRed + ratio * (endRed - startRed),
green: startGreen + ratio * (endGreen - startGreen),
blue: startBlue + ratio * (endBlue - startBlue),
alpha: 1.0)
}
if elapsedTime >= CGFloat(self.CHANGE_COLOR_ANIMATION_DURATION)
{
completion(blockAction)
}
})
self.runAction(action)
}
}

I believe the issue is that you are defining the completedActionsCount variable inside the loop, so each time through the loop a new Int value is defined and the value is reset to 0
does this work for you?
var completedActionsCount = 0
for action in blockActionSets[index] {
var blockActionSet = blockActionSets[index]
…
}
re-reading the question… I'm not sure if you intended for each blockActionSet to have it's own counter. But, to answer one of your sub-questions, I'm pretty sure that even though Int is a value type, it is not being copied into the closure, it's available via the outer scope, and you should be able to modify it.
Is updateWithAction:completion: run in the current thread? if it's asynchronous, that may be a factor.
response to update: Generally any API that has "withDuration" in the name will be async to avoid blocking the main thread.
I'm not sure exactly what is happening, but if the counter is being copied because it's a value type I don't know if switching to the main thread will actually fix your issue, but it's worth a try:
blockNode!.updateWithAction(action, completion:
{ blockAction in
dispatch_async(dispatch_get_main_queue()) {
completedActionsCount++
…
}
}
})
I have somewhat low hopes for this approach though since I suspect the value is being copied and copying it again is certainly not going to give you back the original reference. You may be able to wrap the reference to the counter in a reference type (like a variable array or dictionary):
var completedActionsCounts : [Int:Int] = [:]
for action in blockActionSets[index] {
var blockActionSet = blockActionSets[index]
completedActionsCounts[index] = 0
…
blockNode!.updateWithAction(action, completion:
{ blockAction in
dispatch_async(dispatch_get_main_queue()) {
completedActionsCounts[index] += 1
…
}
}
})
}

Related

Calling random AKPlayers in AKSequencer with repeated random results

This is a multiple audiofile playback project I am currently working on, where multiple AKPlayers are played in a random order, through AKSequencer.
First, I have an array of filenames:
Let filenames = [“1.mp3”, “2.mp3, “3.mp3”, … “10.mp3”]
This is loaded on AKPlayer individually, to later call it in a random order:
Let players: [AKPlayer] = {
Do {
Let filenames = [“1.mp3”, “2.mp3, “3.mp3”, … “10.mp3”]
Return try filenames.map { AKPlayer(audioFile: try AKAudioFile(readRileName: $0)) }
} catch {
fatalError()
}
}()
Then, I called AKPlayer through AKsequencer, by triggering it through ‘playRandom’ function:
Let sequencer = AKSequencer()
Let callbackInst = AKCallbackInstrument()
func playRandom() {
let playerIndex = Int(arc4random_uniform(UInt32(players.count)))
players[playerIndex].play()
}
func addTracks() {
let track = sequencer.newTrack()!
track.add(noteNumber: 48, velocity: 127, position: AKDuration(beats: 0), duration: AKDuration(beats: 16), channel: 0)
track.setMIDIOutput(callbackInst.midiIn)
callbackInst.callback = { status, note, vel in
guard status == .noteOn else { return }
self.playRandom()
}
}
Lastly, I set AKPlayer as AudioKit.output, and started the sequencer.
So far this was successful! The sequencer plays AKPlayer seamlessly, in a random order.
But I wanted to try different kind of randomness: repeating randomly selected player 2 or 3 times. (like 2, 2, 3, 3, 3, 1, 1, 5, 5, 5, 9, 9, …) Right now, ‘playRandom’ simply chooses different AKPlayer on each repeat.
As one solution, thanks to StackOverFlow masters, I tried something like:
class RandomWithRepeats {
var range: ClosedRange<Int>
var repeatRange: ClosedRange<Int>
var repeatCount = 0
var value = 0
init(range: ClosedRange<Int>, repeatRange: ClosedRange<Int>) {
self.range = range
self.repeatRange = repeatRange
}
// generate a random number in a range
// Just use Int.random(in:) with Swift 4.2 and later
func random(in range: ClosedRange<Int>) -> Int {
return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound + 1))) + range.lowerBound
}
func nextValue() -> Int {
// if repeatCount is 0, its time to generate a new value and
// a new repeatCount
if repeatCount == 0 {
// For Swift 4.2, just use Int.random(in:) instead
value = self.random(in: range)
repeatCount = self.random(in: repeatRange)
}
repeatCount -= 1
return value
}
}
And then I modified playRandom function like:
func playRandom() {
Let rand = randomWithRepeats(range: 1…players.count, repeatRange: 2…3)
Do { players[rand.nextValue()].play() }
}
Turns out, this is the exact same result because the playRandom function itself triggered (by AKSequencer) each repeat, so it doesn’t actually ‘repeat’ random AKPlayer 2 or 3 times. Can I solve this issue in a different way? Much appreciated. <3
The job of the RandomWithRepeats class is to keep track of when to deliver you a different AKPlayer instance, but you are creating a new instance of RandomWithRepeats each time you call playRandom(), so it can't keep track of anything.
Create a single instance of RandomWithRepeats when you create the array of players and use this instance in your playRandom() method.
let rand = RandomWithRepeats(range: 0 ... players.count, repeatRange: 2 ... 3)
func playRandom() {
try? players[rand.nextValue()].play()
}
For a cleaner solution, you might encapsulate the logic of RandomWithRepeats together with the AKPlayer array into a single class, such as a RandomlyRepeatingPlayer class.

"Attemped to add a SKNode which already has a parent:" in Repeat Loop. Any simple work around?

I am pretty Newbie to programming. And I am trying to pile up the random blocks dynamically till it hits the upper frame. But it seems that Swift doesn't let me to do so. Did I miss anything please? Any input are appreciated.
let blocks =[block1,block2,block3,block4,block5,block6,block7,block8,block9,block10,block11,block12]
var block:SKSpriteNode!
let blockX:Double = 0.0
var blockY:Double = -(self.size.height/2)
repeat{
block = blocks.randomBlock()
block.zPosition = 2
block.position = CGPoint(x:blockX, y:blockY)
block.size.height = 50
block.size.width = 50
self.addChild(block)
blockY += 50
} while( block.position.y < self.size.height)
extension Array {
func randomBlock()-> Element {
let randint = Int(arc4random_uniform(UInt32(self.count)))
return self[randint]
}
}
you need to have someway of tracking which blocks have been selected and ensure that they don't get selected again. The method below uses an array to store the indexes of selected blocks and then uses recursion to find a cycle through until an unused match is found.
private var usedBlocks = [Int]()
func randomBlock() -> Int {
guard usedBlocks.count != blocks.count else { return -1 }
let random = Int(arc4random_uniform(UInt32(blocks.count)))
if usedBlocks.contains(random) {
return randomBlock()
}
usedBlocks.append(random)
return random
}
in your loop change your initializer to
let index = randomBlock()
if index > -1 {
block = blocks[index]
block.zPosition = 2
block.position = CGPoint(x:blockX, y:blockY)
}
remember that if you restart the game or start a new level, etc. you must clear all of the objects from usedBlocks
usedBlocks.removeAll()

calling function from an array with time delay

Let's say I have this array of functions:
lazy var funcArray = [firstFunc, secondFunc, thirdFunc, ....n+Func, ...Inifinit number of Func........]
How would you call them one after the other with a 2.5 second delay?
I have tried without success many things including this while loop:
while n < funcArray.count {
funcArray[n]()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
n = n +1
}
}
i write small code for better understanding.
initialize count and funcTimer variable and created static function array .
var count:Int = 0
var funcTimer = Timer()
let funcArray:Array = [somefunc(),somefunc(),somefunc()]
After that add these line in appropriate place
funcTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: (#selector(ViewController.scheduleArrayFunctions)), userInfo: nil, repeats: true)
#objc func scheduleArrayFunctions(funcarray:[ String])
{
if count < self.funcArray.count {
//this if is interesting checking both static function when it check it call the somefunc method
if self.funcArray[count] == ViewController.somefunc(){
print("equel")
}
self.count += 1
}
else
{
funcTimer.invalidate()
}
}
func somefunc()
{
print("hello world")
}
Hope so this will work for you.
I think this will work. Follow these steps.
Declare a variable count as zero.
Schedule a timer with time interval 2.5 and repeat to true.
Now call the function from the array with index as count inside the timer callback block.
Check if the count is less than array.count.
Increment the count.
Otherwise stop the timer.
I did a function to function loop
probably bad coding but...it works
n = 0
self.funcArray[n]()
n = n + 1
timerAction()
func timerAction() {
let when = DispatchTime.now() + 2.5
DispatchQueue.main.asyncAfter(deadline: when) {
self.funcArray[self.n]()
self.n = self.n + 1
if self.n < self.funcArray.count {
self.timerAction2()
}
}
}
func timerAction2() {
let when = DispatchTime.now() + 2.5
DispatchQueue.main.asyncAfter(deadline: when) {
self.funcArray[self.n]()
}
if self.n < self.funcArray.count {
self.timerAction()
}
}

Local and Global variables in Swift

I have a simple piece of code that I guess I'm using local and global variables in it. But, I have a hard time understanding what's going wrong in here. I am setting "var hhhh:Int = 0" at first. Then, inside the if statement, I set "hhhh = appleCount["count"] as! Int". Since appleCount["count"] is not zero and has some value, hhhh gets its' value (I tried that uisng a print statement and hhhh is not zero inside if statement), but, later when I print hhhh with print("(hhhh)") outside if, I again get zero for its' value. Does it have something to do with local and global variables? I'm trying to communicate with Parse in the code by the way.
Thanks a lot for your kind help
import UIKit
import Parse
class NewsPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad(
var hhhh:Int = 0
var tttt:Int = 0
var cccc:Int = 1
if cccc == 1 {
var query = PFQuery(className: "Count")
query.getObjectInBackgroundWithId("RhC25gVjZm", block: { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let appleCount = object {
appleCount["count"] = appleCount["count"] as! Int + 1
hhhh = appleCount["count"] as! Int
appleCount.saveInBackground()
}
})
}
print(hhhh)
}
}
It does not have to do with local and global variables. It has to do with background threads. The code in brackets {} after the parameter label "block" will run in a background thread at a later time.
Your print(hhhh) is running before the block has had a chance to change hhhh. Move the print statement back inside the block so you can see the variable being set.
osteven response helped me a lot understanding the problem. Thanks a lot man. In addition to osteven's response, I just waned to add that a major part of my problem was coming because I was trying to do some mathematical operations on the objects I was trying to save in Parse. So, I also figured that I could create an array, save my objects inside that array, and then access the key and update the values. Here is a sample code of what I am using right now. It does some mathematical operation on two different objects saved in Parse and updates the label's text on screen. For accessing the two objects in Parse and updating them I'm using an array.
Hope the answers here will help someone in future as the awesome people of StackOverFlow are helping me now.
Peace!
var hhhh : [Int] = []
#IBOutlet weak var jPercent: UILabel!
#IBOutlet weak var yPercent: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
var query = PFQuery(className: "Count")
if cccc == 1 {
query.getObjectInBackgroundWithId("DcU9tdRdnl", block: { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let jCount = object {
jCount["count"] = jCount["count"] as! Int + 1
jCount.saveInBackground()
}
})
} else if cccc == 2 {
query.getObjectInBackgroundWithId("5Bq4HJbFa3", block: { (object: PFObject?, error: NSError?) -> Void in
if error != nil {
print(error)
} else if let yCount = object {
yCount["count"] = yCount["count"] as! Int + 1
yCount.saveInBackground()
}
})
}
//shouldn't use same query for findObjectsInBackgroundWithBlock and getObjectInBackgroundWithId otherwise you'll get a runtime error
var query2 = PFQuery(className: "Count")
query2.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let users = objects {
for object in users {
if let user = object["count"] as? Int {
self.hhhh.append(user)
}
}
}
var gggg = 100*Float(self.hhhh[0])/(Float(self.hhhh[0]+self.hhhh[1]))
self.yPercent.text = String(format: "%.1f", gggg) + "%"
self.jPercent.text = String(format: "%.1f", 100 - gggg) + "%"
print(self.hhhh[0])
}
}

Trouble using width for String in for-in cycle

I've write a simple code:
extension String {
func trailingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = s + " "
}
return s
}
func leadingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = " " + s
}
return s
}
}
class ViewController: UIViewController {
var users = ["Marco", "Gianni", "Antonio", "Giulio", "Franco"]
var ages = [29, 45, 17, 33, 37]
override func viewDidLoad() {
super.viewDidLoad()
var merged = [String: Int] ()
var totalAge = 0.0
for var i = 0; i < ages.count; i++ {
merged[users[i]] = ages[i]
}
for user in sorted(merged.keys) {
let age = merged[user]
totalAge += Double(age!)
let paddedUser = user.trailingSpaces(10)
let paddedAge = "\(age)".leadingSpaces(3)
println("\(paddedUser) \(age!)")
}
println("\n\(merged.count) users")
println("average age: \(totalAge / Double(merged.count))")
}
}
but I can't make it work the leadingSpaces function and I can't understand the reason, it's quite identical to the other extension func that works.
It give the error
fatal error: Can't form Range with end < start
on runtime
in case you run into this kind of problem, always do a println() of the variable you are using
println("\(age)") right before let paddedAge = "\(age!)".leadingSpaces(3)
reveals the problem
age is an optional, meaning that you are trying to do the padding on a String which has this value "Optional(17)"
Thus, your count(s) is higher than 3, and you have an invalid range
Your variable age is not an Int - it's an optional - Int?. You know this already as you are unwrapping it in the lines totalAge += Double(age!) and println("\(paddedUser) \(age!)") - but you are not unwrapping it in the failing line let paddedAge = "\(age)".leadingSpaces(3). The string being passed to leadingSpaces is not "17", it's "Optional(17)", which is why your padding function is failing, as the length is greater than the requested width.
Having said that, as the commentator #milo256 points out, Swift can only iterate upwards, and so unless you put a check on width >= .count in your padding functions they will crash at some point.