Unable to create array of SKActions - arrays

I'm experimenting with SpriteKit in Swift but somehow seem unable to create an array of actions to use in a sequence. I've split it up to try and pin-point the problem, but no luck so far.
func animateBackground(){
let moveLeft = SKAction.moveByX(100, y: 0, duration: 3)
moveLeft.timingMode = SKActionTimingMode.EaseInEaseOut
let moveRight = SKAction.reversedAction(moveLeft)
let actions = [moveLeft, moveRight] // <--- here there be dragons/trouble
let sequence = SKAction.sequence(actions)
let repeat = SKAction.repeatActionForever(sequence)
}
When trying to create the actions-array I get the error "Cannot convert the expression's type 'Array' to type 'ArrayLiteralConvertible' " So, I thought I might need to be more explicit and attempted to change it to
var actions: SKAction[] = [moveLeft, moveRight]
This seemed to bring down the house, and not in a good way, resulting in the SourceKit terminated bug...

You're adding a function to the array for moveRight, not the SKAction itself. Try using this instead:
let moveRight = SKAction.reversedAction(moveLeft)()

When you create moveRight you're actually generating a function. You can call the function with "()" to get the actual SKAction. I added explicit types to the two SKAction's so it's clear that they can be put in an SKAction[]:
let moveLeft:SKAction = SKAction.moveByX(100, y: 0, duration: 3)
moveLeft.timingMode = SKActionTimingMode.EaseInEaseOut
let moveRight:SKAction = moveLeft.reversedAction()
let actions = [moveLeft, moveRight]
let sequence = SKAction.sequence(actions)
let repeat = SKAction.repeatActionForever(sequence)

Related

How to simplify Swift array initialization

This is in reference to https://stackoverflow.com/a/47765972/8833459.
The following statement is given:
let fooPaths: [WritableKeyPathApplicator<Foo>] = [WritableKeyPathApplicator(\Foo.bar), WritableKeyPathApplicator(\Foo.baz)]
Is there an init or something else that can be done so that the following (or something similar) might also work?
let fooPaths: [WritableKeyPathApplicator<Foo>] = [\Foo.bar, \Foo.baz]
The original statement is just too much typing! My "preferred" method currently gives the error:
error: cannot convert value of type 'WritableKeyPath<Foo, String>' to expected element type 'WritableKeyPathApplicator<Foo>'
The type is unnecessary:
let fooPaths = [WritableKeyPathApplicator(\Foo.bar), WritableKeyPathApplicator(\Foo.baz)]
The second Foo is unnecessary.
let fooPaths = [WritableKeyPathApplicator(\Foo.bar), WritableKeyPathApplicator(\.baz)]
Also the first one if you do provide the type:
let fooPaths: [WritableKeyPathApplicator<Foo>] = [WritableKeyPathApplicator(\.bar),
WritableKeyPathApplicator(\.baz)]
If the major concern is typing, add a local typealias:
typealias WKPA<T> = WritableKeyPathApplicator<T>
let fooPaths: [WKPA<Foo>] = [WKPA(\.bar),
WKPA(\.baz)]

Search Multiple Words (or Characters) in String Array (swift)

I am developing an application for words.
My problem resembles the problem: this
But, I want to do the search in the string array.
let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = ["I","N"]
// myArr.contains(findChrs) //so error
I want to: return 3 or "BERLIN","ISTANBUL" and "WIEN" (Contains "I" and "N")
I tried it, but it's too long...(I look individually. Too many words.):
for i in 0...myArr.count - 1 {
let success = !findChrs.contains(where: { !myArr[i].contains($0) })
if success {
print(myArr[i])
}
}
Is there an easier way? Thank you so much.
There's a swifty way to solve your problem
I started with using just one filter on the myArr array using hardcoded search terms
let filteredStrings : [String] = myArr.filter({
return $0.contains("I") && $0.contains("N")
})
but thats going to help only if your findChars are always going to be I and N only.
Later I realized how to do it without hardcoding the find char characters:
ANSWER 1
let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = ["I","N"]
let filteredStrings : [String] = myArr.filter({ (aString) in
let hasChars = findChrs.filter({(bString) in
return aString.contains(bString)
})
print(hasChars)
return hasChars.count == findChrs.count
})
print(filteredStrings)
Instead of using $0, I think in the second chunk of code, its easier to understand whats happening with aString and bString.
Do check the code in a playground and see how this code works. If you haven't used the higher order functions, it can be a little daunting to understand filters without a playground and the print statements.
Update:
Was just thinking about this problem and I gave this alternate approach a try, using sets, map and filter. It is super swifty, and can be difficult to read/understand:
ANSWER 2, concise
let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = ["I","N"]
let finderSet:Set<String> = Set(findChrs)
let filteredArray = myArr.filter {
return Set($0.characters.map({String($0)})).intersection(finderSet).count == findChrs.count
}
For the sake of readability and ease of understanding, here's what is happening:
Answer 2, verbose
let filteredArray = myArr.filter { (aString) -> Bool in
//for each string in myArr, convert it into an array of string chars
let stringArray = aString.characters.map({aCharacter in
String(aCharacter)
})
//convert string array into a set
let aSet = Set(stringArray)
// find the intersection (common elemnts from aSet and finderSet)
let intersect = aSet.intersection(finderSet)
//return true if aString has all of findChrs elements
return intersect.count == findChrs.count
}
Both Answer 2 'concise' and 'verbose' will give you the same results.
Based on some simple code execution time check, it looks like Answer 1 is ~3x faster than Answer 2. So, Answer 1 is still a better choice and probably easier to understand.
Hope this helps anyone reading the answer understand filter and map!
You could also use a set for the filter provided you're not looking for patterns that contain repeated letters:
let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = Set<Character>(["I","N"]) // let findChrs = Set("IN")
let matchingCities = myArr.filter{ findChrs.isSubset(of:$0) }

Add a delay, when iterating over an array, for every single element

Trying to use an SKLabelNode to display the elements one by one of the array below, on the label. I believe the problem is that it iterates over the array faster than it takes the sequence to run its course, therefore causing a crash because self.addChild() is trying to be displayed again for "Set" while "Ready" is still being displayed.
My question is, how do I slow down this iteration so that "Ready" can appear and disappear, before "Set" gets displayed?
let readySetGo = ["Ready", "Set", "GO!"]
for i in readySetGo {
newLevelLabel.text = i
newLevelLabel.fontSize = 60
let wait = SKAction.wait(forDuration: 2)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([wait, remove])
newLevelLabel.run(sequence)
self.addChild(newLevelLabel)
}
The reason why it is immediately going to GO is because you do not chain all your actions in the same sequence, so all of your stages are running at the same time:
Try this, I've not the compiler now:
let readySetGo = ["Ready", "Set", "GO!"]
newLevelLabel.fontSize = 60
let seq: [SKAction]=[]
let waitTime:TimeIneterval = 2
for i in readySetGo {
let block = SKAction.run{
self.newLevelLabel.text = i
}
let wait = SKAction.wait(forDuration: waitTime)
seq.append(wait)
seq.append(block)
}
let wait = SKAction.wait(forDuration: waitTime)
let remove = SKAction.removeFromParent()
seq.append(wait)
seq.append(remove)
let sequence = SKAction.sequence(seq)
newLevelLabel.run(sequence)
self.addChild(newLevelLabel)

Swift 3: Dictionary from large arrays

I am working with two large arrays containing following data:
print(dataID[1]) // ["DataID123"]
print(dataAR[1]) // ["73.075584"]
I'd like to form a dictionary from the two arrays by:
var arrayofDict = [String: AnyObject?]()
for i in 0...csvDataID.count {
if i < csvDataID.count {
let key = csvDataID[i]
let value = csvDataAG[i]
arrayofDict[key] = value
}
}
But get following error:
Cannot subscript a value to '[String: AnyObject?]' with an index of
type '[String]'
I also tried a few other approaches aswell resulting in the same error.
Could anyone help me and is there even a more efficient way to handle the arrays?
Thanks!
let key = csvDataID[i][0]
let value = csvDataAG[i][0]
Explaination:
The log says that csvDataID[1] is an array: ["DataID123"]
The same is for the second one.:["73.075584"]
So,
let key = csvDataID[i] // you set the key is array.
That's why you can see this error.

Swift trying to cross check array of SKActions with array of strings

Hi I'm trying to pick an SKAction from an array of SKAction's by using a string.
I have an array with all my game's possible SKActions in it. I then need to pull out the particular actions that match a selected node's possible action names (strings).
So, for example, I might have
allActions = [runCentre, runLeft, runRight]
whereby those are SKActions, and
possibleActions = [runCentre, runRight]
which are strings accessed from a property list relating to the given node type.
How do I query the allActions array for the values in possibleActions? I know the mechanics of iterating through both arrays but not what I would actually try to access. Can I give the SKActions a string property somehow? I tried
runStraight.setValue("runStraight", forKey: "name")
but that throws up NSUnknownKeyException. Is there a better way to do this?
Any help would be very much appreciated!
I really don't understand very well what do you want to achieve.
Unfortunately a SKAction don't have a tag or name property as identification.
You can create a dictionary to associate a string key to a SKAction
Suppose you have these actions:
let runCentre = SKAction.move(to: CGPoint(x:100.0,y:100.0), duration: 1.0)
let runLeft = SKAction.move(to: CGPoint(x:0.0,y:100.0), duration: 1.0)
let runRight = SKAction.move(to: CGPoint(x:200.0,y:100.0), duration: 1.0)
let runTop = SKAction.move(to: CGPoint(x:100.0,y:200.0), duration: 1.0)
let runBottom = SKAction.move(to: CGPoint(x:100.0,y:0.0), duration: 1.0)
// create a dictionary (key:string, value:SKAction)
let allActions = ["runCentre":runCentre,"runLeft":runLeft,"runRight": runRight,"runTop":runTop,"runBottom":runBottom]
You can build a dictionary with:
let allActions = ["runCentre":runCentre,"runLeft":runLeft,"runRight": runRight,"runTop":runTop,"runBottom":runBottom]
Now suppose you have a node with these possible actions:
let runDiag = SKAction.move(to: CGPoint(x:0.0,y:200.0), duration: 1.0)
let possibleActions = ["runCentre":runCentre, "runRight": runRight, "runDiag":runDiag]
To know what possibleActions are available in allActions (relying on equal keys) you could do:
let availableActions = allActions.filter { possibleActions.keys.contains($0.key) }
Save the list of all actions in a dictionary of type [String:SKAction].
let allActions = [
"runCenter": runCenter,
"runLeft": runLeft,
// etc
]
If you have a node's possible actions in a String array, you could use (flat)Map.
let possibleActions = ["runCenter", "idle"]
let possibleSKActions = possibleActions.flatMap { allActions[$0] }
// or if we are sure the action names will always have a value
let possibleSKActions = possibleActions.map { allActions[$0]! }

Resources