SugarCube
About
Cocoa and CocoaTouch are verbose frameworks. These extensions hope to make development in rubymotion more enjoyable. With SugarCube, you can create a color from an integer or symbol, or create a UIFont or UIImage from a string.
Many core classes are opened up as well, like adding the '<<' operator to a
UIView instance, instead of view.addSubview(subview), you can use the more
idiomatic: view << subview
.
The basic idea of SugarCube is to turn operations on their head. So instead of:
UIApplication.sharedApplication.openURL(NSURL.URLWithString(url))
How about:
url.nsurl.open
DISCLAIMER
It is possible that you will not like SugarCube. That is perfectly fine! Some people take milk in their coffee, some take sugar. Some crazy maniacs don't even drink coffee, if you can imagine that... All I'm saying is: to each their own. You should checkout BubbleWrap for another take on Cocoa-wrappage.
CONTRIBUTIONS
SugarCube is the result of the hard work of many developers, but it’s mostly maintained by Colin Gray, with help from members of the RubyMotion community. If you have an idea, please open a pull request so we can discuss it! All PRs must be submitted with specs.
If you use SugarCube on your projects, please consider donating a small amount via gratipay.com, or better yet: fork the project, add some specs, and improve the quality of this super-handy gem!
Documentation
A work in progress. This README is the best source, but I am trying to be more diligent about adding Yard documentation, which is available here:
http://rubydoc.info/gems/sugarcube/latest
Versioning
SugarCube uses FerVer: https://github.com/jonathanong/ferver. This means that minor breaking changes occur in minor version bumps, and sometimes a non-breaking change occures in the major version (like when we added OS X support).
Installation
gem install sugarcube
# in Rakefile
require 'sugarcube'
# or in Gemfile
gem 'sugarcube', :require => 'sugarcube-classic'
# or for the bold:
# gem 'sugarcube', :require => 'sugarcube-all'
# or for the picky:
# gem 'sugarcube', :require => [
# 'sugarcube',
# 'sugarcube-repl',
# ]
# in terminal
$ bundle install
Packages
SugarCube has grown over time to be a pretty massive collection of helpers.
While some people choose to use the entire library, other people like to pick
and choose the extensions they want to use. With that in mind, SugarCube is
written so that it does not pollute any classes by default. So if all you do
is require "sugarcube"
, you are NOT going to get much mileage!
In the installation code above, I show the example of using :require => 'sugarcube-all'
to include all of SugarCube's extensions. You can, alternatively require just
the packages you need:
Gemfile
gem 'sugarcube', :require => [
'sugarcube-ui',
'sugarcube-events',
'sugarcube-gestures',
'sugarcube-568',
'sugarcube-attributedstring',
]
Or, from your Rakefile:
$:.unshift('/Library/RubyMotion/lib')
require 'motion/project/template/ios'
require 'bundler'
Bundler.require
require 'sugarcube-ui'
require 'sugarcube-events'
require 'sugarcube-gestures'
require 'sugarcube-568'
require 'sugarcube-attributedstring'
You can require the packages in piecemeal like this, or you can require a group
of packages: classic, common, or all
.
-
sugarcube-classic
: Excludes 568, attributedstring, gestures, repl, awesome, anonymous, unholy, and legacy -
sugarcube-common
: Excludes awesome, anonymous, unholy, and legacy -
sugarcube-all
: Excludes legacy
So without further ado,
SugarCube
Packages are sorted more-or-less by their usefulness. The more esoteric ones are at the end.
REPL (wiki)
If you install SugarCube and only use the REPL package, you will benefit from some of SugarCube's greatest tricks!
require 'sugarcube-repl'
This package is useful during development because it adds methods to the REPL that make adjusting and introspecting views much easier. You'll get a lot more done in the REPL with these additions.
You should NEVER use these methods in your application, because this package is only included in 'development' mode. That means if you hard-code a call to 'tree' in your code, that will crash when you go to release your app. YIKES.
To keep this document lean-and-mean, I've put most of the REPL documentation in the wiki, but here's a quick overview:
-
Use the
tree
commands to output your view hierarchy. It can accept a UIView,UIViewController
, orCALayer
object as the root object, or it defaults to your application'sUIWindow
object.(main)> tree 0: . UIWindow(#6e1f950: [[0.0, 0.0], [320.0, 480.0]]) 1: `-- UIView(#8b203b0: [[0.0, 20.0], [320.0, 460.0]]) 2: +-- UIButton(#d028de0: [[10.0, 10.0], [320.0, 463.400512695312]])
-
The number can be passed to the
adjust
method, aliased toa
, and that will become the view or object you are adjusting.(main)> a 2 => UIButton(#d028de0: [[10.0, 10.0], [320.0, 463.400512695312]])
-
Now you can modify that view, either by accessing it via
a
(with no arguments it returns the object being adjusted) or by using an adjust method:> up 1 > wider 15 # these have shorthands, too > u 1 > w 15
-
Changed your mind? undo all adjustments to the object currently selected:
> restore
-
Which element in the tree did I select with adjust? You can make the object selected flash in the simulator:
> blink
Be sure to read more in the REPL Additions Wiki page.
UI on iOS: UIKit extensions (wiki)
A big package chock full of methods to make working in UIKit a joy.
require 'sugarcube-ui'
A few varieties of methods are in this package:
- Conversions:
'string-to'.uiimage
,image.uiimageview
,'string-to'.uilabel(font)
- Helpers: shorthands for common operations, like
a_view << a_subview
,a_subview.convert_frame_to(a_view)
- Symbols:
:system.uifont(20)
,:label.uifontsize
- Frame accessors:
a_view.x
,a_view.x = 100
(onUIView
andCALayer
)
There are too many methods to define here. Instead: a complete list of methods is available in the documentation, and the wiki page is a great source as well.
UI on OS X: AppKit extensions
Similar extensions as the iOS version, but using the ns
prefix on method names:
- Conversions:
'string-to'.nsimage
,image.nsimageview
,'string-to'.nslabel(font)
- Helpers:
view << subview
- Symbols:
:white.nscolor
,:system.nsfont
- Frame accessors:
a_view.x
,a_view.x = 100
(onUIView
,CALayer
,NSWindow
, andNSScreen
)
UI on Android
(warning: extending built-in classes is not reliable in RubyMotion on Android)
ViewGroup gets the same <<
method that you see in UIView and NSView.
Constants
require 'sugarcube-constants'
There are lots and lots of constants in UIKit, so many that I wanted a way to
write these as symbols instead of UILongConstantNames. This package adds
methods to Symbol
s to convert them into a UIKit or Foundation constant.
:center.nsalignment # => NSTextAlignmentCenter (formerly UITextAlignmentCenter)
:upside_down.uiorientation # => UIDeviceOrientationPortraitUpsideDown
:rounded.uibuttontype # => UIButtonTypeRoundedRect
:highlighted.uicontrolstate # => UIControlStateHighlighted
:touch.uicontrolevent # => UIControlEventTouchUpInside
:change.uicontrolevent # => UIControlEventValueChanged
:all.uicontrolevent # => UIControlEventAllEvents
# these are really handy for custom buttons - touch_start means the finger is
# inside the button, touch_stop is outside the button or canceled
:touch_start # => UIControlEventTouchDown | UIControlEventTouchDragEnter
:touch_stop # => UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit
:large.uiactivityindicatorstyle # :large, :white, :gray
:bar.uisegmentedstyle # :plain, :bordered, :bar, :bezeled
# UITableView and UITableViewCell have LOTS of associated constants... I'm
# adding them as I come across them.
:automatic.uitablerowanimation # or .uitableviewrowanimation
:default.uitablecellstyle # or .uitableviewcellstyle
:disclosure.uitablecellaccessory # or .uitableviewcellaccessorytype
:blue.uitablecellselectionstyle # or .uitableviewcellselectionstyle
See the complete list by browsing the documentation, or open up symbol.rb.
Timer
require 'sugarcube-timer'
Methods get added to the Fixnum class, and are available as methods on
NSTimer
, and can be called via the SugarCube::Timer module.
# once
1.second.later do
@view.shake
end
# repeating
1.second.every do
@view.shake
end
# you can assign the return value (an NSTimer)
timer = 1.second.every do
@view.shake
end
# and invalidate it
timer.invalidate
# the `every` method is available in the SugarCube::Timer module,
# which you might find more readable
include SugarCube::Timer
every 1.minute do
puts "tick"
end
# might as well make an alias for 'later', too
after 1.minute do
puts "ding!"
end
# other time-related methods
# for compatibility with Time methods, the mins/secs (and min/sec) aliases are provided. Personally,
# I like the more verbose minutes/seconds.
1.millisecond || 2.milliseconds
1.millisec || 2.millisecs
1.second || 2.seconds
1.sec || 2.secs # aliases
1.minute || 2.minutes # 1.minute = 60 seconds
1.min || 2.mins # aliases
1.hour || 2.hours # 1.hour = 60 minutes
1.day || 2.days # 1.day = 24 hours
1.week || 2.weeks # 1.week = 7 days
# sensible values for 'month' and 'year', even though we all know you can't
# **really** define them this way (go back to python if you find your brain hemorrhaging):
1.month || 2.months # 1.month = 30 days
1.year || 2.years # 1.year = 365 days
Events
require 'sugarcube-events'
Inspired by BubbleWrap's when
method, but I prefer jQuery-style
verbs and SugarCube symbols. Adds methods to UIControl and UITextView.
UIControl
button = UIButton.alloc.initWithFrame([[0, 0] ,[10, 10]])
button.on(:touch) { my_code }
button.on(:touch_up_outside, :touch_cancel) { |event|
puts event.inspect
# my_code...
}
# remove handlers
button.off(:touch, :touch_up_outside, :touch_cancel)
button.off # all events
You can only remove handlers by "type", not by the action. e.g. If you bind
three :touch
events, calling button.off(:touch)
will remove all three.
UITextView
These handlers are functionally identical in usage to the same methods in
UIControl
. They use the NSNotificationCenter#addObserverForName:object:queue:usingBlock:
method, but you do not have to worry about un-observing. When the text view is
released, these observers will be removed.
There are two aliases for each event, or you can use the event notification. I
prefer the present tense (jQuery-style on :change
), but UIKit prefers past
simple (:editing_did_begin
). The notifications, on the other hand, are in
present simple (UITextViewTextDidBeginEditingNotification
). Whatever floats
your boat.
Anyway, these are all the same:
:editing_did_begin :begin UITextViewTextDidBeginEditingNotification
:editing_did_change :change UITextViewTextDidChangeNotification
:editing_did_end :end UITextViewTextDidEndEditingNotification
text_view = UITextView.new
text_view.on :begin do |notification| # <= you have to accept the notification in your block
p 'wait for it...'
end
text_view.on :change do |notification|
p text_view.text
end
text_view.on :end do |notification|
p 'done!'
end
# if you want to remove the block, use the off method
text_view.off :editing_did_change
# or
text_view.off :change
# or
text_view.off UITextViewTextDidChangeNotification
Gestures
require 'sugarcube-gestures'
SugarCube's gesture support is very similar to BubbleWrap's, and it's entirely possible that the two will be merged into one thing. But SugarCube is all about extending base classes, whereas BubbleWrap tends to add new classes to do the heavy lifting. Plus the options you pass to SugarCube are very different, and the prefix is "on" instead of "when" (e.g. "on_pan" instead of "when_panned")
view.on_pan do |gesture|
location = gesture.locationInView(view)
end
# other gesture methods, with common options:
view.on_tap # use system defaults
view.on_tap(1) # number of taps
view.on_tap(taps: 1, fingers: 1) # number of taps and number of fingers
view.on_pinch # no options
view.on_rotate # no options
view.on_swipe # use system defaults
view.on_swipe :left
view.on_swipe(direction: :left, fingers: 1)
view.on_swipe(direction: UISwipeGestureRecognizerDirectionLeft, fingers: 1)
view.on_pan # use system defaults
view.on_pan(2) # minimum and maximum fingers required
view.on_pan(fingers: 2)
view.on_pan(min_fingers: 2, max_fingers: 3)
# If present, overrides fingers options and instead handles gestures originating at specified screen edges (UIScreenEdgePanGestureRecognizer)
view.on_pan(edges: [<list>]) # Some combination of [:left, :right, :top, :bottom, :all].
# `on_press` is a continuous event (it uses UILongPressGestureRecognizer), so
# you need to check the `gesture`:
view.on_press do |gesture|
if gesture.state == UIGestureRecognizerStateBegan
# handle press
end
end
view.on_press(1.5) # duration
view.on_press(duration: 1.5, taps: 1, fingers: 1)
# this version is only fired when the long-press begins; this is *probably* more
# useful to you:
view.on_press_begin do ... end
# or, when the gesture ends:
view.on_press_ended do ... end
Notifications
require 'sugarcube-notifications'
Makes it easy to post a notification to some or all objects.
# this one is handy, I think:
MyNotification = "my notification"
MyNotification.post_notification # => NSNotificationCenter.defaultCenter.postNotificationName(MyNotification, object:nil)
MyNotification.post_notification(obj) # => NSNotificationCenter.defaultCenter.postNotificationName(MyNotification, object:obj)
MyNotification.post_notification(obj, user: 'dict') # => NSNotificationCenter.defaultCenter.postNotificationName(MyNotification, object:obj, userInfo:{user: 'dict'})
# you can access the userInfo dictionary directly from the notification
def notified(notification)
notification[:user] # => 'dict'
end
# very similar to add or remove an observer
MyNotification.add_observer(observer, :method_name)
MyNotification.add_observer(observer, :method_name, object)
# remove the observer
MyNotification.remove_observer(observer)
MyNotification.remove_observer(observer, object)
UIImage
Image Manipulation - VERY handy! Includes some quick maniputions on UIImage,
and adds an interface to chain together CIFilters. Plus, you can refer to
cifilter.rb
to find out what filters are supported in iOS (all supported
filters get a class method in this file).
require 'sugarcube-image'
UIImage additions
image.scale_to [37, 37]
image.rounded # default: 5 pt radius
image.rounded(10)
image.in_rect([[10, 10], [100, 100]]) # get part of an image
image.darken # => good for "pressed" buttons
image.darken(brightness: -0.5, saturation: -0.2) # these are the defaults
image.gaussian_blur(radius: 5)
image.inverted
image.rotate(:left)
image.rotate(:right)
image.rotate(:flip) # 180° - if you have a better name, let me know!
image.rotate(45.degrees)
image.in_rect(frame) # returns the part of the image contained in frame
image.scale_to(new_size) # won't stretch, but the image might have transparent padding
image.scale_to(new_size, background: :white) # adds a white background before padding
image.scale_within(new_size) # same as scale_to in that it doesn't stretch the
# image, but the size is not guaranteed to be new_size. It is guaranteed not to
# be *bigger* than new_size
image.scale_to_fill(new_size) # again, like scale_to, but the image is guaranteed
# to completely fill new_size, even if some of the image has to be cropped to fit.
# You can control which side or corner you prefer to remain visible. because the
# aspect ratio is maintained, only ONE dimension will need to be cropped.
image.scale_to_fill(new_size, position: :top_left)
# returns a UIColor (and supports retina images)
image.color_at([5, 5])
# default insets are UIEdgeInsetsZero
image.tileable
image.tileable(insets)
image.stretchable
image.stretchable(insets)
# Apply a mask to an image. The mask should be a grayscale image. White areas
# will be made transparent, and black opaque.
image.masked(mask_image)
# Apply a color overlay to an image. This is used used on icons (PNG's), on which
# you want to change the color.
image.overlay(UIColor.redColor)
image.overlay(:red)
# Combine two images
image_ab = image_a << image_b
# Create an image from scratch!
UIImage.canvas([100, 100]) do |context| # opaque: false, scale: UIScreen.mainScreen.scale
# draw here!
end
# use an image as a background to draw on
image.draw do |context|
# draw here!
end
# size
image = UIImage.canvas(size: [10, 20])
image.width # => 10
image.height # => 20
CIFilter additions
# create a filter
gaussy = CIFilter.gaussian_blur(radius: 5)
gaussy = CIFilter.gaussian_blur(5) # this also works - you can find the arg order by looking in cifilter.rb
# apply a filter to a UIImage
new_image = image.apply_filter(gaussy).uiimage # apply_filter returns a CIImage, which is converted to UIImage
# apply a chain of filters using the `|` operator or `apply_filter`
darken = CIFilter.color_controls(saturation: 0, brightness: 0)
new_image = image.apply_filter(gaussy).apply_filter(darken).uiimage
If you include sugarcube-pipes
you can use the |
operator to chain filters:
# using the filters from above
new_image = image | gaussy | darken | UIImage
new_view = view | gaussy | darken | UIView
There are 91 filters available in iOS 6, I won't list them here, but check out the Apple documentation to read about them, and study cifilter.rb.
UIColor
require 'sugarcube-color'
Methods to merge or manipulate a color, or to get information about a color.
Works best on RGB colors, but HSB will work well, too. UIColor
s based on
image patterns can't easily be inverted or mixed.
Any classes that have a well-defined "color" representation are given a
uicolor
method, so it's easy to create a color from hex codes, css names,
or images (as patterns).
:blue.uicolor # UIColor.blueColor
# uicolor() accepts an alpha value, too
:blue.uicolor(0.5)
# all CSS colors are supported (but no "grey" aliases, consistent with UIKit,
# which only provides "grayColor")
:firebrick.uicolor # => 0xb22222.uicolor
# RGB values, in the range 0..255.
[160, 210, 242].uicolor # => UIColor.colorWithRed(0.6274, green:0.8235, blue:0.9490, alpha:1.0)
[160, 210, 242].uicolor(0.5) # => UIColor.colorWithRed(0.6274, green:0.8235, blue:0.9490, alpha:0.5)
# create a UIColor from a hex value
0xffffff.uicolor # => UIColor.colorWithRed(1.0, green:1.0, blue:1.0, alpha:1.0)
0xffffff.uicolor(0.5) # => UIColor.colorWithRed(1.0, green:1.0, blue:1.0, alpha:0.5)
# works when using strings, too
"#fff".uicolor # => UIColor.whiteColor
"#ffffff".uicolor # => UIColor.whiteColor
"#ff00ff".uicolor == :fuchsia.uicolor == 0xff00ff.uicolor # => UIColor.colorWithRed(1.0, green:0.0, blue:1.0, alpha:1.0)
"#f0f".uicolor(0.5) == :fuchsia.uicolor(0.5) == 0xff00ff.uicolor(0.5) # => UIColor.colorWithRed(1.0, green:1.0, blue:1.0, alpha:0.5)
# note: 0xf0f.uicolor == 0x000f0f.uicolor. There's no way to tell the difference
# at run time between those two Fixnum literals.
# UIColor from image name, if the first character is not "#"
"pattern".uicolor == "pattern".uiimage.uicolor # => UIColor.colorWithPatternImage(UIImage.imageNamed("pattern"))
These methods are added onto the UIColor class:
:red.uicolor.invert # => UIColor.cyanColor
:blue.uicolor.invert # => UIColor.yellowColor
:green.uicolor.invert # => UIColor.magentaColor
:red.uicolor + :blue.uicolor # => UIColor.purpleColor
:red.uicolor + :green.uicolor # => :olive.uicolor
# (I didn't know that until I tried it in the REPL, but it was pretty cool to
# see the UIColor#to_s method match that mixture to olive!)
# a more generic color mixing method (`+` delegates to this method):
:white.uicolor.mix_with(:black.uicolor, 0) # => :white
:white.uicolor.mix_with(:black.uicolor, 0.25) # => 0x404040.uicolor
:white.uicolor.mix_with(:black.uicolor, 0.5) # => :gray, same as :white.uicolor + :black.uicolor
:white.uicolor.mix_with(:black.uicolor, 0.75) # => 0xbfbfbf.uicolor
:white.uicolor.mix_with(:black.uicolor, 1) # => :black
# you can get information about a color:
:cyan.uicolor.red # => 0
:cyan.uicolor.green # => 1
:cyan.uicolor.blue # => 1
:cyan.uicolor.hue # => 0.5
:cyan.uicolor.saturation # => 1.0
:cyan.uicolor.brightness # => 1.0
:cyan.uicolor(0.9).alpha # => 0.9
# convert to CGColor
color.cgcolor
Factories
require 'sugarcube-factories'
UIAlertView
Accepts multiple buttons and handlers. In its simplest form, you can pass just
a title and block. An optional :message
can either be passed in as an option,
or as the 2nd positional arg.
Options:
UIAlertView.alert(options)
UIAlertView.alert(title, options)
UIAlertView.alert(title, message, options)
0 => title => String - title of the alert. optional positional arg.
1 => message => String - message of the alert. optional positional arg.
:title => String - title of the alert.
:message => String - message of the alert.
:success => Proc - the success handler
:cancel => Proc - the cancel handler
:buttons => [] - List of buttons ([cancel, others...])
:buttons => {} - Hash of buttons ({cancel:, others: ...}) in any order of course
:style => Symbol | Fixnum - A symbol (uialertstyle) or constant (UIAlertViewStyle*)
:show => Boolean - Whether to show the action sheet (default: true)
# simple title/message alert
UIAlertView.alert('This is happening, OK?', 'An optional message') do
self.it_happened!
end
# a little more complex - the cancel button should be first, and the block will
# receive a string and an index
UIAlertView.alert('This is happening, OK?',
message: 'Don\'t worry, it\'ll be fine.',
buttons: ['Nevermind', 'OK'],
) do |button, button_index|
if button == 'OK' # or: button_index == 1
self.happened!
end
end
# Full on whiz-bangery. The cancel button should be the first entry in
# `buttons:`. When you specify the success and cancel button handlers this way,
# you need not assign both.
UIAlertView.alert('I mean, is this cool?',
buttons: ['No!', 'Sure!', 'Hmmmm'],
message: 'No going back now',
cancel: proc { self.cancel },
success: proc { |pressed| self.proceed if pressed == 'Sure!' }
)
# To keep up with BubbleWrap's awesome BW::ActionSheet and BW::AlertView
# helpers, SugarCube provides a similar interface.
UIAlertView.alert('Confirm action!', 'Are you sure you want to do this?',
buttons: {
cancel: 'No!',
success: 'Sure!',
unsure: 'Hmmm',
}) do |button|
# button will be :cancel, :success or :unsure
end
UIActionSheet
This is very similar to UIAlertView.alert
, but instead of cancel
and
success
handlers, you can have cancel, success, and destructive
handlers,
and there is no message
argument.
If you use an array of buttons (which you probably should), the order of
arguments is [:cancel, :destructive, :others, ...]
. If you dont want a
cancel or destructive button, pass nil
in place.
Options:
UIActionSheet.alert(options)
UIActionSheet.alert(title, options)
0 => title => String - title of the action sheet
:title => Proc - title of the action sheet
:success => Proc - the success handler
:cancel => Proc - the cancel handler
:destructive => Proc - the destructive handler
:buttons => [] - List of buttons ([cancel, destructive, others...])
:buttons => {} - Hash of buttons ({cancel:, destructive:, others: ...}) in any order of course
:style => Symbol | Fixnum - A symbol (uiactionstyle) or constant (UIActionSheetStyle*)
:show => Boolean - Whether to show the action sheet (default: true)
:from => CGRect | UIBarButtonItem | UIToolbar | UITabBar | UIView (default: first window)
Where to display the alert. Mostly relevant on iPad.
:view => UIView (default: first window)
The view to display the alert when used with :from => CGRect
# simple
UIActionSheet.alert 'This is happening, OK?' do
self.happened!
end
# a little more complex, with cancel and destructive buttons
UIActionSheet.alert('This is happening, OK?', buttons: ['Cancel', 'Kill it!', 'Uh, what?']
) do |button|
# button is 'Cancel', 'Kill it!' or 'Uh, what?'
end
# skip cancel and destructive buttons:
UIActionSheet.alert('Should I?', buttons: [nil, nil, 'OK', 'Nevermind']) { |pressed|
self.do_it if pressed == 'OK'
}
UIActionSheet.alert 'I mean, is this cool?', buttons: ['Nah', 'With fire!', 'Sure', 'whatever'],
cancel: proc { self.cancel },
destructive: proc { self.kill_it_with_fire }
success: proc { |pressed| self.proceed if pressed == 'Sure' }
# By passing a Hash to buttons you can get this improved interface, similar to
# BubbleWrap's awesome interface.
UIActionSheet.alert('Well, how bout it?',
buttons: {
cancel: 'Cancel',
destructive: 'Kill it with fire!',
help: 'Tell me more'
}) do |button|
# button is :cancel, :destructive or :help
end
UIAlertController
Starting with iOS 8.0, UIActionSheet and UIAlertView are deprecated and replaced by UIAlertController.
This is very similar to UIAlertView.alert
and UIActionSheet.alert
but you have to pass a UIViewController
as first argument.
Options:
UIAlertController.alert(controller, options)
UIAlertController.alert(controller, title, options)
0 => title => String - title of the action sheet
:title => Title of the alert sheet
:buttons => [] - List of buttons ([cancel, destructive, others...])
:style => Symbol | Fixnum - A symbol (uialertcontrollerstyle) or constant (UIAlertControllerStyle*)
:show => Boolean - Whether to show the alert controller (default: true)
:from => CGRect | UIBarButtonItem | UIView (default: first window)
Where to display the alert. Mostly relevant on iPad.
:view => UIView (default: first window)
The view to display the alert when used with :from => CGRect
# simple
UIAlertController.alert(self, 'This is happening, OK?', buttons: ['Cancel', 'Kill it!', 'Uh, what?']
) do |button|
# button is 'Cancel', 'Kill it!' or 'Uh, what?'
end
UIButton
UIButton.buttonWithType(:custom.uibuttontype)
# =>
UIButton.custom
# many of these are obsolete since iOS 7
UIButton.custom => UIButton.buttonWithType(:custom.uibuttontype)
UIButton.rounded => UIButton.buttonWithType(:rounded.uibuttontype)
UIButton.rounded_rect => UIButton.buttonWithType(:rounded_rect.uibuttontype)
UIButton.detail => UIButton.buttonWithType(:detail.uibuttontype)
UIButton.detail_disclosure => UIButton.buttonWithType(:detail_disclosure.uibuttontype)
UIButton.info => UIButton.buttonWithType(:info.uibuttontype)
UIButton.info_light => UIButton.buttonWithType(:info_light.uibuttontype)
UIButton.info_dark => UIButton.buttonWithType(:info_dark.uibuttontype)
UIButton.contact => UIButton.buttonWithType(:contact.uibuttontype)
UIButton.contact_add => UIButton.buttonWithType(:contact_add.uibuttontype)
UIButton.system => UIButton.buttonWithType(:system.uibuttontype)
UITableView
Default frame is [[0, 0], [0, 0]]
, but most containers will resize it to be
the correct size. But heads up, it was [[0, 0], [320, 480]]
(until
the iphone 5 / 4-inch retina came out).
UITableView.alloc.initWithFrame([[0, 0], [0, 0]], style: :plain.uitableviewstyle)
UITableView.alloc.initWithFrame([[0, 0], [320, 480]], style: :plain.uitableviewstyle)
UITableView.alloc.initWithFrame([[0, 0], [320, 568]], style: :plain.uitableviewstyle)
# custom frame:
UITableView.alloc.initWithFrame([[0, 0], [320, 400]], style: :grouped.uitableviewstyle)
# =>
UITableView.plain
UITableView.plain([[0, 0], [320, 480]])
UITableView.plain([[0, 0], [320, 568]])
# custom frame:
UITableView.grouped([[0, 0], [320, 400]])
UITableViewCell
# factory methods, named for the cell style. cell identifier is required.
UITableViewCell.default('cell_identifier')
UITableViewCell.value1('cell_identifier')
UITableViewCell.value2('cell_identifier')
UITableViewCell.subtitle('cell_identifier')
# you can options for the common settings
cell = UITableViewCell.default('cell_identifier',
accessory: :disclosure,
selection: :blue,
text: 'text',
image: 'icon', # coerced into a UIImage
)
UISegmentedControl
control = UISegmentedControl.alloc.initItems(["one", "ah-two-whoo", "thr-r-r-ree"])
control.segmentedControlStyle = :bar.uisegmentedstyle
# =>
UISegmentedControl.bar(["one", "ah-two-whoo", "thr-r-r-ree"])
# plain, bordered, and bezeled are the other types
UIActivityViewIndicator
UIActivityIndicatorView.alloc.initWithActivityIndicatorStyle(UIActivityIndicatorViewStyleWhite)
UIActivityIndicatorView.alloc.initWithActivityIndicatorStyle(UIActivityIndicatorViewStyleWhiteLarge)
UIActivityIndicatorView.alloc.initWithActivityIndicatorStyle(UIActivityIndicatorViewStyleGray)
# =>
UIActivityIndicatorView.white
UIActivityIndicatorView.large
UIActivityIndicatorView.gray
UIBarButtonItem
These factory methods accept a block, which will get wired up as a target/action.
# Get an instance containing the specified system item.
UIBarButtonItem.done do
self.dismissViewControllerAnimated true, completion:nil
end
# =>
UIBarButtonItem.alloc.initWithBarButtonSystemItem(:done.uibarbuttonitem, target:self, action:"action:")
# with 'action' defined as:
def action(sender)
self.dismissViewControllerAnimated true, completion:nil
end
# the method names are 1::1 with the uibarbuttonitem constants in symbol.rb
UIBarButtonItem.cancel { ... } => UIBarButtonItem.alloc.initWithBarButtonSystemItem(:cancel.uibarbuttonitem, target:self, action:"action:")
UIBarButtonItem.edit { ... } => UIBarButtonItem.alloc.initWithBarButtonSystemItem(:edit.uibarbuttonitem, target:self, action:"action:")
UIBarButtonItem.save { ... } => UIBarButtonItem.alloc.initWithBarButtonSystemItem(:save.uibarbuttonitem, target:self, action:"action:")
.
.
.
UIBarButtonItem.page_curl { ... } => UIBarButtonItem.alloc.initWithBarButtonSystemItem(:page_curl.uibarbuttonitem, target:self, action:"action:")
For custom UIBarButtonItem
s, you can use the titled
and imaged
methods:
# Create a UIBarButtonItem, specifying the title
UIBarButtonItem.titled('Close') do
self.dismissViewControllerAnimated(true, completion:nil)
end
# =>
UIBarButtonItem.alloc.initWithTitle('Close', style: :bordered.uibarbuttonstyle, target:self, action:"action:")
def action(sender)
self.dismissViewControllerAnimated(true, completion:nil)
end
# You can also specify the style.
UIBarButtonItem.titled('Close', :plain) do # :plain, :bordered, :done
# ...
end
# Or specify the image instead
UIBarButtonItem.imaged('close_icon') do # 'close_icon' will be coerced into a UIImage
# ...
end
# =>
UIBarButtonItem.alloc.initWithImage('Close'.uiimage, style: :bordered.uibarbuttonstyle, ...)
# And, like `titled`, specify the style
UIBarButtonItem.imaged('close'.uiimage, :done) do
# ...
end
# If you provide two images, they will be used as the portrait and landscape images
UIBarButtonItem.imaged(['portrait'.uiimage, 'landscape'.uiimage) do
# ...
end
# =>
UIBarButtonItem.alloc.initWithImage('portrait'.uiimage, landscapeImagePhone:'landscape'.uiimage, style: :bordered.uibarbuttonstyle, target:self, action:"action:")
Example Usage:
toolbar = UIToolbar.new
toolbar.items = [
@image_picker_button = UIBarButtonItem.camera { presentImagePickerController(self) },
UIBarButtonItem.flexiblespace,
@saveButton = UIBarButtonItem.save { save_photo(self) }
]
UITabBarItem
Easy to create system or custom UITabBarItem
s.
UITabBarItem.titled('My Title')
# with optional :image and :selected_image options:
UITabBarItem.titled('My Title', image: 'my_icon', selected_image: 'my_icon_selected')
# also supports :tag and :badge
UITabBarItem.titled('My Title', tag: MY_TABBAR_ITEM, badge: '+1')
# system items:
UITabBarItem.more
UITabBarItem.favorites
# Most of the UITabBarItem init methods accept the UIView#tag, and so there is
# support for that in the UITabBarItem factory methods. Defaults to 0. The
# :badge option is supported here as well
UITabBarItem.featured(tag: MY_TABBAR_ITEM, badge: 10)
# All of the UITabBarSystemItem helpers delegate to the 'UITabBarItem.system'
# method, which you can call direcly as well. It accepts :tag and :badge
# options.
UITabBarItem.system(:top_rated, tag: MY_ITEM_TAG, badge: 'hi')
NSError
WARNING: Breaking change in 3.3.0, this method name was
new
, but that caused conflicts with a bunch of 3rd party CocoaPods.
# usually, NSError.error doesn't work, because the only initializer for NSError
# needs more arguments. This method passes some defaults in.
NSError.error('message')
# same as =>
NSError.error('message', domain: 'Error', code: 0, userInfo: {})
UILabel
WARNING: Breaking change in 3.3.0, this method name was
new
, but that caused conflicts with a bunch of 3rd party CocoaPods.
# supports text, font, and font size
UILabel.label('label text')
UILabel.label('label text'.attrd) # detects attributed strings, too
UILabel.label('label text', 'Font name') # You can pass just a font name
UILabel.label('label text', UIFont.fontWithName('Font name', size: 20)) # Or a UIFont object
UILabel.label('label text', 'Font name', 20) # Or the name *and* the size
Animations (wiki)
require 'sugarcube-animations'
Careful, once you start using these helpers, you'll never go back.
view.move_to [100.0, 100.0] # origin
view.center_to [100.0, 100.0] # center
view.scale_to 2 # double the size, and preserves existing rotation transform
# view.scale_to 4 -> CGAffineTransformMakeScale(4, 4)
# view.scale_to [4, 3] -> CGAffineTransformMakeScale(4, 3)
view.fade_out
view.slide :left, 100
view.rotate_to 180.degrees
view.shake # great for showing invalid form elements
view.tumble # great way to dismiss an alert-like-view
# tumbles in the other direction (towards the right side instead of left)
view.tumble(side: :right)
# wow, this is a SuperGoodDeleteWiggle! https://github.com/mxcl/SuperGoodDeleteWiggle
view.wiggle
view.dont_wiggle
# if you modify the AppDelegate to load the WiggleAnimationController you can
# see an example of this animation in action.
# the complement to 'tumble' is 'tumble_in' - the view starts above the window
# and drops in with the same kind of animation as 'tumble'. Before you call
# this method, set the view.frame to the *destination* location.
view.tumble_in(side: :right)
These helpers all delegate to the UIView.animate
method, which accepts all the
options that UIView.animateWithDuration(delay:options:animations:completion:)
or UIView.animateWithDuration(delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)
accept. All options are optional, and they will play nicely inside an animation
chain (see below, and the wiki page).
UIView.animate do
view.alpha = 0
end
The "spring" animations use the method UIView.animateWithDuration(delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)
,
available in iOS 7. In testing, I've found this method to be slightly
unreliable, so use with caution. To "enable" it, pass in the :damping
option.
You can also use :velocity
to set an initial velocity, if there is an
animation in progress.
new_frame = [[110, 200], [100, 20]]
UIView.animate(damping: 0.1) do # default velocity is 0
view.frame = new_frame
end
# equivalent:
view.reframe_to(new_frame, damping: 0.1, velocity: 1)
The [wiki] page documents all the different animation methods, and documents animation chaining, which looks like this:
# fade out and slide left, then fade back in while returning to original position
UIView.animation_chain do
view.fade_out
view.slide :left
end.and_then do
view.fade_in
view.slide :right
end.start
Core Animation classes get some love, too!
view.layer.basic_animation('opacity', from: 0, to: 1, duration: 0.1)
Lots more information in the Wiki!
Modal
require 'sugarcube-modal'
It is nice that any UIViewController
can present a modal, but if you have
tabs or navs or crap in the way, this is actually NOT what you want. You
should use the rootViewController
(whatever it may be) to present to modal.
And since this is a property on UIWindow
, which is more-or-less a constant, we
can make this the easiest to do!
include SugarCube::Modal # make these methods available globally
view_ctlr = EditSomethingViewController.new
present_modal(view_ctlr)
# ...later, when all is well...
dismiss_modal
These accept completion blocks:
present_modal(view_ctlr) { puts "Now You See Me!" }
dismiss_modal { puts "Now You Don't!" }
If you like these methods, but you want to specify the reciever, they are
re-defined on UIViewController
for this purpose:
controller.present_modal(other_controller) { puts "presented" }
Numbers
require 'sugarcube-numbers'
Converting input
Uses NSNumberFormatter
to try and parse a human-readable number string.
if input.nan?
UIAlertView.alert('not a number!')
else
number = input.to_number
# convert a currency
number = input.to_number(NSNumberFormatterCurrencyStyle)
# convert from symbol, requires 'sugarcube-constants'
number = input.to_number(:currency)
end
Pretty print numbers
Use NSNumberFormatter to easily format a number in the current locale
10000.string_with_style # => "10,000"
10000.string_with_style(NSNumberFormatterCurrencyStyle) # => "$10,000.00"
# will convert symbol-constants using the sugarcube-constants package, if it is available
10000.string_with_style(:currency) # => "$10,000.00"
Percent
100.0.percent # => 1.00
55.0.percent # => 0.55
Ordinals
# some number-to-string stuff
1.nth # => 'st'
2.nth # => 'nd'
3.nth # => 'rd'
4.nth # => 'th'
11.nth # => 'th'
13.nth # => 'th'
21.nth # => 'st'
23.nth # => 'rd'
Angles
Since you always want to work in radians, calling 10.degrees
returns 10°, in
radians. You can convert back to degrees using to_degrees
. Lastly, you can
specify a multiple of π as a number:
10.degrees # => π / 18
45.degrees # => π / 4
3.14159.to_degrees # => approx 180
2.pi # => 6.28318...
Distances
If you thought conversion from degrees to radians looks weird, you'll hate conversion from meters to miles:
distance = 1500 # this is in meters. why? because all the methods that return
# a "distance" return it in meters
distance = 2.miles # => 3218.688, that's how many meters are in 2 miles
1500.in_miles # converts meters to miles => 0.932056427001953
Time
By now you have probably "gotten it" and are hooked on these number helpers. Heads up, though, some of these conflict with MotionSupport's definition.
1.second * 5.per_second = 5
1.day * 3.per_hour = 72
1.year.in_minutes = 525960.0
Sizes
Similar conversion methods for hard disk sizes. Uses the "mebi-byte" concepts, e.g. 1024 bytes in a kilobyte.
1.byte # => 1
1.kilobyte # => 1024
1.megabyte # => 1048576
1.gigabyte # => 1073741824
1.terabyte # => 1099511627776
1.petabyte # => 1125899906842624
1.exabyte # => 1152921504606846976
1.megabyte.in_kilobytes # => 1024
Screen
1.pixel # => 1 on non-retina, 0.5 on retina.
# Useful when drawing if you want to specify the number of pixels
AttributedString
require 'sugarcube-attributedstring'
These are pretty fun! Check out [nsattributedstring_spec.rb][] for all the supported attributes (in theory they are all supported, but there's weird issues with missing constants).
'test'.nsattributedstring({}) #=> NSAttributedString.alloc.initWithString('test', attributes:{})
'test'.attrd # => alias for `nsattributedstring`
'test'.bold # => NSAttributedString.alloc.initWithString('test', attributes:{NSFontAttributeName => :bold.uifont})
'test'.italic # => NSAttributedString.alloc.initWithString('test', attributes:{NSFontAttributeName => :italic.uifont})
'test'.underline # => NSAttributedString.alloc.initWithString('test', attributes:{NSUnderlineStyleAttributeName => NSUnderlineStyleSingle})
# you can chain 'em, too.
'test'.bold.underline
# If you look up NSAttributedString Application Kit Additions, you can see all
# the constants. Each of those has a method on NSAttributedString.
# you can add 'em, but the FIRST one MUST be an NSAttributedString
'test'.attrd + '-ing'.italic
# And there's where it gets FUN:
('This'.italic + ' is going to be ' + 'FUN'.bold).underline
# and if you need to join a bunch of strings, there's a helper for that!
['This', 'is'.bold, 'handy!'].join_attrd(' ')
# or join plain strings with an attributed string:
['a', 'b', 'c'].join_attrd('-'.bold)
# join_attrd will *always* return an NSAttributedString
['a', 'b', 'c'].join_attrd(' ') # == NSAttributedString ('a b c')
# You can even generate attributed text from html! This feature uses the
# NSHTMLTextDocumentType option to convert the html to NSAttributedString
'Why on <b>earth<b> would you want to do <em>that</em>?'.attributed_html
And you can easily turn an attributed string into a label, if you include the
sugarcube-ui
package.
view << (("We just met\n".attrd +
"and this is " + "CRAZY".italic + "\n"
"But here's my " + "id_rsa.pub".monospace + " file,\n" +
"so give me SSH access.").uilabel
568
require 'sugarcube-568'
If you require 'sugarcube-568'
in your Rakefile, you can use
UIImage.imageNamed(name)
or name.uiimage
to load images that are specific to
the 4" iphone.
'tall'.uiimage # => UIImage.imageNamed('tall')
# => tall.png on iphone 3g
# => tall@2x.png on iphone 4
# => tall-568h@2x.png on iphone 5
This code is ported from https://github.com/gaj/imageNamed568, which I had some problems with on RubyMotion (it worked, but not always. Very strange).
Files
Methods to find document files, resource files, cache files, temporary files, access entries out of the Info.plist file, and write data/arrays/dictionaries to a file.
require 'sugarcube-files'
# file operations
"my.plist".file_exists? # => NSFileManager.defaultManager.fileExistsAtPath("my.plist")
"my.plist".remove_file! # => NSFileManager.defaultManager.removeItemAtPath("my.plist".document, error: error) (returns error, if any occurred)
"my.plist".document_path # => NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)[0].stringByAppendingPathComponent("my.plist")
"my.plist".cache_path # => NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true)[0].stringByAppendingPathComponent("my.plist")
"my.plist".temporary_path # => NSTemporaryDirectory().stringByAppendingPathComponent("my.plist")
# all of these can be turned into a URL, too
"my.plist".temporary_path.file_url # => NSURL.fileURLWithPath("my.plist".temporary_path)
# get the resource path, useful if you include json files or images you manipulate in the app
"my.plist".resource_path # => NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent("my.plist")
# same, but get a URL instead - often used to display a static HTML page that is stored in resources.
# getting a path URL from a resource is just a little different from creating a URL from any other type of path
"index.html".resource_url # => NSBundle.mainBundle.URLForResource("index", withExtension:"html")
# access data from Info.plist
"CFBundleVersion".info_plist # => NSBundle.mainBundle.infoDictionary["CFBundleVersion"]
# write to file
[].write_to('array.plist'.document_path)
array = NSArray.read_from('array.plist'.resource_path)
{}.write_to('dict.plist'.document_path)
dict = NSDictionary.read_from('dict.plist'.resource_path)
data = UIImagePNGRepresentation('some_image'.uiimage)
# using sugarcube-nsdata:
# data = 'some_image'.uiimage.nsdata
data.write_to('some_image.png'.document_path)
image_data = NSData.read_from('some_image.png'.document_path)
SpriteKit
node_a = SKNode.node
node_a.name = 'parent'
node_b = SKNode.node
node_b.name = 'child'
node_c = SKNode.node
node_c.name = 'child'
# add child nodes
node_a << node_b
node_a << node_c
# set user data
node_a[:life] = 100
# enumerate child nodes
node_a.each_named('child') do |node, stop_ptr|
if node == node_b
stop_ptr.value = true
end
end
node_a.run_action(SKAction.fadeOut) do
# done fading out
end
Localized
require 'sugarcube-localized'
# NSLocalizedString from string
"hello".localized # => NSBundle.mainBundle.localizedStringForKey("hello", value:nil, table:nil)
"hello"._ # == "hello".localized
"hello".localized('Hello!', 'hello_table') # => ...("hello", value:'Hello!', table:'hello_table')
# If you have an NSError object, you can retrieve the localizedDescription the same way:
error.localized
error._
NSCoder
Shorthands and hash-like access to the coder/decoder objects.
require 'sugarcube-nscoder'
# hash access is the handiest
coder['key'] = self.value
@value = decoder['key']
if decoder.key?('key')
value = decoder['key']
end
# but if you want to store booleans and such (in their C form,
# which will take up less space):
coder.set('sugarcube_is_neat', toBool: self.sugarcube_is_neat?)
@sugarcube_is_neat = decoder.bool('sugarcube_is_neat')
coder.set('number_of_things', toInt:self.number_of_things)
@number_of_things = decoder.int('number_of_things')
# Archiving and unarchiving is straightforward
# archiving uses NSKeyedArchiver
NSCoder.archive(root_object) # returns NSData
NSCoder.archive(root_object, to_file: 'foo'.document_path) # returns success boolean
# unarchiving uses NSKeyedUnarchiver
NSCoder.unarchive(data)
NSCoder.unarchive('foo'.document_path)
# the entire list of encode/decode helpers:
coder.set(key, toBool:value)
coder.set(key, toDouble:value)
coder.set(key, toFloat:value)
coder.set(key, toInt:value)
coder.set(key, toPoint:value)
coder.set(key, toRect:value)
coder.set(key, toSize:value)
decoder.bool(key)
decoder.double(key)
decoder.float(key)
decoder.int(key)
decoder.point(key)
decoder.rect(key)
decoder.size(key)
NSData
require 'sugarcube-nsdata'
Going to and from NSData
is really useful when doing HTTP posts.
# default string encoding is UTF8, you can pass other encodings to this method
string_data = 'String'.nsdata
# PNG data representation
image = 'an image'.uiimage
image_data = image.nsdata
# this will download the URL contents
url = 'http://localhost'.nsurl
url.nsdata # => NSData.dataWithContentsOfURL(self)
string_data.nsstring # => 'String'
image_data.uiimage # => whatever 'an image' was
NSDate (wiki)
require 'sugarcube-nsdate'
This package includes additions to the NSDate
class, and related additions to
Numeric
and NSString
. There's a lot here, so check out the wiki for detailed information.
Foundation
Smaller additions to the CoreFoundation classes. Some extensions, like
NSDate
, are large enough that they get broken out into their own packages.
require 'sugarcube-foundation'
NSArray
[1, 3].nsindexpath # NSIndexPath.indexPathWithIndex(1).indexPathByAddingIndex(3)
[1, 3].nsindexset
[1, 3].nsset
NSIndexSet
index_set.to_a # => [1, 2, ...]
NSIndexPath
index_path.to_a # => [1, 2, ...]
NSString
'https://github.com'.nsurl
'/path/to/file'.fileurl
'https://google.com/search?q=' + 'search terms'.escape_url # => "..?q=search%20terms"
'%20'.unescape_url #=> " "
'nô àccénts!'.remove_accents # => "no accents!"
NSURL
url = 'https://github.com'.nsurl
url.can_open? # => true, it will open in safari
url.open # opens in safari
url.nsurlrequest # convert to NSURLRequest object
IndexPath
require 'sugarcube-indexpath
Use the IndexPath
class to match NSIndexPath
objects, for instance in a
UITableViewDelegate
.
index_path = [0, 2].nsindexpath
case index_path
when IndexPath[0]
when IndexPath[1, 0..5]
when IndexPath[1, 5..objects.length]
end
[0, 2].nsindexpath.to_a == [0, 2] # => true
NSUserDefaults
require 'sugarcube-nsuserdefaults'
This file does one thing very DANGEROUS... to "help" with defaults.
When storing nil
into NSUserDefaults
, it is converted into false
, because
Cocoa complains if you give it nil
, and the RubyMotion runtime refuses to
allow the NSNull.null
object. Without relying on an external project (like
nsnulldammit I don't know of a sensible workaround...
If you want to "tap into" the defaults system that SugarCube uses, add a
to_nsuserdefaults
method and that will get called if you hand your object to
NSUserDefaults[]=
. However, there's no way to get it back later, so the
usefulness of this is very limited.
NSUserDefaults['key'] = ['any', 'objects'] # => NSUserDefaults.standardUserDefaults.setObject(['any', 'objects'], forKey: :key)
NSUserDefaults['key'] # => NSUserDefaults.standardUserDefaults.objectForKey(:key)
# symbols are converted to strings, so these are equivalent
NSUserDefaults[:key] = ['any', 'objects'] # => NSUserDefaults.standardUserDefaults.setObject(['any', 'objects'], forKey: :key)
NSUserDefaults[:key] # => NSUserDefaults.standardUserDefaults.objectForKey(:key)
Keep in mind that NSUserDefaults serializes the object you pass to it, it doesn't maintain a reference. That means that if you modify an object in place, it will not get persisted. An example will explain this better:
NSUserDefaults['test'] = { my: 'test' }
NSUserDefaults['test']['my'] == 'test'
# NSUserDefaults['test'] returns the hash, and the 'my' key returns 'test', so
# this comparison returns `true`
# but there is a temptation, perhaps, to modify that hash:
NSUserDefaults['test']['my'] = 'new' # BUG
# the corrected code
test = NSUserDefaults['test']
test['my'] = 'new'
NSUserDefaults['test'] = test # saved
CoreGraphics
This package is included by a few of the other packages, like repl, animations, and image.
Is it CGMakeRect
or CGRectMake
? What arguments does CGRect.new
take?
Instead, just use the coercion methods Rect()
, Size()
and Point()
. They
will happily convert most sensible (and some non-sensible) arguments into a
CGRect/CGSize/CGPoint
struct.
These are namespaced in the SugarCube::CoreGraphics
module, but I recommend
you include SugarCube::CoreGraphics
in app_delegate.rb.
For more CoreGraphics additions, you should use geomotion by Clay
Allsopp. It adds methods to CGRect
, CGPoint
, and CGSize
to make these
structures more rubyesque (these methods used to be part of SugarCube, but were
removed in an attempt to decrease the amount of duplicated code).
f = Rect(view.frame) # the identity function - returns a copy of the CGRect
o = Point(view.frame.origin) # returns a copy of CGPoint
s = Size(view.frame.size) # returns a copy of CGSize
# lots of other conversions are possible.
# a UIView or CALayer => view.frame
f = Rect(view)
# 4 numbers
f = Rect(x, y, w, h)
# or two arrays
f = Rect([x, y], [w, h])
# one array
f = Rect([[x, y], [w, h]])
# a CGPoint and CGSize
p = Point(x, y) # or just [x, y] works, too
s = Size(w, h) # again, [w, h] is fine
f = Rect(p, s)
# any combination of point/array and size/array
f = Rect(p, [w, h])
f = Rect([x, y], s)
Base64
require 'sugarcube-base64'
Todo: add UIImage/NSImage support Todo: add Android support?
Uses the NSData#base64EncodedStringWithOptions
and
NSData#initWithBase64EncodedData
methods to encode/decode base64 data. Normal
use is to convert your image/binary data into an NSData
instance, and then
call to_base64
on that object.
There is a helper on NSString
, so you can convert an NSString
instance
directly to base-64, using UTF8 encoding (or any encoding Apple supports).
base64_str = 'test string'.to_base64
...
NSString.from_base64(base64_str) == 'test string'
# require 'sugarcube-nsdata'
image = 'some_image'.uiimage
data = image.nsdata # defaults to PNG data
base64_str = data.to_base64
...
data = NSData.from_base64(base64_str)
image = data.uiimage # defaults to reading PNG data
Pointer
require 'sugarcube-pointer'
These are not UIKit-related, so I reverted to Ruby's preferred to_foo
convention.
[0.0, 1.1, 2.2].to_pointer(:float)
# is equivalent to
floats = Pointer.new(:float, 3)
floats[0] = 0.0
floats[1] = 1.1
floats[2] = 2.2
To_s
require 'sugarcube-to_s'
to_s
methods are defined on the following classes:
- NSError
- NSIndexPath
- NSLayoutConstraint
- NSNotification
- NSSet
- NSURL
- UIColor
- UIEvent
- UIView
- UILabel
- UITextField
- UITouch
- UIViewController
The output of these is (much?) more useful than the default.
CoreLocation
require 'sugarcube-corelocation'
Open up CLLocationCoordinate2D
to provide handy-dandies
# distances
> denver_co = CLLocationCoordinate2D.new(39.739188, -104.985223)
=> #<CLLocationCoordinate2D latitude=39.7391815185547 longitude=-104.985198974609>
> loveland_oh = CLLocationCoordinate2D.new(39.268128, -84.257648)
=> #<CLLocationCoordinate2D latitude=39.2681274414062 longitude=-84.2576293945312>
> denver_co.distance_to(loveland_oh)
=> 1779547.32010451 # in meters
> denver_co.distance_to(loveland_oh).in_miles
=> 1105.75943993609
# move around the globe using x/y distances in miles or kilometers
> denver_co.delta_miles(1101.6, -32.556)
=> #<CLLocationCoordinate2D latitude=39.2685263870498 longitude=-84.2744401887072>
# our location is pretty close!
> denver_co.delta_miles(1101.6, -32.556).distance_to(loveland_oh).in_miles
=> 0.900871117223827
> denver_co.delta_kilometers(10, 10) # 10 kilometers east, 10 kilometers north
=> #<CLLocationCoordinate2D latitude=39.8290195284119 longitude=-104.868401269013>
Pipes
This package short-circuits the |
operator to perform coercion and filtering
between all sorts of objects.
Coercion
Any object that defines a coercion method (image.uicolor, string.uiimage,
:symbol.uifont) can use the |
and the class name to perform the same method.
:label | UIFont # => # :label.uifont
"image_name" | UIImage # => "image_name".uiimage
view | UIImage | UIColor # => view.uiimage.uicolor
Filters
You can pipe objects that have some idea of "filter", like using an image mask or image filter.
image | mask # => image.masked(mask)
image | darken_cifilter # => image.apply_filter(darken_cifilter)
# this one is... interesting!
"My name is Mud" | /\w+$/ # => "Mud"
"My name is Mud" | /\d+$/ # => nil
"My name is Mud" | "Mud" # => "Mud"
"My name is Mud" | "Bob" # => nil
Awesome
require 'sugarcube-awesome'
SugarCube adds support for Motion-Awesome! The awesome_icon
method is added to Symbol
, which returns an NSAttributedString that uses the
MotionAwesome font. You can pass in :size
and :color
options.
sugarcube-attributedstring
is not required for this extension to function, but
it adds NSAttributedString
methods that really help. Below I'm usind #+
and
#bold
and #color
to construct an NSAttributedString
.
# in Rakefile
require 'sugarcube-awesome'
require 'sugarcube-attributedstring'
# in your app
label.attributedText = (:down_arrow.awesome_icon + ' Going down?'.bold).color(:white)
# OR for buttons
button.setAttributedTitle(:twitter.awesome_icon, forState:UIControlStateNormal)
Anonymous
require 'sugarcube-anonymous'
Convert Hash
es into an "anonymous object". Existing keys will be able to be
accessed using method names. Uses the SugarCube::Anonymous
class to
accomplish this, though the usual interface is via Hash#to_object
.
h = { foo: 'FOO', 'bar' => 'BAR' }.to_object
# You can use methods instead of keys.
h.foo # => h[:foo]
h.bar # => h['bar']
h.foo = 'Foo' # => h[:foo] = 'Foo'
h.bar = 'Bar' # => h['bar'] = 'Bar'
# only existing keys are accessed this way
h.baz # => NoMethodError
h.baz = 'baz' # => NoMethodError
Unholy
require 'sugarcube-unholy'
I added these a long time ago to SugarCube, and so it's possible some people use these extensions. Honestly, I should have put them somewhere else, they are silly and not terribly useful, except maybe to me (I use them a lot!). 💩
class Baz ; end
foo = Baz.new
# (:symbol || 'string').ivar
foo.instance_variable_set(:bar.ivar, value) # => foo.instance_variable_set(:@bar, value)
foo.instance_variable_set(var_name.ivar, value) # => foo.instance_variable_set("@#{var_name}", value)
# (:symbol || 'string').setter
foo.send(varname.setter, 'value')
# (:symbol || 'string').cvar
Baz.class_variable_set(var_name.cvar, value) # => Baz.class_variable_set("@@#{var_name}", value)
Legacy
require 'sugarcube-legacy'
This is where deprecated methods go to suffer a long, slow death.
Contributions
If you want to see new features, please fork, commit, and pull-request! 😃