For our first real exercise in RubyMotion, there is no better place to start than writing our very first gem. We'll be creating a gem called 'motion-launchpad' that I'll open source and release for others to use, providing a simple schedule interface based on launch counts, for performing tasks in your app.
If you want to just view the gem directly and skip this tutorial (shame on you) you're welcome to check out the project on github.
The first thing we are going to need to do is create a git repo and the basic gem structure for a RM project. That means a folder with the following structure:
- app - Gemfile - lib/ - motion/ - launchpad.rb - version.rb - motion-launchpad.rb - motion-launchpad.gemspec - spec/
A good way to get going is by installing the excellent gem release command and using
gem bootstrap [gem_name] as a starting point. This applies to any Ruby gem and is not RM specific.
We'll start by creating the contents of our
unless defined?(Motion::Project::Config) raise "This file must be required within a RubyMotion project Rakefile." end lib_dir_path = File.dirname(File.expand_path(__FILE__)) Motion::Project::App.setup do |app| gem_files = Dir.glob(File.join(lib_dir_path, "motion/**/*.rb")) app.files.unshift(gem_files).flatten! end
We'll also want to ensure that the project
Rakefile is updated for a RM project. At this point in our progress you can check out the first commit in our repo.
Configuring RM App Delegate
For our test suite to run, we'll need a UIApplication delegate that just returns true and doesn't worry about anything else, allowing our unit tests to run.
We're not including anything in the
app directory as the contents of our gem, they're strictly there for the test suite to have a valid application to run against.
class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) return true if RUBYMOTION_ENV == 'test' end end
Checking our Progress
At this point, you should be able to just run
rake from the Terminal and open the simulator with a black screen. We have not created a window, rootViewController or anything else in our delegate and don't need to.
You can play in REPL and see that we have our modules and classes defined and that they are accessible.
Motion::Launchpad.new => #<Motion::Launchpad:0x8e8a050>
Write our First Test
Now that we have a basic structure and can use REPL, we're ready to write our first test and add our functionality.
describe Motion::Launchpad do it "should return an instance" do Motion::Launchpad.configure.should.be.instance_of Motion::Launchpad end it "should return the same instance twice" do instance = Motion::Launchpad.configure instance.should.be.equal Motion::Launchpad.configure end end
And the code that makes this test pass:
module Motion class Launchpad class << self attr_accessor :instance def configure(&block) self.instance = new if instance.nil? instance end end end end
You can read more about the rspec-ish syntax of MacBacon here.
For speed sake, we're just going to reference the project on github for the finished gem.
Our finished product provides a nice and clean DSL for configuring events, such as:
class AppDelegate def application(app, didFinishLaunchingWithOptions: options) setup_schedule Motion::Launchpad.run! end private def setup_schedule Motion::Launchpad.configure do |config| config.on :every do # maybe track app launch with analytics? end config.on 1 do # first launch, maybe show user a tutorial? end end end # You can call `configure` multiple times Motion::Launchpad.configure do |config| config.on 5 do # ask user to rate your app? Hate doing that, but best I could come up with end end end
Releasing the gem is as easy as running
gem release --tag from the directory, which I've already done. Enjoy and send a pull request if you have feedback, questions or improvements!
In the next article we'll be taking a into meta-programming in RubyMotion and also be taking a look into the new
AVSpeechSynthesizer class which debuted in iOS 7.