Choose random data from an Array in Swift - arrays

I would like to create a new Array of 3 random values pulled from this list to be used in two other view controllers. Not sure how to assign the Array so those values are accessible.
class WorkoutDataSource {
var allWorkouts:[Workout]
init() {
allWorkouts = []
let bh1 = Workout(title: "Figure 8s - Clockwise", workoutText: "Clockwise around each leg.", color: UIColor.flatNavyBlueColorDark())
allWorkouts.append(bh1)
let bh2 = Workout(title: "Figure 8s - Counter Clockwise", workoutText: "Counter Clockwise around each leg.", color: UIColor.flatNavyBlue())
allWorkouts.append(bh2)
let bh3 = Workout(title: "Dribble Left Handed", workoutText: "Low and Powerfull.", color: UIColor.flatTealColorDark())
allWorkouts.append(bh3)
let bh4 = Workout(title: "Dribble Right Handed", workoutText: "Low and powerfull", color: UIColor.flatTeal())
allWorkouts.append(bh4)
let bh5 = Workout(title: "Around Both Feet", workoutText: "With feet together, circles around ankles-waist-head, work up & down, the full length of body.", color: UIColor.flatSkyBlueColorDark())
allWorkouts.append(bh5)
let bh6 = Workout(title: "Spider Dribble", workoutText: "feet apart, 1 dribble with each hand in front, then 1 dribble with each hand in back. ", color: UIColor.flatSkyBlue())
allWorkouts.append(bh6)
let bh7 = Workout(title: "Helicopter", workoutText: "1 hand in front, 1 hand in back – move your hands from front to back while catching the ball between your legs.", color: UIColor.flatGreenColorDark())
allWorkouts.append(bh7)
let bh8 = Workout(title: "Scissors - Legs Not Moving", workoutText: "One leg forward and one leg back. Dribble between your legs low and fast.", color: UIColor.flatGreen())
allWorkouts.append(bh8)
let bh9 = Workout(title: "Behind the Back", workoutText: ".",color: UIColor.flatGray())
allWorkouts.append(bh9)
let bh10 = Workout(title: "Fingertip Squeeze", workoutText: ".",color: UIColor.flatPink())
allWorkouts.append(bh10)
}
func getWorkOuts() -> [Workout]{
return allWorkouts
}
}
Thank you

If you simply want to get a random value from the allWorkouts array, then you can do something like this:
let ndx = Int(arc4random_uniform(UInt32(allWorkouts.count)))
let workout = allWorkouts[ndx]
If you want to get three workouts at random, and you don't care if they are unique or not, then you can do something like this:
var workouts = [Workout]()
for i in 0 ..< 3 {
let ndx = Int(arc4random_uniform(UInt32(allWorkouts.count)))
let workout = allWorkouts[ndx]
workouts.append(workout)
}
The workouts array will have the three workouts at that point.
If you want the random items to be unique, then you should use a Set instead of an array and ensure that you run till you get three items, like this:
var workouts = Set<Workout>()
while workouts.count < 3 {
let ndx = Int(arc4random_uniform(UInt32(allWorkouts.count)))
let workout = allWorkouts[ndx]
workouts.insert(workout)
}

Related

How do I fix a random amount of for-loop?

I am creating a game (space shooter) using SpriteKit. Currently adding animations to the game. When explosions spawn, the animation loops at an X number of times (its really random). Sometimes it will loop 3 times and sometimes up to 10 times. The screen ends up being filled up with meaningless explosion animations.
I used to have a simple fade in/fade out animation which was working fine, but have finally upgraded to something smoother. I introduced a for loop and it has given me this issue. i also tried using a while loop with no avail. I have tried using the animation without a sequence but that doesn't fix anything either.
func spawnExplosion(spawnPosition: CGPoint) {
var explosion = SKSpriteNode()
textureAtlas = SKTextureAtlas(named: "explosion")
for i in 1...textureAtlas.textureNames.count {
let name = "explosion\(i).png"
textureArray.append(SKTexture(imageNamed: name))
print(i)
}
explosion = SKSpriteNode(imageNamed: "explosion1.png" )
explosion.setScale(0.6)
explosion.position = spawnPosition
explosion.zPosition = 3
self.addChild(explosion)
print(textureAtlas.textureNames.count)
//explosion animation-action
let explosionAnimation = SKAction.repeat(SKAction.animate(with: textureArray, timePerFrame: 0.05), count: 1)
let delete = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSound, explosionAnimation, delete])
explosion.run(explosionSequence)
}
The expected result is, when the function is called, the animation should run through ONCE and delete itself. Instead, it runs once up to 10 or so times.
Thanks to #Knight0fDragon, I was able to fix this issue by making the texture array local within the function. Now each explosion has it's own instance.
func spawnExplosion(spawnPosition: CGPoint) {
var explosion = SKSpriteNode()
var textureAtlas = SKTextureAtlas()
var textureArray = [SKTexture]()
textureAtlas = SKTextureAtlas(named: "explosion")
for i in 1...textureAtlas.textureNames.count {
let name = "explosion\(i).png"
textureArray.append(SKTexture(imageNamed: name))
}
explosion = SKSpriteNode(imageNamed: "explosion1.png" )
explosion.setScale(0.6)
explosion.position = spawnPosition
explosion.zPosition = 3
self.addChild(explosion)
//explosion animation-action
let explosionAnimation = SKAction.repeat(SKAction.animate(with: textureArray, timePerFrame: 0.05), count: 1)
let delete = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSound, explosionAnimation, delete])
explosion.run(explosionSequence)
}

Swift: Merge custom object elements of an Array if they have the same key

I have a Realm Object class called Performance which looks like this
class Performance: Object {
#objc dynamic var move = ""
#objc dynamic var score = 0
}
and when I print the results of the Realm object I get something like this
[Performance {
move = Run;
score = 3;
}, Performance {
move = Walk;
score = 3;
}, Performance {
move = Run;
score = 2;
}]
Then I try to convert the realm results into an Array so that I can merge the performances with the same move and add their score so that I can show the user which of their move has the highest and lowest scores.
I would want to know if there is any chance where I can merge the Performance with the same move and add their score so that the array would look like this.
[Performance {
move = Run;
score = 5;
}, Performance {
move = Walk;
score = 3;
}]
I have tried few solutions like One, two and three.
But they are not really helpful for my problem.
You could iterate through the array and create a dictionary with your move type being the key.
For example:
var totals: [MoveType: Int] = [:]
for item in performances {
totals[item.move] = item.score + (totals[item.move] ?? 0)
}
For each item, this will add the score to the dictionary for the relevant move type, creating a new dictionary entry if it's the first item with that move type.

Accessing the number of elements in an array and applying gravity behaviour

I'm having issues with getting ALL elements of an array to fall using the Gravity module. I have managed to get the LAST element in the array to fall and then the remaining elements just stay at the top of the screen during testing. Upon debugging
I am using UIKit and want to understand this language thoroughly before using other various engines such as SpriteKit and GameplayKit.
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
gravityBehavior = UIDynamicItemBehavior(items: [carView]) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: [carView, mainCar]) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
dynamicAnimator.addBehavior(collisionBehavior)
}
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
}
With the game so far the last car in the array falls and the main player car that I control has collision behaviour, which is a big step for me!
You are creating a new UIDynamicAnimator with each iteration of the loop and assigning it to dynamicAnimator. That is why only the last element is working, because it is the last one assigned to that variable.
To fix it, just move this line to somewhere that would only be called once.
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
viewDidLoad is a possible place that should work.
UIKitDynamics is backwards of most similar frameworks. You don't animate the object. You have an animator and attach objects to it. As Clever Error notes, you only want one animator in this case.
The key point is that you don't attach gravity to cars; you attach cars to behaviors (gravity), and then behaviors to the animator. Yes, that's bizarre and backwards.
I haven't tested this, but the correct code would be closer to this:
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
var carViews: [UIImageView] = []
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
// First create all the views
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
carViews.append(carView)
}
// and then attach those to behaviors:
gravityBehavior = UIGravityBehavior(items: carViews) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: carView + mainCar) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
dynamicAnimator.addBehavior(collisionBehavior)
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
// You don't need this; it's built into Gravity
// gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
}
The main way that UIKitDynamics is different than most animation frameworks is that things that are animated don't know they're being animated. You can't ask a car what behaviors it has, because it doesn't have any. A UIDynamicAnimator basically is a timing loop that updates the center and transform of its targets. There's really not anything fancy about it (in contrast to something like Core Animation which has many fancy things going on). With a little iOS experience, you could probably implement all of UIKitDynamics by hand with a single GCD queue (it probably doesn't even need that, since it runs everything on main....)

Generate random Strings without a shuffle and repetition of previous ones?

My code already generates a random String of an array when I press a button, but sometimes a String gets repeated. What do I have to do so that the String "Mango" only gets called again when all the other Strings where already called without using a shuffle, I want to call one String at a time?
Example: "Mango", "Kiwi", "Banana", "Pineapple", "Melon", "Mango", "Kiwi",.....
Here is my code:
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
let fruits = Int(arc4random_uniform(UInt32(array.count)))
print(array[fruits])
In order to avoid repetitions you need to keep track of which fruits have previously been seen. There are several ways to do that an all of the proposed solutions do it in one way or another.
For your specific use case, you will need this tracking to be retained outside of the code executed by the button (in your view controller for example).
Here is a generalized structure that could help with this:
(you can define it inside the view controller if this is a one-time thing or outside of it if you intend to reuse the mechanism elsewhere)
struct RandomItems
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
}
To use it, you would declare a variable (in your VC) that will keep track of your fruits:
var fruits = RandomItems(["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"])
And in the button's code use that variable (fruits) to print a non-repeating fruit name at each execution
func buttonPressed() // <- use your function here
{
print( fruits.next() )
}
You need to implement some logic. It's quite easy if you think harder. Run this in your Playground, or if you fully understand this block of code, you can do this in your project already.
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var selectedIndices = [Int]()
for _ in 1...20 {
let randomFruitIndex = Int(arc4random_uniform(UInt32(array.count)))
// Print only if not yet printed once
if !selectedIndices.contains(randomFruitIndex) {
print(array[randomFruitIndex])
selectedIndices.append(randomFruitIndex)
}
// Reset
if selectedIndices.count == array.count {
print("----- CLEARING SELECTED INDICES----")
selectedIndices.removeAll()
}
}
So as you can see, we are adding each generated random number (in your case, it's the fruits variable.) into an array of Int. Then if the number of selectedIndices is equal to the count of the array of fruits, clear all the stored selectedIndices.
OUTPUT:
Pineapple
Melon
Mango
Kiwi
Banana
Apple
----- CLEARING SELECTED INDICES----
Mango
Melon
This is an adaption from the accepted answer of the linked topic in my comment:
var source = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var usedElements = [String]()
func choosePseudoRandomElement() -> String {
if source.count == 0 {
source = usedElements
usedElements = []
}
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
let randomItem = source[randomIndex]
usedElements.append(randomItem)
source.remove(at: randomIndex)
return randomItem
}
for _ in 1...18 {
print("Item: \(choosePseudoRandomElement())")
}
One potential issue with this solution is that it may happen, that the last element of one complete iteration also occurs as the first element of the second iteration. You can handle that case by comparing the randomly chosen item with the item which was chosen before (use a while loop until the items doesn't match anymore).
Also, this does remove elements from the source array. If you do not want that, create a copy of the source array.

Swift - How to create an array of UITextFields and add them to a UIView

I currently have a controller with three different views (top, center and bottom). I would like to add several small textfields/boxes to the center area (kind of like the ones they use for a pin code). My current problem is that I need as many textfields as the amount of characters of a word I send to this view.
For example: if the word is "four", I need four textfields and if the word is "seven", I need five.
My idea was to create an array of textFields, loop through the word to count the amount of characters, and then fill the array. I think my code below does at least that, but now I want to add these small textfields to the centerView using anchors.
How can I add these boxes to the view so that they are horizontally displayed?
var arrayOfTextFields: [UITextField] = []
var game: Game? {
didSet {
if var answerContent = game?.answer {
for char in answerContent.characters {
let count = answerContent.characters.count
for _ in 1...count {
let myTextField: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 40, height: 60))
self.arrayOfTextFields.append(myTextField)
answerContent = answerContent.replacingOccurrences(of: "\(char)", with: "_")
}
}
}
}
}
My centerView:
func createCenterView() -> UIView {
let centerView = UIView()
centerView.backgroundColor = Constants.MAIN_THEME_COLOR
centerView.translatesAutoresizingMaskIntoConstraints = false
return centerView
}
Create an array of views of uncertain count
In swift, there's no need to use loop.
let text = "seven"
let views = (0..<text.characters.count).map{ _ in UITextField(frame:.zero) }
Arrange these views horizontally
use UIStackView, or any other 3rd party StackView that support iOS 9 below.
or other layout system like Flexbox (SwiftBox)
or use UICollectionView.

Resources