Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trial
George Cremer
6,580 PointsImproving Fun Facts App - Preventing repetition: Unclear error, please help!
I recently finished the the Fun Facts App and thought i'd try my hand at polishing it. Namely I wanted to ensure that both the colours and facts don't repeat themselves until all of the colours / facts have been iterated through. I've managed to achieve this for the factsModel but for some reason the same logic doesn't seem to work for the colorsModel?! The problem seems to be in the return statement:
return colors.removeAtIndex(randomNumber)
But i'm really not sure what is wrong with it. Perhaps something to do with the array storing UIColor instead of a string?
Any ideas? It's got me stumped!
Here's the FactModel which works a treat:
import GameKit
class FactModel {
var storage = [String]()
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostritches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver", // Snip
]
func getRandomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(facts.count) // Generates random number
if facts.count == 0 { // checks if the facts array has been exhausted
facts = storage // if True, the Facts array is repopulated with the contents of the Storage array
storage = [] // the storage array is then reset to empty
return getRandomFact()} // the getRandomFact func is then called again.
if facts.count > 0 { // checks if the facts array still has items in the array
storage.append(facts[randomNumber]) // if True, the item selected from the array is copied to the storage array
}
return facts.removeAtIndex(randomNumber) // returns the selected item from the color array before deleting it from the color aray.
}
}
And here's the ColorModel which doesnt work :(
import UIKit
import GameKit
class ColorModel {
var storage = [UIColor]() // empty array to store colors after they are used.
var colors = [
UIColor(red: 90/255.0, green: 187/255.0, blue: 181/255.0, alpha: 1.0), // teal color
UIColor(red: 222/255.0, green: 171/255.0, blue: 66/255.0, alpha: 1.0), // yellow color
UIColor(red: 223/255.0, green: 86/255.0, blue: 94/255.0, alpha: 1.0), // red color
UIColor(red: 239/255.0, green: 130/255.0, blue: 100/255.0, alpha: 1.0), // orange color
UIColor(red: 77/255.0, green: 75/255.0, blue: 82/255.0, alpha: 1.0), // dark color
UIColor(red:105/255.0, green: 94/255.0, blue: 133/255.0, alpha: 1.0), // purple color
UIColor(red: 85/255.0, green: 176/255.0, blue: 112/255.0, alpha: 1.0), // green color
]
func getRandomColor() -> UIColor {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(colors.count) // Generates random number
if colors.count == 0 { // checks if the color array has been exhausted,
colors = storage // if True, the color array is repopulated with the contents of the Storage array
storage = [] // the storage array is then reset to empty
return getRandomColor() // the getRandomColor func is then called again.
}
if colors.count > 0 { // checks if the color array still has items in the array
storage.append(colors[randomNumber]) // if True, the item selected from the array is copied to the storage array
}
return colors.removeAtIndex(randomNumber) // returns the selected item from the color array before deleting it from the color aray.
}
}
Would you really appreciate any insights, thanks in advance!
3 Answers
rh12
4,407 PointsHey George, sorry for the delayed response.
I created a simple iOS app to run your code (basically you tap a button and it changes the view's colour based on your get randomColor function). I also incorporated your print statements. While running this version of your code, the previousColor variable is being updated appropriately for me.
Would it be possible for you to post your calling code as well? That is the code which is instantiating and calling your ColorModel.
Your getRandomColor function seems fine. This may be related to how/where your creating your ColorModel.
Here is my calling code:
class ViewController: UIViewController {
let colourModel = ColorModel()
@IBOutlet weak var colourView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func changeColour(sender: AnyObject) {
colourView.backgroundColor = colourModel.getRandomColor()
}
}
The ColorModel is instantiated once, when the ViewController is created. The changeColour function is being called when the button is tapped.
rh12
4,407 PointsI tried your code in a playground and I cannot see anything wrong with your solution. It appears to be working as expected. I get a different fact/colour until the array is exhausted and then it starts all over again.
Are you getting an actual error message or is the behaviour not as expected? If the behaviour is not as expected can you please provide some more details on exactly what you are seeing?
Below is a modified solution that utilizes structs to keep track of whether or not a fact/colour has been presented. This also works well in the playground.
protocol Presentable {
var presented: Bool { get }
}
struct Fact: Presentable {
var fact: String
var presented: Bool
}
class FactModel {
var facts = [
Fact(fact: "Ants stretch when they wake up in the morning.", presented: false),
Fact(fact: "Ostritches can run faster than horses.", presented: false),
Fact(fact: "Olympic gold medals are actually made mostly of silver", presented: false)
]
func getRandomFact() -> String {
// Count the presented facts
let presentedCount = facts.filter { $0.presented == true }.count
if presentedCount == facts.count {
// If all facts were presented, reset the presented flag on each fact to false
facts = facts.map({Fact(fact: $0.fact, presented: false)})
// ... and try again
return getRandomFact()
} else {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(facts.count)
if !facts[randomNumber].presented {
facts[randomNumber].presented = true
return facts[randomNumber].fact
} else {
return getRandomFact()
}
}
}
}
struct Color: Presentable {
var color: UIColor
var presented: Bool
}
class ColorModel {
var colors = [
Color(color: UIColor(red: 90/255.0, green: 187/255.0, blue: 181/255.0, alpha: 1.0), presented: false), // teal color
Color(color: UIColor(red: 222/255.0, green: 171/255.0, blue: 66/255.0, alpha: 1.0), presented: false), // yellow color
Color(color: UIColor(red: 223/255.0, green: 86/255.0, blue: 94/255.0, alpha: 1.0), presented: false), // red color
Color(color: UIColor(red: 239/255.0, green: 130/255.0, blue: 100/255.0, alpha: 1.0), presented: false), // orange color
Color(color: UIColor(red: 77/255.0, green: 75/255.0, blue: 82/255.0, alpha: 1.0), presented: false), // dark color
Color(color: UIColor(red:105/255.0, green: 94/255.0, blue: 133/255.0, alpha: 1.0), presented: false), // purple color
Color(color: UIColor(red: 85/255.0, green: 176/255.0, blue: 112/255.0, alpha: 1.0), presented: false) // green color
]
func getRandomColor() -> UIColor {
// Count the presented colors
let presentedCount = colors.filter { $0.presented == true }.count
if presentedCount == colors.count {
// If all colors were presented, reset the presented flag on each fact to false
colors = colors.map({Color(color: $0.color, presented: false)})
// ... and try again
return getRandomColor()
} else {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(colors.count)
if !colors[randomNumber].presented {
colors[randomNumber].presented = true
return colors[randomNumber].color
} else {
return getRandomColor()
}
}
}
}
Btw I think this is a good idea for the Facts App :)
rh12
4,407 PointsBecause of the similarities between the FactModel and ColorModel in my solution, they can be refactored into a single generic class. This would make it reusable for other models as well.
George Cremer
6,580 PointsThanks for your insight Ryan. You're right about it being a behavioural issue as opposed to a compiler error, I wasn't as clear as I should've been. I've realised what the issue is, I had little epiphany after you said that the solution works for you :)
The behviour I wasn't expecting is that every now and then a colour will still repeat. I think this is because when the color variable is reset there is a 1/7 chance that the random color will be the same as the previous color presented.
I'm trying to solve this by creating another check, this time using a UIColor variable instead to store and check against the the previous colour. Unfortunately my current implementation doesn't seem to work.....
Any ideas?
Thanks again for your help, really appreciate it!
import UIKit
import GameKit
import CoreGraphics
class ColorModel {
var storage = [UIColor]() // empty array to store colors after they are used.
var previousColor:UIColor = UIColor(red: 1/255.0, green: 1/255.0, blue: 1/255.0, alpha: 1.0)// init color : Variable holds previous colour
var colors = [
UIColor(red: 90/255.0, green: 187/255.0, blue: 181/255.0, alpha: 1.0), // teal color
UIColor(red: 222/255.0, green: 171/255.0, blue: 66/255.0, alpha: 1.0), // yellow color
UIColor(red: 223/255.0, green: 86/255.0, blue: 94/255.0, alpha: 1.0), // red color
UIColor(red: 239/255.0, green: 130/255.0, blue: 100/255.0, alpha: 1.0), // orange color
UIColor(red: 77/255.0, green: 75/255.0, blue: 82/255.0, alpha: 1.0), // dark color
UIColor(red:105/255.0, green: 94/255.0, blue: 133/255.0, alpha: 1.0), // purple color
UIColor(red: 85/255.0, green: 176/255.0, blue: 112/255.0, alpha: 1.0), // green color
]
func getRandomColor() -> UIColor {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(colors.count) // Generates random number
if previousColor == colors[randomNumber] { // checks if the color is colour is the same as the previous colour presented
return getRandomColor() // if True, the getRandomColor func is then called again - until a unique color is found
}
if colors.count == 0 { // checks if the color array has been exhausted,
colors = storage // if True, the color array is repopulated with the contents of the Storage array
storage = [] // the storage array is then reset to empty
return getRandomColor() // the getRandomColor func is then called again.
}
if colors.count > 0 { // checks if the color array still has items in the array
storage.append(colors[randomNumber]) // if True, the item selected from the array is copied to the storage array
previousColor = colors[randomNumber] // overwrites previous colour variable with the current colour
}
return colors.removeAtIndex(randomNumber) // returns the selected item from the color array before deleting it from the color array.
}
}
rh12
4,407 PointsNo problem! Glad I could help.
With your new solution I think there is a minor issue with the order of your checks. The way it is currently results in a runtime error. Please give the following a try.
func getRandomColor() -> UIColor {
if colors.count == 0 { // checks if the color array has been exhausted,
colors = storage // if True, the color array is repopulated with the contents of the Storage array
storage = [] // the storage array is then reset to empty
return getRandomColor() // the getRandomColor func is then called again.
}
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(colors.count) // Generates random number
if previousColor == colors[randomNumber] { // checks if the color is colour is the same as the previous colour presented
return getRandomColor() // if True, the getRandomColor func is then called again - until a unique color is found
}
if colors.count > 0 { // checks if the color array still has items in the array
storage.append(colors[randomNumber]) // if True, the item selected from the array is copied to the storage array
previousColor = colors[randomNumber] // overwrites previous colour variable with the current colour
}
return colors.removeAtIndex(randomNumber) // returns the selected item from the color array before deleting it from the color array.
}
The difference is checking the colours count first, before doing the previous colour comparison. The runtime error is the result of trying to access an index of an empty colours array before it has been repopulated from your storage variable.
George Cremer
6,580 PointsHmmmm it still isn't working. I've added a few print statements to check whats going on and it seems that although the previous colour variable is being updated at the end of the function, for some reason when the function is called a 2nd time that updated data is lost and the previousColor variable returns to its default value.
Here is a sample of the print statements I'm getting - you'll notice that the previousColor variable remains unchanged at each new call:
1st Call
Previous Colour = UIDeviceRGBColorSpace 0.00392157 0.00392157 0.00392157 1 / New Colour = UIDeviceRGBColorSpace 0.352941 0.733333 0.709804 1 New Stored Colour = UIDeviceRGBColorSpace 0.352941 0.733333 0.709804 1
2nd Call
Previous Colour = UIDeviceRGBColorSpace 0.00392157 0.00392157 0.00392157 1 / New Colour = UIDeviceRGBColorSpace 0.937255 0.509804 0.392157 1 New Stored Colour = UIDeviceRGBColorSpace 0.937255 0.509804 0.392157 1
3rd Call
Previous Colour = UIDeviceRGBColorSpace 0.00392157 0.00392157 0.00392157 1 / New Colour = UIDeviceRGBColorSpace 0.301961 0.294118 0.321569 1
New Stored Colour = UIDeviceRGBColorSpace 0.301961 0.294118 0.321569 1
Here is the updated function, which resolves the runtime issue:
func getRandomColor() -> UIColor {
if colors.count == 0 { // checks if the color array has been exhausted,
colors = storage // if True, the color array is repopulated with the contents of the Storage array
storage = [] // the storage array is then reset to empty
return getRandomColor() // the getRandomColor func is then called again.
}
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(colors.count) // Generates random number
print("Previous Colour = \(previousColor) / New Colour = \(colors[randomNumber])")
if previousColor == colors[randomNumber] { // checks if the color is the same as the previous color presented
return getRandomColor() // if True, the getRandomColor func is then called again - until a unique color is found
}
if colors.count > 0 { // checks if the color array still has items in the array
storage.append(colors[randomNumber]) // if True, the item selected from the array is copied to the storage array
}
previousColor = colors[randomNumber] // overwrites previous colour varaiable with the current colour
print("New Stored Colour = \(previousColor)")
return colors.removeAtIndex(randomNumber) // returns the selected item from the color array before deleting it from the color aray.
}
}
George Cremer
6,580 PointsHey Ryan ( / All ), just wanted to see if you'd had a chance to check out my response above. Would love to get to the bottom of this.
Cheers,
George
George Cremer
6,580 PointsGeorge Cremer
6,580 PointsHey Ryan, you're spot on! I had failed instantiate colourmodel, and was simply trying to call ColourModel(). getRandomColor() inside the @IBAction function....
Thank you so much for your assistance and patience.
Cheers,
George