How to fill UISegmentedControl Indexes with String Array Values in Swift - arrays

I have a UISegmentedControl with 4 indexes that contains quiz answers that are gathered from a string array. My goal is to fill all 4 indexes with wrong answers, and then randomly replace one of those indexes with the correct answer. When I run the simulator, the app appears to enter an infinite loop and the view never loads. When I comment-out the entire second for-loop (which is supposed to fill the indexes with incorrect guesses), the view loads and the segmented control appears with just the correct answer displayed in one segment. Why is the infinite loop being entered (if that is the case) and how can I modify this code to display the segmented controls as desired?
func nextQuestion()
{
questionNumberLabel.text = String(format: "Question %1$d of %2$d",
(correctGuesses + 1), numberOfQuestions)
answerLabel.text = ""
correctAnswer = allAnimals.removeAtIndex(0)
animalImageView.image = UIImage(named: correctAnswer) // next animal
// re-enable UISegmentedControls and delete prior segments
for segmentedControl in segmentedControls
{
segmentedControl.enabled = true
segmentedControl.removeAllSegments()
}
// place guesses on displayed UISegmentedControls
allAnimals.shuffle() // shuffle array
var i = 0
for segmentedControl in segmentedControls
{
if !segmentedControl.hidden
{
var segmentIndex = 0
while segmentIndex < 4
{
if i < allAnimals.count && correctAnswer != allAnimals[i]
{
segmentedControl.insertSegmentWithTitle(
getStringFromFile(allAnimals[i]),
atIndex: segmentIndex, animated: false)
++segmentIndex
}
++i
}
}
}
// pick random segment and replace with correct answer
let randomIndexInRow = Int(arc4random_uniform(UInt32(4)))
segmentedControls[0].removeSegmentAtIndex(
randomIndexInRow, animated: false)
segmentedControls[0].insertSegmentWithTitle(
getStringFromFile(correctAnswer),
atIndex: randomIndexInRow, animated: false)
}

The solution is:
selectedSegmentIndex = 2

Related

How to make array of 30 different unique random numbers?

I want to use arc4random to generate array of 30 different numbers, so that there is no repeating in it. How can I do it?
Update:
Thanks to #Sulthan for his elegant thought (commented on Gereon's answer):
anyway, depending on the difference between limit and the number of
generated elements this can have a terrible performance. The problem
is the case when limit is close to the number of generated elements.
Then it would be much better to take 1..<limit and shuffle it.
Which means that there is no even need to generate random Int. The simplest way I could think of is to do it as (Swift 4.2):
let randoms = Array(0..<30).shuffled()
therefore randoms is an array of Ints, contains 30 unique values from 0 to 29.
Less Than Swift 4.2:
However, if you are not using Swift 4.2, I would recommend to check this great answer for getting shuffled collection.
Ignored Solution:
You could achieve it like this:
var uniques = Set<UInt32>()
func generateUniqueUInt32() -> UInt32 {
let random = arc4random_uniform(31)
if !uniques.contains(random) {
uniques.insert(random)
return random
} else {
return generateUniqueUInt32()
}
}
let randomArray = Array(0..<30).map { _ in Int(generateUniqueUInt32()) }
Therefore randomArray is an array of Ints, contains 30 unique values from 0 to 29.
Swift 4.2:
You should replace:
let random = arc4random_uniform(31)
with:
let random = Int.random(in: 1..<30)
which means that generateUniqueUInt32 should return -directly- Int (and renamed to generateUniqueInt):
func generateUniqueInt() -> Int {
let random = Int.random(in: 1..<30)
if !uniques.contains(random) {
uniques.insert(random)
return random
} else {
return generateUniqueInt()
}
}
let randomArray = Array(0..<30).map { _ in generateUniqueInt() }
It may be a pretty heavy action but you can do like that:
var randomNumbers: [Int] = []
while randomNumbers.count != 30{
let number = Int(arc4random_uniform(1000) + 1)
if randomNumbers.contains(number) {
print("Already Exits")
}else{
randomNumbers.append(number)
}
}
replace "1000" for a range of number that you need. That function generated 30 different number between 0...1000
Use a Set to store the generated numbers so far
func thirtyUniqueRandomNumbers(_ limit: Int) -> [Int] {
var randoms = Set<Int>()
repeat {
let rnd = Int(arc4random_uniform(UInt32(limit)))
if !randoms.contains(rnd) {
randoms.insert(rnd)
}
} while randoms.count < 30
return randoms.map { $0 }
}
Here is a simple solution. Start a while loop. Within this loop generate a random number (between 0 and 1000). And then append the number into your array if the array doesn't contains the number.
func generateArrayOfRandomNumbers() -> [Int] {
var array: [Int] = []
while array.count < 30 {
let randomInt = Int.random(in: 0...1000)
guard !array.contains(randomInt) else { continue }
array.append(randomInt)
}
return array
}

Select middle value in array - Swift

I'm trying to make sure that the middle value Lists is the first view that is seen when building my application. Xcode offers if let firstView = viewList.first and if let firstView = viewList.last but I can't workout how to select the middle value.
lazy var viewList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let lists = sb.instantiateViewController(withIdentifier: "Lists")
let reminders = sb.instantiateViewController(withIdentifier: "Reminders")
let todo = sb.instantiateViewController(withIdentifier: "To Do")
return [reminders, lists, todo]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstView = viewList.first {
self.setViewControllers([firstView], direction: .forward, animated: true, completion: nil)
}
}
Similar to first and last, you can extend Array with a computed middle property that returns an optional Element.
extension Array {
var middle: Element? {
guard count != 0 else { return nil }
let middleIndex = (count > 1 ? count - 1 : count) / 2
return self[middleIndex]
}
}
Usage Example:
if let middleView = viewList.middle {
//... Do something
}
I want you to be aware that first and last can return the same element if the array has only 1 element.
Similarly, though this extension will work for any array length, it can return the same element for:
first, middle & last if your array has only 1 element
middle & last if your array has only 2 elements
Can add an extension to Array to accomplish this:
extension Array {
var middleIndex: Int {
return (self.isEmpty ? self.startIndex : self.count - 1) / 2
}
}
let myArray: [String] = ["Hello", "World", "!"]
print("myArray.middleIndex: \(myArray.middleIndex)") // prints 1
Since viewList declared as [UIViewController] (not as optionals - [UIViewController?]), you don't have to "optional binding" (checking the whether the element is nil or not) because it has to be exist. what you should do instead is to check if the index in the range (making sure that the index is in the range).
Logically speaking (obviously), if you are pretty sure that viewList always has 3 elements, there is no need to do any check, just:
let middleViewController = viewList[1]
In case of the number of elements in viewList is undetermined and you are aiming to get the middle element, you simply get it as:
let middleViewController = viewList[(viewList.count - 1) / 2]
Remember, first and last are optionals, in your case there is no need to work with optionals...
Your viewList is an array of type UIViewController. And first and last only denotes their 0th and last index. like:
viewList.first is same as viewList[0]
viewList.last is same as viewList[viewList.count - 1]
Only difference in using these are if you will use viewList.first it will return nil if your array is empty, but if you will use viewList[0] on empty array, you app will be crash with error index out of bound...
So, you can easily access your middle value with index as:
if viewList.count > 1 {
let middleView = viewList[1]
self.setViewControllers([middleView], direction: .forward, animated: true, completion: nil)
}
If you are not sure about the viewList.count will be 3 or more, Then:
let middleIndex = (viewList.count - 1) / 2
let middleView = viewList[middleIndex]

Swift, Generating Array Variables of a Numbered List

I'm making a simple game in swift and xcode and I ran into this problem that I can't figure out. Because I have so many levels, the code locks up indexing and slows down the whole program. I get a color wheel spinning for a few minutes but it never crashes. Just takes several minutes everytime I type in a few characters. Strange, but xcode has always had it's bugs right?
Each Button ("button1, button2..." below) gets a single number from "level1:Array". It's like a code that fills in the button's value for the game. There are only 4 buttons, but the numbers should be able to change as they each have their own variables.
I want to generate this for every level. I should be able to generate something like "button1 = level#[0]" where # is replaced by "userLevel". Changing to a string and doing something like "button1 = ("level(userLevel)") as! Array... doesn't seem to work. Look below and use my terminology when giving examples if you can. Thanks!
Here's the example:
let level1:Array = [9,7,4,1] // current puzzle, to go directly into button vars below (button1,button2,ect)
var userLevel = 1 // current user's level
if userLevel == 1 {
print("making Level 1, setting buttons to value from array")
button1 = level1[0]
button2 = level1[1]
button3 = level1[2]
button4 = level1[3]
}
Now, since level# is repeated so often (for each level as the number progresses) I would rather just make it something like this:
//this doesn't work in swift, but what else can I do?
if userLevel > 0 {
button1 = level\(userLevel)[0]
button2 = level\(userLevel)[1]
button3 = level\(userLevel)[2]
button4 = level\(userLevel)[3]
}
Is there an easy way to do this? Thanks!
-GG
Try using a for-in loop. Create an array of the buttons, and then
var index = 0
for button in buttons {
button = level1[index]
index++
}
EDIT since you want both the user level and the level number to increase, I suggest you define the levels like this. (Make sure that the number of buttons is equal to the number of userLevels, otherwise you will have problems)
var array = [1,2,3]
let levels = [1:[1,3,8],2:[3,6,4],3:[4,2,5]]
var index = 0
if array.count == levels.count {
for number in array {
array[index] = levels[index+1]![index]//The second index can be 0 if you want
index++
}
}
//array = [1,6,5]
// You could create a second index to match the number of levels within the main user level.
In this case, assume array to be your array of buttons
EDIT 2 :)
I've made a function that will allow you to assign all the levels to your array for a specific userLevel, since I see that is what you want
let levels = [1:[1,3,8],2:[3,6,4],3:[4,2,5]]
func assignValuesToArray(levelNo:Int) -> [Int] {
var array: [Int] = []
if (levelNo > 0) && (levelNo <= levels.count) {
for (level,levelArray) in levels {
if level == levelNo {
for value in levelArray {
array.append(value)
}
}
}
return array
} else {
print("This Level number does not exist")
return []
}
}
var finalArray = assignValuesToArray(2)
print(finalArray) // Returns [3,6,4]
As you can see from this example, you will return your array of buttons from the function, and you can assign the returned array values to whatever you like.

How to loop through array to find 4 identical values that are consecutive?

I have the following swift array:
var winSuitArray = [cardSuit1, cardSuit2, cardSuit3, cardSuit4, cardSuit5, cardSuit6, cardSuit7]
cardSuit1, cardSuit2 and so on, are variables that will equal strings like "clubs" or "hearts". What I want to do is loop through this array, and if the loop finds 4 identical objects whose indexes are consecutive, set the winSuitStatus bool to true.
For example, if the array looks like this:
["hearts", "clubs", "clubs", "clubs", "clubs", "diamonds", "spades"]
I want to loop through it like so:
for card in winSuitArray {
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
Is this possible to do?
To tell the truth, I'd probably do something similar to #KnightOfDragon's answer. There's nothing wrong with that approach. But this problem opens up some opportunities to build some much more reusable code at little cost, so it seems worth a little trouble to do that.
The basic problem is that you want to create a sliding window of a given size over the list, and then you want to know if any of the windows contain only a single value. So the first issue to to create these windows. We can do that very generally for all collections, and we can do it lazily so we don't have to compute all the windows (we might find our answer at the start of the list).
extension Collection {
func slidingWindow(length: Int) -> AnyRandomAccessCollection<SubSequence> {
guard length <= count else { return AnyRandomAccessCollection([]) }
let windows = sequence(first: (startIndex, index(startIndex, offsetBy: length)),
next: { (start, end) in
let nextStart = self.index(after: start)
let nextEnd = self.index(after: end)
guard nextEnd <= self.endIndex else { return nil }
return (nextStart, nextEnd)
})
return AnyRandomAccessCollection(
windows.lazy.map{ (start, end) in self[start..<end] }
)
}
}
The use of AnyRandomAccessCollection here is to just hide the lazy implementation detail. Otherwise we'd have to return a LazyMapSequence<UnfoldSequence<(Index, Index), ((Index, Index)?, Bool)>, SubSequence>, which would be kind of crazy.
Now are next question is whether all the elements in a window are equal. We can do that for any kind of Equatable sequence:
extension Sequence where Iterator.Element: Equatable {
func allEqual() -> Bool {
var g = makeIterator()
guard let f = g.next() else { return true }
return !contains { $0 != f }
}
}
And with those two pieces, we can just ask our question. In the windows of length 4, are there any runs that area all equal?
let didWin = suits.slidingWindow(length: 4).contains{ $0.allEqual() }
Or we could go a little different way, and create a SlidingWindowSequence that we could iterate over. The logic here is basically the same. This just wraps up the windowing into a specific type rather than a AnyRandomAccessCollection. This may be overkill for this problem, but it demonstrates another powerful pattern.
public struct SlidingWindowSequence<Base: Collection>: Sequence, IteratorProtocol {
let base: Base
let windowSize: Base.IndexDistance
private var windowStart: Base.Index
public init(_ base: Base, windowSize: Base.IndexDistance) {
self.base = base
self.windowSize = windowSize
self.windowStart = base.startIndex
}
public mutating func next() -> Base.SubSequence? {
if base.distance(from: windowStart, to: base.endIndex) < windowSize {
return nil
}
let window = base[windowStart..<base.index(windowStart, offsetBy: windowSize)]
windowStart = base.index(after: windowStart)
return window
}
}
let didWin = SlidingWindowSequence(suits, windowSize: 4).contains{ $0.allEqual() }
var suit = ""
var count = 1
for card in winSuitArray {
if(suit == card)
{
count++
}
else
{
count = 1
suit = card
}
if(count == 4)
{
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
}
You can use a counter variable to do this, initialized to 1.
for each value in array:
if value equals previous value
increment counter
else
counter = 1
if counter >= 4
set winCounter to true

Create a function to iterate and cycle through an array in Swift

I'm trying to create a function in Xcode that I can call every time I hit a button to iterate through an array in sequence which then updates the value of the button title.
I can't seem to crack the challenge. I've tried various iterations of while loops and if statements but everytime I run it I end straight up at the last value in the array. Here's the code I've got at the moment, I tried to add a break clause to stop the function from automatically iterating through the whole array but it's now throwing up an error message saying that the code after the return statement will never be executed:
So, I've created an instance of a button within my viewController as follows:
#IBAction func repCount() {
repCountButton.setTitle("\(repCounter.repCount())", forState: UIControlState.Normal)
I'm hoping that this will then update the title of the button with what I return from the repCount function that is called every time the button is pressed.
I've set up the function in a separate Swift file called repCounter and my code for the repCount function is as follows:
var repArray = [1,2,3,4,5]
var repArrayIndex: Int = 0
func repCount () -> String {
if repArrayIndex < repArray.count {
while repArrayIndex < repArray.count {
return "\(repArray[repArrayIndex])"
break
}
repArrayIndex++
} else {
return "\(repArray[0])"
}
}
What I'd like this to do is to cycle through the array every time it is called and once it's got to the end of the array to start cycling from the beginning of the array again.
Thanks in advance for any help!
I'm not at a computer where I can pull up XCode to test it, but I think the version below will do what you want. It isn't the most elegant code, but it is very straightforward. You have to do all the index juggling before the return statement since once the code hits a return, nothing following it will be executed.
I added some code to reset the index once it reaches the end of the array.
var repArray = [1,2,3,4,5]
var repArrayIndex: Int = 0
func repCount () -> String {
while repArrayIndex < repArray.count {
var curIndex = repArrayIndex
repArrayIndex = repArrayIndex + 1;
if repArrayIndex >= repArray.count {
repArrayIndex = 0
}
return "\(repArray[curIndex])"
}
return "\(repArray[0])"
}
Another option to getting this count, without iterating, is to do
// From Swift 1.2
func repCount () -> String {
return count(repArray)
}
// Before Swift 1.2
func repCount () -> String {
return countElements(repArray)
}
If you insist on iterating there are multiple options, see Iterating over an array, one which could be:
var count = 0
for rep in repArray {
count++
}
Or you could for the round trip iteration provided in the other answer, but why do it the hard way, when you don't need to? Or is there something you haven't told us?

Resources