Welcome, Guest User :: Click here to login

Logo 67443

Lab 3: RailsCards

Due Date: September 24

Objectives

  • Give students more practice working with models
  • Give students more practice working with multiple controllers
  • Give students more practice dealing with closures and optionals
  • Teach students to incorporate navigation bar to app

README.md

RailsCards


There is a Flashcard App for Ruby on Rails developers that we will replicate in a similar fashion for this lab.

Ours is very similar, but there is plenty of room for creative freedom. Here is a look at what the final app might look like:

There are two screens: one to show the command, and the other to show the definition.

This lab can be done in Xcode or using the Playgrounds app on the iPad. I will be presuming Xcode here, but both would work.

Models

  1. Create a new Single View App called RailsCards. We will be using SwiftUI for this lab, so make sure you set your "User Interface" option accordingly.

  2. If you want an AppIcon for this app, you can get one here or create one here.

  3. Create three folders -- Models, Controllers, Views -- like we did last week to organize our work.

  4. Within the project create a new, blank Swift file called Flashcard.swift and create a simple model named Flashcard within this file to represent the essence of a flashcard. In this case, that means the model has the following:

    • A variable called command of type String (this is the front of the flashcard with the rails command).
    • A variable called definition also of type String (this is the back of the flashcard with that the rails command does).
    • A constructor method that takes a command String and a definition String as arguments and sets the appropriate variables.

    In this case, use a struct and not a class for this model since this model has only state and not behavior. (As discussed in class, this is not the primary distinction between structs and classes in Swift, but that is true in other languages and we will observe this practice here.)

  5. Create another new file called Deck.swift that will help represent a series of flashcards. Within this file, we fill define a new class called Deck. This will be a class since it will have both state and behavior.

  6. Create a variable called cards in Deck.swift, which will be an array of Flashcard objects (using the struct we created earlier).

  7. In the init() method, set the body of the method to the following code:

    init() {
      let cardData = [
        "rails generate model ModelName": "Creates a model with the specified model_name",
        "rails generate migration MigrationName": "Creates a migration with the specified migration_name",
        "rails generate controller ControllerName": "Creates a controller with the specified controller_name",
        "rails generate scaffold ModelName": "Provides shortcut for creating your controller, model and view files in one step",
        "rails destroy scaffold ModelName": "Destroys the created controller, model and view files that were generated for the given Model",
        "rails server": "Starts ruby server at http://localhost:3000",
        "rails console": "Opens the rails console for the current RAILS_ENV",
        "rake test:units": "Runs all unit tests for the application",
        "rake -T": "Lists all available rake tasks",
        "rake db:create": "Creates the database defined in config/database.yml for the current RAILS_ENV",
        "rake db:migrate": "Migrates the database through scripts in the db/migrate directory",
        "rake db:drop": "Drops the database for the current RAILS_ENV",
        "rake db:reset": "Drops and recreates the database from db/schema.rb for the current environment",
        "rake db:rollback": "Runs the down method from the latest migration",
        "rake doc:app": "Builds the RDoc HTML files",
        "gem list": "lists the gems that this rails application depends on",
        "gem server": "Presents a web page at http://localhost:8808/ with info about installed gems",
        "bundle install": "Installs all required gems for this application",
        "rake log:clear": "Truncates all *.log files in log/ to zero bytes",
        "rake routes": "Prints out all the defined routes in match order with names",
        "rake tmp:clear": "Clears session, cache and socket files from tmp/",
        "rake test:benchmark": "Benchmarks your application"
      ]
      
      // Write a simple way to loop through the dictionary of cards, create a `Flashcard` object
      // for each card, and add that object to the `cards` array we created earlier
      
    }
    
  8. Replace the comment above with the appropriate code. After that, add a method that draws a random card (see below):

    func drawRandomCard() -> Flashcard {
    	return cards[Int(arc4random_uniform(UInt32(cards.count)))]
    }
    

Controller

  1. Now create a new Swift file called ViewController.swift. Create a new class called ViewController and make sure it inherits from ObservableObject like we did in lab 2.

  2. Create two variables in our ViewController:

    let deck = Deck()				// create an instance of `Deck`
    @Published var flashcard: Flashcard		// holds a single flashcard object from the deck
    

    Remember, because this class is an ObservableObject it can 'publish' information to any other object that might be observing it and that object will update as soon as the published data has changed. Last week, our published data were just strings, but this time we are publishing an object of type Flashcard. That might seem different, but it's really not.

  3. Write two methods for our controller:

    1. An init() method that sets self.flashcard to a random Flashcard from the deck.
    2. A method to update the self.flashcard variable to a different random card.

    Both of these methods can utilize the drawRandomCard() method in the Deck class.

Views

  1. First thing we are going to do is move ContentView.swift and RailsCardsApp.swift into the Views folder. Second, we are going change the name of ContentView to a more meaningful name like CardView. After we do that, we also need to go into the RailsCardsApp file and change ContentView to CardView.

  2. Let's start creating the view in the CardView.swift. Create an instance of your ViewController in the CardView. Make sure this is an @ObservedObject like we did last week.

  3. Change Text("Hello, World!") to display the rails command from the ViewController.

    This is what your canvas should look like:

  4. Let's create the definition page now. Create a new SwiftUI File called DefinitionView.swift.

  5. Put the following code under struct DefinitionView: View:

    let viewController: ViewController
    
  6. Replace the code following struct DefinitionView_Previews: PreviewProvider with:

    struct DefinitionView_Previews: PreviewProvider {
        static var previews: some View {
          DefinitionView(viewController: ViewController())
        }
    }
    
  7. Now replace, Text("Hello, World!") with the flashcard definition from the ViewController.

  8. Now let's link the two views. In CardView.swift, wrap your Text element in a NavigationView.

  9. Then, wrap your Text element again in a NavigationLink with DefinitionView as your destination. Pass in the instance of your ViewController to your DefinitionView.

  10. Now try out your app! It should be mostly functional. However, you might notice that you are getting the same flashcard every time. This is because we never update our flashcard with a new, random card. Let's do that in our CardView.swift.

  11. Add .onAppear() { } outside your NavigationLink (but inside your NavigationView) to call your ViewController method that updates the flashcard with a new, random card everytime the command screen comes into the foreground. (To do this, call the method within in the curly braces of the .onAppear() block.)

  12. Now the app should be fully functional, but it is not as appealing as it could be. Let's refactor the views to improve the appearance of our user interface.

Making Cards

  1. Since our app is called RailsCards let's clean up our views using cards.

  2. Do this by wrapping your NavigationLink in a ZStack, setting the width to 350.0 and the height to 200.0.

  3. Add the following code right before the .onAppear block to create a card outline. Make sure you understand what each statement is doing.

    .overlay(
    	RoundedRectangle(cornerRadius: 10.0)
    		.stroke(Color.gray)
    )
    
  4. Create a similar card in the DefinitionView. Make sure to add some padding inside the card and to center the text.