sugarcube

by rubymotion-community

rubymotion-community / sugarcube

Some sugar for your cocoa. RubyMotion helpers.

429 Stars 69 Forks Last release: almost 6 years ago (3.0.2) Other 1.4K Commits 231 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

SugarCube

Join the chat at https://gitter.im/rubymotion/sugarcube

Build Status Version

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
    , or
    CALayer
    object as the root object, or it defaults to your application's
    UIWindow
    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 to
    a
    , 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
    (on
    UIView
    and
    CALayer
    )

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
    (on
    UIView
    ,
    CALayer
    ,
    NSWindow
    , and
    NSScreen
    )

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. "onpan" instead of "whenpanned")

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: []) # 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 earth would you want to do that?'.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
# => [email protected] on iphone 4
# => [email protected] 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]NSDate 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) => # > loveland_oh = CLLocationCoordinate2D.new(39.268128, -84.257648) => # > 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) => #

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 => #

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!). :poop:

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! :smiley:

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.