Share
Embed
An introduction to RubyMotion
Presentation made at the Bristol Ruby User Group about Rubymotion
Andrew Nesbitt
October 26, 2012
Transcript
34 slides
-
410m iOS devices As of June 2012 http://en.wikipedia.org/wiki/IOS That is
a market that is hard to ignore -
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 -
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 -
£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 -
// 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 -
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 -
[:!, :!=, :!~, :%, :*, :+, :<, :<<, :<=, :<=>,
:==, :===, :=~, :>, :>=, :[], : []=, :__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 -
[:!, :"!=:", :"! ~:", :"%:", :"*:", :"+:", :"<:", :"<<:", :"<=:",
:"<=>:", :"==:", :"===:", :"=~:", :">:", :">=:", :"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 -
Things you can’t do Kernal.anything require ‘foo’ eval ‘foo’ Remember
it’s not “real” ruby -
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 -
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 -
Interface Builder Can be hooked up to IB nib files
and also supports storyboarding for iOS 5 onwards -
# colour '#FF8A19'.to_color # => #<UIDeviceRGBColor:0x8d54110> # 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"} -
In The App Store There have been quite a few
Rubymotion apps approved on the App store, let’s check out a few -
Command line driven (no xcode) Bring your own editor Memory
Management Interactive Console Fast, native apps It’s Ruby! Pros -
Not an offical Apple project Lacking in documentation Still have
to learn Cocoa Provisioning still a pain Proprietary software camelCaseMethods Cons -
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