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.
Getting Started
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 lib/motion-launchpad.rb
file:
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.
Wrapping up
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
You can check out the complete Motion::Launchpad::Schedule
class here, the Motion::Launchpad::Event
class here, and the module showing some cooler meta programming methods in ruby here.
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!
Up Next
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.