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.
Avoid Xcode
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.
Use BubbleWrap
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
RubyMotion’s 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.
James Harton
on 13 Feb 13I’m glad you found the work I did on using BubbleWrap to create RubyMotion gems useful – can you make any suggestions on how I can make this easier/better for people authoring gems?
Nick
on 13 Feb 13James: Thanks so much! I would love to see RubyMotion itself support gems, and not have to go through BubbleWrap. I’m not sure how that will work but I’m sure that will open up the floodgates.
Jonas
on 13 Feb 13Do you use BubbleWrap Persistence for all the app data persistence / caching / syncing / etc.? I would like to use CoreData, but haven’t found an enjoyable, RubyMotion-esque way to deal with it.
Nick
on 13 Feb 13Jonas: Just for some simple persistence. The most complicated data we store locally is a hash for each project with a name, id, etc. Most of the app’s data is coming down the pipe as HTML and rendered by UIWebView(s).
I tried out CoreData and MotionModel for an app experiment last July, and having to modify the schema inside of Xcode was a total pain. I haven’t looked into too many RubyMotion CoreData wrappers since then, but I’m sure the situation has improved a bit.
Mauricio
on 13 Feb 13What is 37s’ thoughts on supporting other mobile platforms? Does covering only iOS satisfy the majority of your demand for mobile versions of your apps?
Ryan
on 13 Feb 13Mauricio—I don’t think we can gauge demand for Android et al. until we ship something there. We move a step at a time. iPhone was the first step we wanted to take.
To Ryan
on 13 Feb 13He said: ...”iPhone was the first step we wanted to take.”
What’s next?
Sudara
on 13 Feb 13Just wanted to say thanks for writing this up! And double thanks for the links!
Dmitry
on 13 Feb 13It’s perfectly ok to write [NSObject new] as a shortcut to [[NSObject alloc] init] in Objective-C, not sure why it’s seemingly overlooked, probably old habits.
Adam Hallett
on 13 Feb 13Nick, Nice job documenting your experience. Tech talk in the future?
Richard Venneman
on 13 Feb 13Thanks for sharing your experiences!
I’ve been avoiding IB because I always ended up modifying views after initialization in code to achieve unique UIs. But that’s also the reason I end up with dozens of lines of code in loadView.
I’d love to end support for iOS 5 and start using auto layouts. I am very curious about your gem!
James Hancock
on 13 Feb 13How barberic!
Try writting a Windows Phone 8 app. Will take you a tenth the time and uses an awesome interface and an intelligent markup system that is like HTML done right. It’s truely trivial to write an app (1 week to do a complete banking app)
Same thing on iOS? Months, even for experts. Android? Weeks at least, probably longer.
Seriously, Apple gets away with crappy programming because of it’s popularity. I predict that’s about to change…. (Yes I know there are tools like mono-touch but you still have to build the GUI in xcode… yuk!)
Jens
on 14 Feb 13I was also very excited when I saw RubyMotion. I’ve started iOS dev using ObjectiveC but Ruby(Motion) just better fits my mind. The hybrid approach is very pragmatic, but are you planning to move parts or the whole of it to native code?
P.S. I am looking forward to your RubyMotion talk at inspect!
Paul Engel
on 14 Feb 13Nick: “I would love to see RubyMotion itself support gems, and not have to go through BubbleWrap. I’m not sure how that will work but I’m sure that will open up the floodgates.”.
In the past week, I have been developing my Ruby gem lock-o-motion. It’s goal is to get as much Ruby gems working within RubyMotion applications as possible. All you have to do is adding the gem to the :lotion bundle group and run the app. LockOMotion will require all the sources and track the files dependencies for you.
The gem is still in development, but I am able to require a few gems which are not RubyMotion aware. I think this might bring us a few steps closer for gems support within RubyMotion.
Alper
on 14 Feb 13As a newbie, seeing .alloc.init in Ruby hurts my head. In order to manage the number of concepts to grok, I try to stick to pure HTML5 / JavaScript along with the appropriate mobile and js frameworks when building mobile apps. This allows the app to be deployed on any platform without extra effort. What do you think about the effort to port your app to another platform? Is it significant in your opinion?
Robert Gleeson
on 14 Feb 13I battled with style as well. I wish RubyMotion(and MacRuby) took the approach that JRuby takes: you can call a method with camelCase or snake_case (JRuby handles the conversion for you). That doesn’t make the APIs less verbose, though.
Sanjay Challagundla
on 15 Feb 13Not sure if this is the right place to post. The Basecamp App is asking to Setup Security questions before I login. Is there a way that I can bypass that ? Having to answer Security questions on a mobile app with small screen doesn’t make sense.
Javier [ http://www.emastudios.com/ ]
on 15 Feb 13I’ve built games with xcode and also apps and I must say: while it is easy enough for any medium dev to understand, boy what a pain in the butt. This looks like a perfect solution and I wasn’t even looking for this. I came here because of your new book and now I can leave with something else in my pocket!
Don Schenck
on 15 Feb 13Interesting. I’ve been using Rhomobile to build my apps—Ruby that supports iOS, Android and Windows Phone with the same code base.
But Rhodes has been purchased by Motorola and the support is tepid at best.
Personally, I target Android first. I’ve built some iOS apps, but they (Apple) are a bear to work with (our sentiments are “love the hardware, hate the company”).
Thanks, Nick, for the post.
David
on 15 Feb 13I’m glad to see a well known company like 37signals using Rubymotion. I used it to prototype something for the company I used to work for (a rails shop) and they said they didn’t think Rubymotion was anything but a toy. I can’t wait to see their faces when everyone starts jumping on the ship and they’re stuck playing catch-up. Thanks for helping to re-instill the faith!
Leif
on 16 Feb 13Would you say it’s easier to jump into rubymotion rather than obj-c when coming from java/groovy?
Jens
on 16 Feb 13Leif, Ruby and RubyMotion have a more familiar and similar syntax than objc for someone coming from Java/Groovy background. Also objc needs header files which may seem foreign and unneccessary.
Phillipus
on 19 Feb 13You don’t have to use Interface Builder in XCode to create a UI. You say, “Avoiding Xcode means that we had to wire up the interface all in code.” And you can do just that with Objective-C. It ain’t that hard! This Ruby stuff sounds complicated to me!
This discussion is closed.