BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: Checkpoint 6: Changing Gears

Forums > 100 Days of SwiftUI

Enjoying the updated videos!

For Checkpoint 6 Paul challenges us to write a Car struct. Here's my attempt. Try it on your own before looking at my solution.

struct Car {
    let model: String     // constant! probably won't change
    let seatCount: Int    // same!
    private(set) var currentGear = 1  // only change via an internal method.

    enum GearDirection {
        case up, down, neutral
    }

    public mutating func changeGear(_ direction: GearDirection) {
        switch direction {
        case .down : currentGear -= 1
            if currentGear < 1 {currentGear = 1}
        case .up :   currentGear += 1
            if currentGear > 10 { currentGear = 10 }
        case .neutral:
            currentGear = 1  // arbitrary selection
        }
        print("The \(model) is in gear: \(currentGear)")
    }
}

// Test Code
var prefect = Car(model: "Ford Prefect", seatCount: 4, currentGear: 3)
prefect.changeGear(.up)
prefect.changeGear(.neutral) // Jump right to Gear 1.
prefect.changeGear(.down)  // try this line a couple of times. try to get below zero.

I tried to add validation code as a property observer. In the willSet clause you get a free variable newValue. I checked to see if newValue was valid ( 1 to 10). If it was outside the range, I wasn't sure how to change it. That must come in a later lesson?

BONUS QUESTION: What does the playground compiler do if you add another option to the GearDirection enum? Try adding reverse to the enum. What does the compiler do? See Enumerations for more details.

Don't forget your towel!

2      

I tried to add validation code as a property observer. In the willSet clause you get a free variable newValue. I checked to see if newValue was valid ( 1 to 10). If it was outside the range, I wasn't sure how to change it. That must come in a later lesson?

You would do it in didSet.

    private(set) var currentGear = 1 {
        didSet {
            currentGear = min(max(currentGear, 1), 10)
        }
    }

2      

Here is my take on solving this:

struct Car {
    let model: String // Once a car is made, the model doesn't change
    let numberOfSeats: Int // Same with the number of seats
    private(set) var currentGear = 1 // New instance will start in 1st gear

    mutating func shiftUp() {
        if currentGear + 1 > 10 { // check if shifting will exceed the top gear
            print("You are already in the top gear!")
        } else {
            currentGear += 1
        }
    }

    mutating func shiftDown() {
        if currentGear - 1 < 1 { // check if shifting will exceed the bottom gear
            print("You are alredy in the lowest gear!")
        } else {
            currentGear -= 1
        }
    }
}

5      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

I came up with this solution. Not sure if this is an overkill to solve it like this. My goal was to minimize effort on code change when a cars would get more, less or other gears.

enum Gear: Int, CaseIterable {
    case reverse = 0
    case parking = 1
    case first = 2
    case second = 3
    case third = 4
    case fourth = 5
    case fivth = 6
    case sixth = 7
}

struct Car {
    let model: String
    let seats: Int
    private(set) var currentGear: Gear = Gear.parking

    func toString() -> String {
        "This model \"\(model)\" has \(seats) seats and is currently in \(currentGear) gear!"
    }

    mutating func gearUp() {
        if currentGear.rawValue < Gear.sixth.rawValue {
            currentGear = next()
        }
    }

    mutating func gearDown() {
        if currentGear.rawValue > Gear.reverse.rawValue {
            currentGear = previous()
        }
    }

    private func previous() -> Gear {
        let all = Gear.allCases
        let idx = all.firstIndex(of: currentGear)!
        let previous = all.index(before: idx)
        return Gear(rawValue: previous) ?? currentGear
    }

    private func next() -> Gear {
        let all = Gear.allCases
        let idx = all.firstIndex(of: currentGear)!
        let next = all.index(after: idx)
        return Gear(rawValue: next) ?? currentGear
    }
}

var bmw = Car(model: "BMW 318i", seats: 4)
print(bmw.toString())
bmw.gearUp()
bmw.gearUp()
bmw.gearDown()
print(bmw.toString())

   

Daniel posts a clever implementation and states:

Not sure if this is an overkill to solve it like this. My goal was to minimize effort on code change when a cars would get more, less or other gears.

Well done! Indeed, this is overkill. But only in the sense that this was a simple homework exercise! On the otherhand, your solution shows a great understanding of the 'keep it simple' principles and a forward looking vision of code reuse.

These are hard skills to teach, and you're demonstrating them early. Nice!

Keep posting, it will be nice to follow your progress. Also, since you're demonstrating advanced techniques, please jump in and answer other's questions! This will help in a few ways.

First, and foremost, if you are trying to solve other coder's SwiftUI problems, you'll gain a better understanding of the concepts which will help you solidify your understanding.

Second, this is a site dedicated to helping folks all over the world build their skills. This site thrives on contributions from all sorts. Please contribute.

Third, it's great fun!

   

Looking at the solutions above, worried my approach might be overly simplistic but thought I'd share just in case. This is for a manual transmission. What do you think?

struct BMW {

    // Properties
    //Both the model and seat number properties will not change, hence the "let" constants
    let model: String = "M4"
    let noOfSeats: Int = 4

    // currentSpeed variable added to determine gear shifts since assignment said: "it's traveling around," implying at minimum a gear of 1 as the starting point
    var currentSpeed: Int = 10
    // "private" to prevent bypass of currentSpeed method and "set" to allow outside reading access of currentGear.
    private(set) var currentGear: Int

    //initializing since I want to modify the currentSpeed and currentGear properties
    init(model: String, noOfSeats:Int, currentSpeed: Int) {
    switch currentSpeed {
            case 0...10:
                currentGear = 1
            case 11...20:
                currentGear = 2
            case 21...30:
                currentGear = 3
            case 31...40:
                currentGear = 4
            case 41...50:
                currentGear = 5
            default:
                currentGear = 6
            }
        }
    }
//Testing one instance 
var bmwM4 = BMW(model: "M4", noOfSeats: 4, currentSpeed: 56)
print(bmwM4.currentGear)

   

Mario is worried:

worried my approach might be overly simplistic. This is for a manual transmission. What do you think?

You get full points for meeting the requirements for this challenge! Mind you, I would not drive a car using your software!!

The goal isn't to implement a Tesla-level transmission solution. You just needed to write a Car struct. Well done. It's great that you're thinking ahead and pondering your designs. You'll need these skills in later challenges.

Keep coding! And ask more questions!

1      

Well, after running through Day 9 three times and still feeling like I only took in about 10% of it, I pushed on into Days 10 and 11 and finally made it to Checkpoint 6. I'd love some feedback as to whether I've covered all of what was required here. In any case, it was good fun and a welcome break from those pesky closures.

struct Car {
    let owner: String
    let model: String
    let numSeats: Int
    static let maxGear = 10
    var currentGear = 1
    let gearWords = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "...er, tenth"]

    mutating func changeGear(currentGear: Int, shiftNum: Int){
        let newGear = currentGear + shiftNum
        if newGear > 0 && newGear <= Car.maxGear {
            print("Driving \(owner)'s \(numSeats)-seater \(model) and shifting into \(gearWords[newGear-1]) gear!")
            self.currentGear = newGear
                } else {
            print("Take it easy - you're out of gears!")
                }
    }
}

// Buy my car...
var myCar = Car(owner: "Sam", model: "Mustang", numSeats: 3)

// Take her for a spin...
for i in 1...Car.maxGear {
    myCar.changeGear(currentGear: i, shiftNum: 1)
}

   

Ben completed Day 10 and 11 and asks for feedback:

i'd love some feedback as to whether I've covered all of what was required here.

Nice! Who doesn't want to take a ride in a 'stang?

Don't worry so much about trying to cover 'all of what was required'. You're getting a lot of new info. And your code shows you are practicing. That's the important part. Practice writing harsh, apprentice level code. Get the code to compile and work. Working code = success!

Next, teach yourself to review other people's code. Try the Rubber Duck 🐤 technique to explain SwiftUI code to yourself. As you progress, you'll unlearn some techniques and get yourself on track to write fluid, Swifty code.

Feedback

    static let maxGear = 10  // <-- You coded a 'magic number' here.
    // Now you must keep the gearWords array synced with the maxGear variable. 
    // Program managers call this 'maintenance nightmare'
    let gearWords = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "...er, tenth"]

This is a snip from your code. See how you commanded this struct to have a maximum of 10 gears? Then you defined text labels for each of the gear. You're on Day 11? This is exactly the code you should be writing! ✔︎

However, this might not make the grade in a team environment. Program managers call this a maintenance nightmare.

Why? Because you now have the distinct possibility that the hard coded maxGear variable may not match the number of gears in your gearWords array! "That's unpossible!", you might say. Yet, it's these types of bugs that drive you insane.

So, instead, what might you do?

Computed Properties

Take a look at @twoStraws' article on computed properties. See -> Computed Properties

In your case, you have a gearWords array with some number of gears. It's quite possible that this number will change right before your company ships the application to thousands of users! Why not let your Car struct figure out how many gears your car has? This is where computed properties shine.

    // Definitive gears available in your Car.
    let gearWords = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "...er, tenth"]
    var maxGear: Int {
        gearWords.count  // <-- Allow your struct to calculate this. It will never be out of sync with your gearWords array.
    }

Keep coding!

   

Thanks so much for the feedback, @Obelix!

That's great advice about the computed property for maxGear. My plan is to revisit each checkpoint at the end of the consolidation points to see where I can trim the fat and also apply any additional knowledge retrospectively.

1      

Hello! Here is my code for this challenge. I really like the solution I came up with, as it uses only one method. However, any opinions on how to make it more simplistic and elegant are welcome. I'm sure there's room for improvement. Thoughts?

import Cocoa

struct Car {
    let model: String
    let numberOfSeats: Int
    private(set) var currentGear: Int = 10 {
        willSet {
            print("Gear \(currentGear) is now changing to \(newValue)")
        }
    }

    enum GearDirection {
        case up, down
    }

    mutating func changeGears(withDirection gearDirection: GearDirection) {
        if (1...10).contains(currentGear) {
            if gearDirection == .up && currentGear + 1 <= 10 {
                currentGear += 1
            } else if gearDirection == .up {
                print("You cannot go above the highest gear!")
            }

            if gearDirection == .down && currentGear - 1 >= 1 {
                currentGear -= 1
            } else if gearDirection == .down {
                print("You cannot go below the lowest gear!")
            }
        } else {
            print("That is not a valid gear!")
        }
    }
}

var car = Car(model: "Mazda CX-30", numberOfSeats: 5, currentGear: 2)
car.changeGears(withDirection: .up)
car.currentGear

This would print, "Gear 2 is now changing to 3" at the bottom of the screen.

Thanks! :)

   

Hi! I'm the kind of dev that only does what is required (a lazy one that is). The best code is the one that wasn't written at all.

Here is my take at checkpoint 6. Thoughts?

import Cocoa

struct Car {   
    enum shift {
        case up, down
    }
    let model: String
    let seats: Int
    private(set) var gear: Int = 10

    mutating func changeGear(shift: shift) -> Bool {
        var tmpGear = gear

        tmpGear += (shift == .up ? 1:-1) 
        if (tmpGear >= 1 && tmpGear <= 10) {
            gear = tmpGear
            print("\(shift == .up ? "Upshifted":"Downshifted") to gear \(gear).")
            return true
        }
        print("Couldn't \(shift == .up ? "upshift":"downshift") more, already in gear \(gear).")
        return false
    }
}

   

Alphonso shares clever code and asks for feedback!

// --- Your code ---
if (tmpGear >= 1 && tmpGear <= 10) {
            gear = tmpGear
            // ... snip ...
        }

// --- Is this clearer? More Swifty? ---
if (1...10).contains(tmpGear) {  // <-- You might see this syntax to verify values in a range
            gear = tmpGear
            // ... snip ...
        }

Keep coding!

1      

I love that there are so many ways to tackle a problem with code :-)

Here is my implementation:

struct Car {
    let gears = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    let model: String
    let seats: Int

    func carDetails() {
        print("This car is a \(model) with \(seats) seats")
    }

    mutating func changeGear(gear: Int) {
        if gear < gears[0] || gear > gears[9] {
            print("This is an invalid gear")
        } else {
            print("You changed into gear \(gear)")
        }
    }
}

Then I can make the calls below :

var modely = Car(model: "Model Y", seats: 5)
modely.carDetails()
modely.changeGear(gear: 0)

and receive the following feedback:

This car is a Model Y with 5 seats
This is an invalid gear

   

Stuart joins in sharing his take:

I love that there are so many ways to tackle a problem with code :-)

Observations


One observation I have for your solution... what gear am I currently in?
If I create a new car object, model3, how can I ask model3 to tell me what gear I'm currently in?

   let model3 = Car( type: "Model 3")  // <-- Create new car object
   let myCurrentGear = model3.??? // <-- How do I get current gear?

Also, this code looks a lot like Java, or C++

    if gear < gears[0] || gear > gears[9] {
         // ... snip ...
    }

You might look through other answers here for a more Swifty approach.

In particular, Swift provides some convenience methods for arrays. Here's a more Swifty way to get the first and last element of an array.

    if gear < gears.first || gear > gears.last {  // <-- get the first and the last elements of an array!
         // ... snip ...
    }

Keep Coding!

1      

Thank you, Obelix. I see you are a very active member on the forum. I appreciate all the time and effort you put in to help us!

2      

hi all.

i thought id share my code and wouldnt mind some feedback please. here in the uk i'm used to gears where i choose the number rather than shift up or down. so thats what i went with! have i taken the task too simply?

thanks

struct Car {

    let model: String
    let numberOfSeat: Int
    private(set) var gear = 0 {
        didSet { print("Your \(model) car gear has changed from \(oldValue) to \(gear)")}
    }

    mutating func changeGear (newGear:Int) {
        if newGear >= 0 && newGear <= 10 {
            self.gear = newGear
        } else {
            print("This gear is out of bounds! Choose between 0 (neutral) and 10")
            return
        }
    }
}

var myCar = Car(model: "Audi Q3", numberOfSeat: 5)
myCar.changeGear(newGear: 6)

   

Hi everyone,

Here is my solution to Checkpoint 6 and I welcome any feeback you may please have for me. Thanks in advance! :)

/// Catches gears that are not found - anything below 1 or above 10
enum GearError: Error {
    case gearNotFound
}

struct Car {
    let model: String
    let numberOfSeats: Int
    var currentGear: Int

    init(model: String, numberofSeats: Int, currentGear: Int) {
        self.model = model
        self.numberOfSeats = numberofSeats
        self.currentGear = currentGear
    }

    /// Takes input of newGear, throws error if necessary, returns newGear
    mutating func gearChange(newGear: Int) throws -> Int {
        ///  Checks if gear is above or below max and throws error if that is so.
        if newGear < 1 || newGear > 10 {
            throw GearError.gearNotFound
        }

        /// Checks if newGear is above or below the currentGear.
         if newGear > currentGear {
            print("You up shifted your vehicle's gear!")
         } else if newGear < currentGear {
            print("You down shifted your vehicle's gear!")
         } else {
            print("You are going the same speed")
         }

        print("Your vehicle is in \(newGear)th gear!")
        /// returns the newGear
        return newGear
    }

}

var car = Car(model: "Outlander", numberofSeats: 7, currentGear: 1)
var shiftGears = 10

/// do catch block to catch errors.
do {
    _ = try car.gearChange(newGear: shiftGears)
} catch GearError.gearNotFound {
    print("Gear not found!")
}

   

Coming from germany where I learned driving with a "manual" gear shift back in the days I also used an approach that lets you type the value instead going just "up", "down" etc. Going from 10 to 1 immediately might feel a little strange though ;-)

What do you think?

import UIKit

struct Car {
    let carModel = "Mercedes"
    let carSeats = 5
    private var currentGear = 1

    mutating func gearChange(newGear: Int) {
         if currentGear + newGear <= 10 && currentGear + newGear >= 1 {
         currentGear += newGear
             print("""
                    Model: \(carModel)
                    Seats: \(carSeats)
                    Gear: \(currentGear)
                    """)
         }
         else {
             print("This doesn't work")
         }
     }
}
var car = Car()
car.gearChange(newGear: 9) 

This will result in

Model: Mercedes Seats: 5 Gear: 10

   

@tomn  

Here's my attempt. Any feedback welcome. I saw that some people used private(set) and enum. I didn't think of those and went another way with a bunch of if statements. Thanks for everything!

struct Car {
    let model: String
    let numberOfSeats: Int
    private var currentGear: Int

    init (model: String, seats: Int) {
        self.model = model
        self.numberOfSeats = seats
        self.currentGear = 1
    }

    mutating func changeGear(changeUp: Bool) {
        if currentGear == 1 && changeUp == false {
            print("Cannot shift down anymore. Already at \(currentGear).")
        } else if currentGear == 5 && changeUp {
            print("Cannot shift up anymore. Already at \(currentGear).")
        } else if changeUp {
            currentGear += 1
            print("\(model) shifted up to \(currentGear)")
        } else {
            currentGear -= 1
            print("\(model) shifted down to \(currentGear)")
        }
    }
}
var bmw = Car(model: "BMW X7", seats: 7)
bmw.changeGear(changeUp: true)

   

Here is my solution to checkpoint-6. If any suggestions or feedback to give on this , you are welcomed ! Happy coding !

import Cocoa

struct Car {
    let model : String
    let noOfSeats : Int
    private(set) var currentGear : Int {
        willSet {
            if self.currentGear == -1 {
                print("car will be in nuetral gear")
            }
            print("changing the gear number from \(currentGear) to \(newValue)")
        }
    }
    let maxGearNo = 6
    let nuetralGearNo = 0
    let reverseGearNo = -1

    enum GearChange {
            case Up, Down, Reverse , neutral
    }

   mutating func changeGear(Action : GearChange) {
       if Action == GearChange.Up {
            if currentGear == maxGearNo{
                print("You are already on maximum gear No")
            } else {
                self.currentGear += 1
            }
       } else if Action == GearChange.Down {
           if currentGear == -1 {
               print("You are on minimum gear No")
           } else {
               self.currentGear -= 1
           }
       } else if Action == GearChange.Reverse {
           self.currentGear = reverseGearNo
       } else if Action == GearChange.neutral {
           self.currentGear = nuetralGearNo
       } else {
           print("Please change the gear properly")
       }
    }
}

var c = Car (model: "3gx2v", noOfSeats: 7, currentGear: 6)

c.changeGear(Action: .Reverse)

   

Helo everybody

This is my first interaction here

Can you check my solution for checkpoint 6 :

struct Car {
    let carmodel = "Citroen"
    let numberOfSeats = 5
    var currentGear = 5
    }
struct ShiftGear {
    var gear = 5 {
        didSet {
            print("Gear is now \(gear)")
        }
    }
}
var citroen = Car(currentGear: 5)
print(citroen)
var shiftGear = ShiftGear()
shiftGear.gear += 0
shiftGear.gear -= 1
shiftGear.gear -= 1
shiftGear.gear -= 1
shiftGear.gear -= 1

Car(carmodel: "Citroen", numberOfSeats: 5, currentGear: 5)

Gear is now 5

Gear is now 4

Gear is now 3

Gear is now 2

Gear is now 1

   

@deniz  

Hello good people!

This is my code, but I'm not 100% sure if I did it correct (I always have that feeling after completing a challenge and especially if I see that Paul's solution is different ). Any thoughts/feedback?

import Cocoa

struct Car {
    let model: String
    let seats: Int
    var currentGear = 0

    mutating func changeGears(gear: String) {
        if currentGear < 10 && gear == "up"{
                currentGear += 1
                print ("You are driving a \(model) with \(seats) seats and changing to gear \(currentGear).")
            }
        else if currentGear > 0 && gear == "down" {
                currentGear -= 1
                print ("You are driving a \(model) with \(seats) seats and changing to gear \(currentGear).")
            }
        else {
                print ("Invalid gear.")
        }
        }
}

var change = Car(model: "Toyota", seats: 6)

//Checking if the code is working//
change.changeGears(gear: "up")
change.changeGears(gear: "up")

change.changeGears(gear: "down")
change.changeGears(gear: "down")
change.changeGears(gear: "down")
change.changeGears(gear: "down")

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.