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

iOS

Improving 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

Hey 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.

Hey 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

I 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 :)

Because 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.

Thanks 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.
    }

}

No 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.

Hmmmm 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.
    }

}

Hey 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