Let's Write a RubyMotion App: Part 2

Final product image
What You'll Be Creating

RubyMotion is a fantastic framework for building performant iOS applications using the Ruby language. In the first part this tutorial, you learned how to set up and implement a RubyMotion application. You worked with Interface Builder to create the application's user interface, implemented a view controller, and learned how to write tests for your application.

In this tutorial, you'll learn about the Model-View-Controller or MVC design pattern and how you can use it to structure your application. You'll also implement a painting view and add a gesture recognizer that allows the user to draw on the screen. When you're done, you'll have a complete, fully-working application.

1. Model-View-Controller

Apple encourages iOS developers to apply the Model-View-Controller design pattern to their applications. This pattern breaks classes into one of three categories, models, views, and controllers.

  • Models contain your application's business logic, the code that determines the rules for managing and interacting with data. Your model is where the core logic for you application lives.
  • Views display information to the user and allow them to interact with the application.
  • Controllers are responsible for tying the models and views together. The iOS SDK uses view controllers, specialized controllers with a little more knowledge of the views than other MVC frameworks.

How does MVC apply to your application? You've already started implementing the PaintingController class, which will connect your models and views together. For the model layer, you'll add two classes:

  • Stroke This class represents a single stroke in the painting.
  • Painting This class represents the entire painting and contains one or more strokes.

For the view layer, you'll create a PaintingView class that is responsible for displaying a Painting object to the user. You'll also add a StrokeGestureRecongizer that captures touch input from the user.

2. Strokes

Let's start with the Stroke model. A stroke will consist of a color and several points representing the stroke. To start, create a file for the Stroke class, app/models/stroke.rb, and another one for its spec, spec/models/stroke.rb.

Next, implement the stroke class skeleton and a constructor.

The Stroke class has two attributes, points, a collection of points, and color, the color of the Stroke object. Next, implement a constructor.

That looks great so far. The constructor accepts two arguments, start_point and color. It sets points to an array of points containing start_point and color to the provided color.

When a user swipes their finger across the screen, you need a way to add points to the Stroke object. Add the add_point method to Stroke.

That was easy. For convenience, add one more method to the Stroke class that returns the start point.

Of course, no model is complete without a set of specs to go along with it.

This should start to feel familiar. You've added four describe blocks that test the initialize, start_point, add_point, and start_point methods. There's also a before block that sets a few instance variables for the specs. Notice the describe block for #initialize has a before block that resets the @stroke object. That's fine. With specs, you don't have to be as concerned with performance as you do with a regular application.

3. Drawing

It's the moment of truth, it's time to make your application draw something. Start by create a file for the PaintingView class at app/views/painting_view.rb. Because we're doing some specialized drawing, the PaintingView class is tricky to test. For the sake of brevity, I'm going to skip the specs for now.

Next, implement the PaintingView class.

Phew, that's a lot code. Let's break it down piece by piece. The PaintingView class extends the UIView class. This allows PaintingView to be added as a subview of PaintingController's view. The PaintingView class has one attribute, stroke, which is an instance of the Stroke model class.

With regards to the MVC pattern, when working with the iOS SDK, it's acceptable for a view to know about a model, but it's not okay for a model to know about a view.

In the PaintingView class, we've overridden UIView's drawRect: method. This method allows you to implement custom drawing code. The first line of this method, super, calls the method on the super class, UIView in this example, with the provided arguments.

In drawRect:, we also check that the stroke attribute isn't nil. This prevents errors if stroke hasn't been set yet. We then fetch the current drawing context by invoking UIGraphicsGetCurrentContext, configure the stroke that we're about to draw, move the drawing context to the start_point of the stroke, and adds lines for each point in the stroke object. Finally, we invoke CGContextStrokePath to stroke the path, drawing it in the view.

Add an outlet to PaintingController for the painting view.

Fire up Interface Builder by running bundle exec rake ib:open and add a UIView object to the PaintingController's view from the Ojbect Library on the right. Set the view's class to PaintingView in the Identity Inspector. Make sure that the painting view is positioned underneath the buttons you added earlier. You can adjust the ordering of the subviews by changing the positions of the view's in the view hierarchy on the left.

Control and drag from the view controller to the PaintingView and select the painting_view outlet from the menu that appears.

Select the painting view and set its background color to 250 red, 250 green, and 250 blue.

Don't forget to add a spec to spec/controllers/painting_controller_spec.rb for the painting_view outlet.

To make sure your drawing code works correctly, add the following code snippet to the PaintingController class and run your application. You can delete this code snippet when you've verified everything is working as expected.

4. Painting

Now that you can draw a stroke, it's time to level up to the entire painting. Let's start with the Painting model. Create a file for the class at app/models/painting.rb and implement the Painting class.

The Painting model is similar to the Stroke class. The constructor initializes strokes to an empty array. When a person touches the screen, the application will start a new stroke by calling start_stroke. Then, as the user drags their finger, it will add points with continue_stroke. Don't forget the specs for the Painting class.

Next, modify the PaintingView class to draw a Painting object instead of a Stroke object.

You've changed the stroke attribute to painting. The drawRect: method now iterates over all of the strokes in the painting and draws each one using draw_stroke, which contains the drawing code you wrote previously.

You also need to update the view controller to contain a Painting model. At the top of the PaintingController class, add attr_reader :painting. As the name implies, the viewDidLoad method of the UIViewController class—the superclass of the PaintingController class—is called when the view controller has finished loading its view. The viewDidLoad method is therefore a good place to create a Painting instance and set the painting attribute of the PaintingView object.

As always, don't forget to add tests for viewDidLoad to spec/controllers/painting_controller_spec.rb.

5. Gesture Recognizers

Your application will be pretty boring unless you allow people to draw on the screen with their fingers. Let's add that piece of functionality now. Create a file for the StrokeGestureRecognizer class along with its spec by running the following commands from the command line.

Next, create the skeleton for the class.

The StrokeGestureRecognizer class extends the UIGestureRecognizer class, which handles touch input. It has a position attribute that the PaintingController class will use to determine the position of the user's finger.

There are four methods you need to implement in the StrokeGestureRecognizer class, touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, and touchesCancelled:withEvent:. The touchesBegan:withEvent: method is called when the user starts touching the screen with their finger. The touchesMoved:withEvent: method is called repeatedly when the user moves their finger and the touchesEnded:withEvent: method is invoked when the user lifts their finger from the screen. Finally, the touchesCancelled:withEvent: method is invoked if the gesture is cancelled by the user.

Your gesture recognizer needs to do two things for each event, update the position attribute and change the state property.

Both the touchesEnded:withEvent: and touchesCancelled:withEvent: methods set the state to UIGestureRecognizerStateEnded. This is because it doesn't matter if the user is interrupted, the drawing should remain untouched.

In order to test the StrokeGestureRecognizer class, you need to be able to create an instance of UITouch. Unfortunately, there's no publicly available API to accomplish this. To make it work, we'll make use of the Facon mocking library.

Add gem 'motion-facon' to your Gemfile and run bundle install. Then, add require "motion-facon" below require "sugarcube-color" in the project's Rakefile.

Next, implement the StrokeGestureRecognizer spec.

extend Facon::SpecHelpers makes several methods available in your specs, including mock. mock is a simple way to create test objects that work exactly the way you want them to. In the before block at the beginning of the specs, you're mocking instances of UITouch with the locationInView: method that returns a predefined point.

Next, add a stroke_gesture_changed method to the PaintingController class. This method will receive an instance of the StrokeGestureRecognizer class whenever the gesture is updated.

When the gesture recognizer's state is UIGestureRecognizerStateBegan, this method starts a new stroke in the Painting object using the StrokeGestureRecognizer's position and selected_color. Otherwise, it continues the current stroke.

Add the specs for this method.

RubyMotion provides several helper methods to simulate user interaction, including drag. Using drag, you can simulate a user's interaction with the screen. The points option allows you to provide an array of points for the drag.

If you were to run the specs now, they would fail. That's because you need to add the gesture recognizer to the storyboard. Launch Interface Builder by running bundle exec rake ib:open. From the Object Library, drag an Object into your scene, and change its class to StrokeGestureRecognizer in the Identity Inspector on the right.

Control and drag from the StrokeGestureRecognizer object to the PaintingController and choose the select_color method from the menu that appears. This will ensure the select_color method is called whenever the gesture recognizer is triggered. Then, control and drag from the PaintingView object to the StrokeGestureRecognizer object and select gestureRecognizer from the menu that appears.

Add a spec for the gesture recognizer to the PaintingController specs in the #painting_view describe block.

That's it. With these changes your application should now allow a person to draw on the screen. Run your application and have fun.

6. Final Touches

There are a few final touches left to add before your application is finished. Because your application is immersive, the status bar is a bit distracting. You can remove it by setting the UIStatusBarHidden and UIViewControllerBasedStatusBarAppearance values in the application's Info.plist. This is easy to do in the RubyMotion setup block inside the project's Rakefile.

The application's icons and launch images are included in the source files of this tutorial. Download the images and copy them to the resources directory of the project. Then, set the application icon in the Rakefile configuration. You may have to clean the build by running bundle exec rake clean:all in order to see the new launch image.

Conclusion

That's it. You now have a complete app that's ready for a million downloads in the App Store. You can view and download the source for this application from GitHub.

Even though your app is finished, there's so much more you could add to it. You can add curves between the lines, more colors, different line widths, saving, undo, and redo, and anything else you can imagine. What will you do to make your app better? Let me know in the comments below.

Tags:

Comments

Related Articles