$30 off During Our Annual Pro Sale. View Details »

An introduction to RubyMotion

An introduction to RubyMotion

Presentation made at the Bristol Ruby User Group about Rubymotion

Andrew Nesbitt

October 26, 2012
Tweet

More Decks by Andrew Nesbitt

Other Decks in Technology

Transcript

  1. Andrew Nesbitt
    github.com/andrew
    @teabass

    View Slide

  2. forwardtechnology.co.uk

    View Slide

  3. View Slide

  4. View Slide

  5. 410m iOS devices
    As of June 2012
    http://en.wikipedia.org/wiki/IOS
    That is a market that is hard to ignore

    View Slide

  6. http://www.textfromxcode.com
    That is a market that is hard to ignore

    View Slide

  7. http://macruby.org
    Macruby
    Rubymotion is based on Macruby, which implements ruby natively in the objective c runtime
    Rubymotion has a number of internal differences which allow it to run within the sandboxed
    iOS
    Including memory management code as iOS lacks proper garbage collection

    View Slide

  8. twitter.com/lrz
    Laurent Sansonetti
    Build by Laurent Sansonetti, formerly of the iOS team Apple
    Core contributor to Macruby
    Left Apple to be able to release Rubymotion from his new company, Hipbyte

    View Slide

  9. £129
    ($199)
    Proprietary software is pretty unusual in the ruby community.
    Concerns about what long term support
    So far so good, lots of regular updates and good customer support
    About the same cost as the iOS Developer Program

    View Slide

  10. // Objective C
    - (NSInteger)pickerView:(UIPickerView *)pickerView forComponent:(NSInteger)component {
    ...
    }
    # RubyMotion
    def pickerView(pickerView, titleForRow:row, forComponent:component)
    ...
    end
    Code
    The are quite similar, syntactic sugar aside, except for the static typing
    The main difference between Rubymotion and normal ruby motions are the keyword
    arguments

    View Slide

  11. String => NSMutableString => NSString => NSObject
    Integer => Numeric => NSNumber => NSValue => NSObject
    Hash => NSMutableDictionary => NSDictionary => NSObject
    Array => NSMutableArray => NSArray => NSObject
    Symbol => NSString => NSObject
    Time => NSDate => NSObject
    Inheritance
    Ruby classes are implemented on top of their Cocoa equivalents

    View Slide

  12. [:!, :!=, :!~, :%, :*, :+, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, :[], :
    []=, :__id__, :__send__, :ascii_only?, :between?, :bytes, :bytesize, :byteslice, :capitalize, :capitalize!, :casecmp, :center, :chars, :chomp, :chomp!, :chop, :chop!, :chr, :class, :clear, :clone, :codepoints,
    :concat, :count, :crypt, :define_singleton_method, :delete, :delete!, :display, :downcase, :downcase!, :dump, :dup, :each_byte, :each_char, :each_codepoint, :each_line, :empty?, :encode, :encode!,
    :encoding, :end_with?, :enum_for, :eql?, :equal?, :extend, :force_encoding, :freeze, :frozen?, :getbyte, :gsub, :gsub!, :hash, :hex, :include?, :index, :initialize_clone, :initialize_dup, :insert, :inspect,
    :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :intern, :is_a?, :kind_of?, :length, :lines, :ljust,
    :local_methods, :lstrip, :lstrip!, :match, :method, :methods, :next, :next!, :nil?, :object_id, :oct, :ord, :partition, :prepend, :pretty_inspect, :pretty_print, :pretty_print_cycle, :pretty_print_inspect,
    :pretty_print_instance_variables, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :replace, :respond_to?, :respond_to_missing?, :reverse, :reverse!, :rindex, :rjust,
    :rpartition, :rstrip, :rstrip!, :scan, :send, :setbyte, :singleton_class, :singleton_methods, :size, :slice, :slice!, :split, :squeeze, :squeeze!, :start_with?, :strip, :strip!, :sub, :sub!, :succ, :succ!, :sum, :swapcase,
    :swapcase!, :taint, :tainted?, :tap, :to_c, :to_enum, :to_f, :to_i, :to_r, :to_s, :to_str, :to_sym, :tr, :tr!, :tr_s, :tr_s!, :trust, :unpack, :untaint, :untrust, :untrusted?, :upcase, :upcase!, :upto, :valid_encoding?]
    "ruby".methods.sort
    Here we can see the methods available on a string in ruby
    including methods for Object, which string inherits from

    View Slide

  13. [:!, :"!=:", :"!
    ~:", :"%:", :"*:", :"+:", :"<:", :"<<:", :"<=:", :"<=>:", :"==:", :"===:", :"=~:", :">:", :">=:", :"CA_addValue:multipliedBy:", :CA_copyRenderValue, :"CA_distanceToValue:", :"CA_interpolateValue:byFraction:",
    :"CA_interpolateValues:::interpolator:", :CA_prepareRenderValue, :Complex, :"Complex:", :"LS_hasCaseInsensitivePrefix:", :LS_unescapedQueryValue, :MCAppendDeviceName,
    :MCAppendGreenteaSuffix, :MCSHA256DigestWithSalt, :MCSafeFilenameHash, :"MCSafeFilenameHashWithExtension:", :"MLBindToSQLiteStatement:atPosition:", :MLSortString,
    :MPMediaLibraryDataProviderSystemML3CoercedString, :Rational, :"Rational:", :UTF8String, :[], :"[]:", :
    []=, :"[]=:", :__callee__, :__id__, :__method__, :__send__, :"__send__:", :__type__, :accessibilityActivate, :accessibilityActivationPoint, :"accessibilityArrayOfTextForTextMarkers:",
    :"accessibilityAttributeValue:", :"accessibilityAttributeValue:forParameter:", :"accessibilityBoundsForTextMarkers:", :"accessibilityCandidateWordDescription:", :accessibilityColumnRange,
    :"accessibilityCompareGeometry:", :accessibilityContainer, :accessibilityContainerElements, :"accessibilityContentForLineNumber:", :accessibilityCustomActions, :"accessibilityCustomRotorItemsAtIndex:",
    :accessibilityCustomRotorTitles, :accessibilityDecrement, :"accessibilityEditOperationAction:", :"accessibilityElementAtIndex:", :accessibilityElementCount, :accessibilityElementDidBecomeFocused,
    :accessibilityElementDidLoseFocus, :"accessibilityElementForRow:andColumn:", :accessibilityElementIsFocused, :accessibilityElementsHidden, :"accessibilityEnumerateContainerElementsUsingBlock:",
    :"accessibilityEnumerateContainerElementsWithOptions:usingBlock:", :accessibilityFlowToElements, :accessibilityFrame, :"accessibilityFrameForLineNumber:", :accessibilityHeaderElements,
    :accessibilityHint, :"accessibilityHitTest:", :accessibilityIdentification, :accessibilityIdentifier, :accessibilityIncrement, :accessibilityInvalidStatus, :accessibilityIsComboBox, :accessibilityLabel,
    :"accessibilityLabelForRange:", :accessibilityLanguage, :"accessibilityLineNumberForPoint:", :accessibilityLinkedElement, :accessibilityMathEquation, :accessibilityMathMLSource, :accessibilityMenuActions,
    :accessibilityPageContent, :accessibilityPaths, :"accessibilityPerformAction:withValue:", :"accessibilityPerformCustomAction:", :accessibilityPerformEscape, :accessibilityPerformMagicTap,
    :accessibilityPlaceholderValue, :"accessibilityPostNotification:withObject:afterDelay:", :accessibilityRequired, :accessibilityRootElement, :accessibilityRowRange, :"accessibilityScroll:",
    :accessibilityScrollDownPageSupported, :accessibilityScrollLeftPageSupported, :accessibilityScrollRightPageSupported, :accessibilityScrollUpPageSupported, :accessibilitySecondaryLabel,
    :"accessibilitySetIdentification:", :accessibilityShouldEnumerateContainerElementsArrayDirectly, :accessibilitySpeechHint, :accessibilityStartStopToggle, :"accessibilityStringForTextMarkers:",
    :accessibilityTitleElement, :accessibilityTraits, :accessibilityURL, :accessibilityUserDefinedActivationPoint, :accessibilityUserDefinedFrame, :accessibilityUserDefinedHint,
    :accessibilityUserDefinedIdentifier, :accessibilityUserDefinedIsMainWindow, :accessibilityUserDefinedLabel, :accessibilityUserDefinedLanguage, :accessibilityUserDefinedNotFirstElement,
    :accessibilityUserDefinedServesAsFirstElement, :accessibilityUserDefinedSize, :accessibilityUserDefinedTraits, :accessibilityUserDefinedValue, :accessibilityUserDefinedWindowVisible, :accessibilityValue,
    :accessibilityViewIsModal, :"accessibilityViewWithIdentifier:", :"accessibilityZoomInAtPoint:", :"accessibilityZoomOutAtPoint:", :"addObserver:forKeyPath:options:context:", :allowSafePerformSelector,
    :allowsWeakReference, :"appendCharacters:length:", :"appendFormat:", :"appendString:", :ascii_only?, :autoContentAccessingProxy, :autorelease, :"awakeAfterUsingCoder:", :awakeFromNib,
    :becomeActive, :becomeInactive, :"between?:", :blockingMainThreadProxy, :boolValue, :bytes, :bytesize, :cString, :cStringLength, :"cStringUsingEncoding:", :camelize, :"camelize:",
    :"canBeConvertedToEncoding:", :capitalize, :capitalize!, :capitalizedString, :"capitalizedStringWithLocale:", :"caseInsensitiveCompare:", :"casecmp:", :center, :"center:", :"characterAtIndex:", :chars, :chomp,
    :chomp!, :"chomp!:", :"chomp:", :chop, :chop!, :"chop!:", :"chop:", :chr, :class, :classForArchiver, :classForCoder, :classForKeyedArchiver, :classForPortCoder, :className, :clear, :clone, :codepoints,
    :"commonNonWordBreakingPrefixWithString:options:", :"commonPrefixWithString:options:", :"compare:", :"compare:options:", :"compare:options:range:", :"compare:options:range:locale:",
    :"completePathIntoString:caseSensitive:matchesIntoArray:filterTypes:", :"componentsSeparatedByCharactersInSet:", :"componentsSeparatedByString:", :"concat:", :"conformsToProtocol:",
    :"containsSubstring:", :copy, :"copyUTF8StringOfLength:", :"copyWithZone:", :copyWithoutInsignificantCharacters, :copyWithoutInsignificantPrefixAndCharacters, :count, :"count:",
    :countOfComposedCharacterSequences, :"crypt:", :"cts_boundingRectWithSize:options:attributes:", :"cts_doBoundingRectWithSize:options:attributes:", :"cts_drawAtPoint:withAttributes:",
    :"cts_drawInRect:withAttributes:", :"cts_drawWithRect:options:attributes:", :"cts_sizeWithAttributes:", :cutStringByResolvingAndStandardizingPath, :"dataUsingEncoding:",
    :"dataUsingEncoding:allowLossyConversion:", :"dd_appendSpaces:", :dealloc, :debugDescription, :decimalValue, :decomposedStringWithCanonicalMapping, :decomposedStringWithCompatibilityMapping,
    :defaultAccessibilityTraits, :define_singleton_method, :"define_singleton_method:", :"delayedProxy:", :delete, :delete!, :"delete!:", :"delete:", :"deleteCharactersInRange:", :description,
    :destinationIdIsEmailAddress, :destinationIdIsPhoneNumber, :"dictionaryWithValuesForKeys:", :"didChange:valuesAtIndexes:forKey:", :"didChangeValueForKey:",
    :"didChangeValueForKey:withSetMutation:usingObjects:", :disallowSafePerformSelector, :displayableString, :"displayableStringByStrippingOffCommonPrefixWithString:",
    :"displayableStringByTrimmingPrefixString:", :"doesNotRecognizeSelector:", :doubleValue, :downcase, :downcase!, :"drawAtPoint:forWidth:withAttributes:",
    :"drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment:", :"drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment:includeEmoji:",
    :"drawAtPoint:forWidth:withFont:lineBreakMode:", :"drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:", :"drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:includeEmoji:",
    :"drawAtPoint:forWidth:withFont:minFontSize:actualFontSize:lineBreakMode:baselineAdjustment:",
    :"drawAtPoint:forWidth:withFont:minFontSize:actualFontSize:lineBreakMode:baselineAdjustment:includeEmoji:", :"drawAtPoint:withFont:", :"drawInRect:withAttributes:", :"drawInRect:withFont:",
    :"drawInRect:withFont:lineBreakMode:", :"drawInRect:withFont:lineBreakMode:alignment:", :"drawInRect:withFont:lineBreakMode:alignment:lineSpacing:",
    :"drawInRect:withFont:lineBreakMode:alignment:lineSpacing:includeEmoji:", :dump, :dup, :each_byte, :each_char, :each_codepoint, :each_line, :"each_line:", :"editDistanceFrom:", :empty?, :encode,
    :encode!, :"encode!:", :"encode:", :"encodeWithCoder:", :encoding, :end_with?, :"end_with?:", :endsSentence, :endsWord, :enum_for, :"enum_for:", :"enumerateLinesUsingBlock:",
    :"enumerateLinguisticTagsInRange:scheme:options:orthography:usingBlock:", :"enumerateSubstringsInRange:options:usingBlock:", :"eql?:", :"equal?:", :extend, :"extend:", :fastestEncoding,
    :fileSystemRepresentation, :finalize, :floatValue, :"force_encoding:", :formatConfiguration, :"forwardInvocation:", :"forwardingTargetForSelector:", :freeze,
    :"fromMainThreadPostNotificationName:object:userInfo:", :"fromNotifySafeThreadPerformSelector:withObject:", :"fromNotifySafeThreadPostNotificationName:object:userInfo:", :frozen?,
    :"getBytes:maxLength:filledLength:encoding:allowLossyConversion:range:remainingRange:", :"getBytes:maxLength:usedLength:encoding:options:range:remainingRange:", :"getCString:",
    :"getCString:maxLength:", :"getCString:maxLength:encoding:", :"getCString:m
    "iOS".methods.sort
    And here is the methods available on a string in Rubymotion
    These include all Cocoa and Obecjtive C methods inherited from NSString and NSObjectas
    well as ruby’s string methods

    View Slide

  14. Things you can’t do
    Kernal.anything
    require ‘foo’
    eval ‘foo’
    Remember it’s not “real” ruby

    View Slide

  15. Building and
    Debugging
    Building and running apps is all done on the command line using rake
    Debugging is done with the rubymotion repl, which integrates with the iOS simulator
    A debugger was released a few weeks ago

    View Slide

  16. Demo
    http://github.com/andrew/lightning

    View Slide

  17. Testing
    http://www.rubymotion.com/developer-center/articles/testing/
    testing using bacon, an rspec clone
    test ran with `rake test`

    View Slide

  18. Ship it!
    rake archive generates an .ipa file which can be uploaded to the app store, used for adhoc distribution or sent
    out to testers via testflight

    View Slide

  19. Interface Builder
    Can be hooked up to IB nib files and also supports storyboarding for iOS 5 onwards

    View Slide

  20. 3rd Party Libraries
    http://rubymotion-wrappers.com
    Rubygems
    CocoaPods
    http://rubymotion-wrappers.com

    View Slide

  21. BubbleWrap
    http://bubblewrap.io

    View Slide

  22. Notification Center
    Color conversion
    Gestures
    Camera
    Timers
    Audio
    HTTP
    JSON

    View Slide

  23. # colour
    '#FF8A19'.to_color
    # => #
    # Notification Center
    @reload_observer = App.notification_center.observe ReloadNotification do |
    notification|
    loadAndRefresh
    end
    # Gestures
    view.whenTapped do
    UIView.animateWithDuration(1,
    animations:lambda {
    # animate
    # @view.transform = ...
    })
    end
    # Camera
    BW::Device.camera.front.picture(media_types: [:movie, :image]) do |result|
    image_view = UIImageView.alloc.initWithImage(result[:original_image])
    end
    # JSON
    BW::JSON.parse "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}"
    # => {"foo"=>1, "bar"=>[1, 2, 3], "baz"=>"awesome"}

    View Slide

  24. In The App Store
    There have been quite a few Rubymotion apps approved on the App store, let’s check out a
    few

    View Slide

  25. Cabify
    http://blog.rubymotion.com/post/30514580062/rubymotion-success-story-cabify

    View Slide

  26. Ultra Reaction
    http://ultrareaction.sausyn.com

    View Slide

  27. Everclip
    http://blog.rubymotion.com/post/27906866028/rubymotion-success-story-everclip

    View Slide

  28. Open source
    example apps
    http://github.com/hipByte/RubyMotionSamples

    View Slide

  29. Summary

    View Slide

  30. Command line driven (no xcode)
    Bring your own editor
    Memory Management
    Interactive Console
    Fast, native apps
    It’s Ruby!
    Pros

    View Slide

  31. Not an offical Apple project
    Lacking in documentation
    Still have to learn Cocoa
    Provisioning still a pain
    Proprietary software
    camelCaseMethods
    Cons

    View Slide

  32. Playing
    Hacking
    Internal Tools
    Small apps on the store
    Use cases
    Right now it feels risky to invest a lot of time and money into building a large, complex
    rubymotion app
    That being said, we have started building a number of medium size apps within Forward that
    we plan on putting in the App Store

    View Slide

  33. MobiRuby
    http://mobiruby.org
    There may soon be an open source alternative

    View Slide

  34. Questions?

    View Slide