It’s been over 8 months since our workplace experiment last July, when I got an entire month to buckle down and make an iPhone app. Last Friday, Basecamp landed in the App store. It’s been a long journey of simulator runs, device testing, and app crashes, but I’m convinced that I wouldn’t have been able to ship an iPhone app at all without RubyMotion.
I’ve tried to jump into mobile development a few times over the past few years, and I got stuck every time. I made a Android app in Eclipse, wrapped my head around Lua and Corona SDK, and tried to deal with Objective-C, Xcode, and Interface Builder. Not only did I have to throw out my existing toolset and workflow to break into this world, I had to ramp up with new APIs, frameworks, and more.
RubyMotion came at the perfect time: I wanted to make mobile apps, I didn’t have to throw out my existing workflow, and I could still write Ruby! Here’s what I learned about my experience with RubyMotion from the past few months.
Working inside of Xcode and dealing with Interface Builder brought memories back of wiring up Visual Basic controls and suffering through Visual Studio crashes. I left that world years ago, and I don’t want to look back.
Avoiding Xcode means that we had to wire up the interface all in code. Our hybrid web/native approach helped with this, since there isn’t that much native UI to hook up. Luckily, iOS6 added a new API to make this easier: Auto Layout. Using Auto Layout is almost like laying out interface elements in ASCII art. Here’s a simple example from the app, from our home screen:
These are the visual format language strings that describe this layout:
# horizontal "|-10-[switchButton]-10-|" "|-10-[helpButton]-10-|" # vertical "|-15-[switchButton]-10-[helpButton(==switchButton)]-15-|"
The syntax is a little obtuse on first glance, but it’s pretty simple: brackets contain UI elements, pipes are the edges of the screen, dashes denote spacing between elements. You can add in specific numbers for measurements of either spacing or elements, and lock the size of elements to other ones already on the screen. This is a far cry from CSS, but it’s definitely better than manually setting the size and origin for each element. I’m planning to release a gem to help make Auto Layout even easier with RubyMotion soon. Stay tuned!
Respect the style
RubyMotion is Ruby, which is normally succinct, short, and sweet. The iOS APIs stick out like sore thumbs amongst what is normal Ruby code:
camelCase usage is pervasive and
reallyFreakingLongParameterNames can’t be tuned out. The entire Apple docs for iOS have even been lovingly translated to Ruby, which makes it pretty clear that slapping Ruby’s normal style on top of the APIs is not easy.
I have struggled with my style of coding inside of the app from the start: Should I stick with the normal Ruby style of
snake_case, short variable names, and multiple non-named parameters? Or, ditch it in favor of speaking the native tongue of Objective-C?
So far, I’ve ended up with staying in Objective-C style. All of the API calls you make into the various iOS frameworks need to be in that style, and seeing the different opinions of variable and method naming clashing ended up hurting my head. Here’s a pretty typical method, from when the app summons our
StackerController, a custom container controller that handles the “page stacking” animations and transitions:
def stack(controller) stacker = StackerController.alloc.init stacker.rememberScreenshot(self.parentViewController.view) parentViewController.pushViewController(stacker, animated:false) stacker.push(controller, animated:true) end
RubyMotion allows us to use Ruby’s
new to create objects, but usually you’ll see
alloc.init in most Objective-C code, so we’re keeping that. Methods on
StackerController are in camelCase, and if they take more than one parameter they use Objective-C named parameters instead of Ruby-style parameters. This approach is definitely a compromise, but it kept my brain at a constant level of sanity while jumping back and forth between Ruby and Objective-C.
If there’s any one thing you can do after starting a RubyMotion project, it’s immediately drop BubbleWrap into your repo. There’s a lot of great, well documented code inside this gem that has saved me many hours of poring over Apple’s guides and implementation time. Here’s some quick ways we’re using it:
- Firing almost every HTTP request to fetch pages, projects, and attachments
- Listen for and post NSNotificationCenter messages
- Quickly persist data locally without using CoreData or other custom wrappers
I’ve learned an immense amount by reading through BubbleWrap’s code, and I’d recommend any iOS developer to do the same. I hope RubyMotion spawns more libraries like this: they’re extremely valuable to the community and provide a great foundation to build an app on.
Vendoring ain’t easy
One thing I found out early on is that the state of external dependencies in the iOS/Cocoa world is in a sorry state compared to Ruby’s. CocoaPods is making some serious inroads in creating reusable software packages, but I don’t feel like it’s enough. There’s clearly no guidance or leadership from Apple on this issue, despite the amount of OSS projects written in Objective-C available. Every README I browsed that said “Just drag the .xcodeproj into Xcode” or “Drop the .h and .m files into your project” made my heart sink.
RubyMotion does support vendoring static libraries and Xcode projects directly into your app, so not all is lost. It is also possible to package gems to use with RubyMotion via BubbleWrap, it just requires a bit of legwork to get set up.
I’d love to see this situation improve. We’re already using Ruby, so we should be able to tap into the RubyGems ecosystem properly from the start. If there’s one way we can prove that RubyMotion can win in the long-term, it’s by showing how easily it can link to both battle-tested Objective-C libraries and the Ruby community’s favorite gems.
Get out of the simulator
rake based workflow is wonderful. Run one command, and it compiles the app and launches the simulator with it running. Want the app on your device? After you run through the iOS certificate/provisioning gauntlet on your machine,
rake device makes it happen. We used TestFlight to ship beta builds out, and wrapped up deploying in another rake task:
rake deploy notes='Polish scrollbar behavior'.
We spent a lot of time on the v1.0.0 sprint dealing with bad network conditions, differences between 3.5” and 4” iPhones, and other performance issues. Tools like the Network Link Conditioner can help, and there’s even an official RubyMotion guide for debugging. It’s easy to forget the simulator is actually simulating your app! If you’re working on an app, this may seem obvious: use it on your device, and push it out to as many as possible, as early as you can.
Just keep shipping!
I’m really bullish about RubyMotion’s future, especially at 37signals. I think there’s a huge opportunity to make veteran iOS developers more productive by throwing the doors open to the Ruby community. New iOS developers win as well, since RubyMotion makes the jump to mobile development less scary by keeping your toolchain similar to other open source platforms. I’m excited to see what is going to happen in the RubyMotion community over the next few months and years, and I hope you’ll join me!
Thanks to Jason, Javan, and Ryan for feedback, and Jamie once again for the doodle.