1 - Getting Started

What is RMQ?

RMQ is a RubyMotion Front-end Library. It’s fast, non-polluting, non-magical, and chaining. It’s like jQuery for RubyMotion plus templates, stylesheets, events, animations, etc.

It’s designed to be lightweight and highly performant. You can use as little or as much of RMQ as you like. You typically do things the standard SDK way, using RMQ to streamline the tedious parts.

Some of the very cool features:

rmq_parts-1024x790

  • events and gestures
  • view factory
  • stylesheets
  • actions
  • position
  • selecting (querying views)
  • templates
  • traversal through view hierarchy (moving around the tree)
  • animations
  • validations
  • tagging
  • colors
  • fonts
  • image utilities
  • app
  • device

RMQ only works in iOS, not OS X


RMQ doesn’t require any other wrapper or gem.


The following are the only pollution in RMQ

  • UIView
    • rmq
    • rmq_data
  • UIViewController
    • rmq
    • rmq_data
    • Object
    • rmq

Well tested: over 610 tests and 1745 assertions

Back To Top

Getting started

Quick Start

Install gem
gem install ruby_motion_query

If you use rbenv, rehash:
rbenv rehash

Create an app
rmq create my_app

Then
cd my_app
bundle
rake

Now let’s add a button to your controller (MainController):

rmq.append(UIButton, :submit_button).on(:touch) do
  puts 'submitted'
end

If you run that, the button will be there but it will have no size so you won’t see it (or color or text). Let’s style it. Add this to your stylesheet (MainStylesheet)

def submit_button(st)
  st.frame = {left: 20, from_right: 20, height: 30, centered: :vertical}
  st.background_color = color.green
  st.color = color.white
  st.text = "Submit"
end

Run it:
rake

Installation

RMQ requires no other gems. If you use stuff like scale and certain animations it will require some frameworks (like QuartzCore or CoreGraphics)

  • gem install ruby_motion_query

If you use rbenv

  • rbenv rehash

Require it

  • require 'ruby_motion_query'

or add it to your Gemfile:

  • gem 'ruby_motion_query'

for RMQ-edge, add this to your Gemfile:

  • gem 'ruby_motion_query', :git => 'git@github.com:infinitered/rmq.git'

Deprecation

  • UIView#rmq_did_create(self_in_rmq)Use rmq_build instead
  • st.padded
  • st.left
  • st.x
  • st.top
  • st.y
  • st.width
  • st.height
  • st.bottom
  • st.from_bottom
  • st.right
  • st.from_right
  • st.centered

Example App


Clone this repo and run the example app to see an example of use.

git clone git@github.com:infinitered/rmq.git
cd rmq
rake

Back To Top

2 - General Features

Command-line tools and generators

Live stylesheet reloading

In REPL, type: rmq_live_stylesheets

image

Generators

Like Rails, RMQ provides a command-line tool, mostly for generating files. Create a new app:

> rmq create my_app

To get all the commands available just do:

> rmq

To generate controllers, models, views, etc, do the following:

 > rmq create model foo
 > rmq create controller bar
 > rmq create view foo_bar
 > rmq create shared some_class_used_app_wide
 > rmq create lib some_class_used_by_multiple_apps

 > rmq create collection_view_controller foos
 > rmq create table_view_controller bars

 > rmq create view my_view dry_run

I recomend you play around with it, do this:

> cd
> cd Desktop
> rmq create test_rmq_app
> cd test_rmq_app
> rmq create table_view_controller people
> rmq create view group
> bundle
> rake

Console Fun


rmq.log :tree
rmq.all.log
rmq.all.log :wide
rmq(UIView).show
rmq(UILabel).animations.blink
rmq(UIButton).nudge l: 10

Back To Top

Basic syntax

The main command is rmq and you use it everywhere.

# Like jQuery you wrap your objects or selectors in a rmq(), here are some examples
rmq(my_view).show
rmq(UILabel).hide

# You can use zero or more selectors, all these are valid
rmq(my_view, UILabel).hide
rmq(:a_tag, :a_style_name, UIImageView).hide
rmq(hidden: true).show

# If you use a set of selectors, they are an "or", to do an "and" use .and
rmq(UIImageView).and(hidden: true).show

rmq by itself is the rmq instance of the current UIViewController if you’re inside a UIViewController.

If you’re inside a UIView you’ve created, rmq.closest(UIControl) is the same as rmq(self).closest(UIControl).

rmq is always an array-like object (enumerablish), never nil, etc:

Because of this, you never have to check if your selectors return anything before calling actions or other methods on your rmq object.

# This will hide any UILabel in the controller, if any exist. If not, nothing happens
rmq(UILabel).hide

# Because of this, you can create chains without worry of exceptions
rmq(:this_tag_does_not_exist).show.apply_style(:my_style_name).on(:touch_down){puts 'score!'}

Almost all methods of an rmq object return either itself or another rmq object, so you can chain:

rmq.append(UILabel, :label_style).on(:tap){|sender,event| sender.hide}

# or

rmq.closest(AddEntry).find(:delete_button).hide
rmq(AddEntry).find(UILabel).blink

If you want the actual inner object(s), use .get

# returns my_view
rmq(my_view).get

# returns an array
rmq(UILabel).get

rmq_instance

Back To Top

Selectors

  • Constant
  • :a_tag
  • :a_style_name
  • my_view_instance
  • text: ‘you can select via attributes also’
  • :another_tag, UILabel, text: ‘an array’ <- this is an “or”, use .and for and

The more common use is to select any view or views you have assigned to variables, then perform actions on them. For example:

view_1 = UIView.alloc.initWithFrame([[10,10],[100, 10]])
view_2 = UIView.alloc.initWithFrame([[10,20],[100, 10]])
@view_3 = rmq.append(UIView, :some_style).get

rmq(view_1).layout(l: 20, t: 40, w: 80, h: 20)

rmq(view_1, view_2, @view_3).hide
a = [view_1, view_2, @view_3]

rmq(a).distribute(:vertical, margin: 10)

rmq(a).on(:tap) do |sender|
   puts 'Tapped'
end

Back To Top

Traversing

Moving around the subview tree.

Controller and root_view

rmq.view_controller
rmq.root_view # View of the view_controller

Window of the root_view

rmq.window

All subviews, subviews of subviews, etc for root_view:

rmq.all

Find

Find all children/grandchildren/etc:

rmq(my_view).find  # Different from children as it keeps on going down the tree

More commonly, you are searching for something:

rmq(my_view).find(UITextField)

Closest

Closest is interesting, and very useful. It searches through parents/grandparents/etc and finds the first occurrence that matches the selectors:

rmq(my_view).closest(Section)

Let’s say that someone clicked on a button in a table cell. You want to find and disable all buttons in that cell. So first you need to find the cell itself, and then find all buttons for that cell, then let’s say we want to hide them. You’d do that like so:

rmq(sender).closest(UITableViewCell).find(UIButton).hide

Children of selected view, views, or root_view

rmq.children  # All children (but not grandchildren) of root_view
rmq(:section).children  # All children of any view with the tag or stylename of :section
You can also add selectors
rmq(:section).children(UILabel)  # All children (that are of type UILabel of any view with the tag or stylename of :section

Parent or parents of selected view(s)

rmq(my_view).parent # superview of my_view
rmq(my_view).parents # superview of my_view, plus any grandparents, great-grandparents, etc
rmq(UIButton).parent # all parents of all buttons

Siblings

Find all your siblings:

rmq(my_view).siblings # All children of my_view's parent, minus my_view)

Get the sibling right next to the view, below the view:

rmq(my_view).next

Get the sibling right next to the view, above the view:

rmq(my_view).prev

And, not, back, and self

These four could be thought of as Selectors, not Traversing. They kind of go in both, anywho.

By default selectors are an OR, not an AND. This will return any UILabels and anything with text == ”:

rmq(UILabel, text: "") # This is an OR

So if you want to do an and, do this:

rmq(UILabel).and(text: "")

Not works the same way:

rmq(UILabel).not(text: "")

Back is interesting, it moves you back up the chain one. In this example, we find all images that are inside test_view. Then we tag them as :foo. Now we want to find all labels in test_view and tag them :bar. So after the first tag, we go back up the chain to test_view, find labels, then tag them :bar:

rmq(test_view).find(UIImageView).tag(:foo).back.find(UILabel).tag(:bar)

Filter

Filter is what everything else uses (parents, children, find, etc), you typically don’t use it yourself.

Back To Top

Tags

# Add tags
rmq(my_view).tag(:your_tag)
rmq(my_view).clear_tags.tag(:your_new_tag)

rmq(my_view).find(UILabel).tag(:selected, :customer)

# You can use a tag or tags as selectors
rmq(:selected).hide
rmq(:your_tag).and(:selected).hide

# Check a selection for tags
rmq(your_view).has_tag?(:foo)

# Untag  (Available in edge and in 1.2.0 when it's released)
rmq(your_view).untag(:foo)
rmq(:your_tag).untag(:your_tag)
rmq(view_a, view_b).untag(:foo, bar)

# You can optionally store a value in the tag, which can be super useful in some rare situations
rmq(my_view).tag(your_tag: 22)
rmq(my_view).tag(your_tag: 22, your_other_tag: 'Hello world')

# Or, dig deep into rmq_data to see tags on a single view
your_view.rmq_data.tag_names
your_view.rmq_data.tags

Back To Top

App

rmq.app.window
rmq.app.delegate
rmq.app.environment
rmq.app.release? # .production? also
rmq.app.test?
rmq.app.development?
rmq.app.version
rmq.app.name
rmq.app.identifier
rmq.app.resource_path
rmq.app.document_path
rmq.app.after 2 { do_something } # after 2 seconds
rmq.app.every 5 { do_something_repetitive } # every 5 seconds

Back To Top

Device

rmq.device.screen
rmq.device.width # screen width
rmq.device.height # screen height
rmq.device.ipad?
rmq.device.iphone?
rmq.device.retina?

# Detect phone size?
rmq.device.three_point_five_inch?
rmq.device.four_inch?
rmq.device.four_point_seven_inch?
rmq.device.five_point_five_inch?

# Detect the iOS version
rmq.device.ios_version # "8.0" etc
rmq.device.is_version? "8.0" # be specific on minor versions
rmq.device.is_version? "8" # or just use the major version number
rmq.device.ios_at_least? 8 # iOS must be 8.0 or higher for true
rmq.device.ios_at_least? 8.1 # returns true if version is 8.1 or higher

# return values are :unknown, :portrait, :portrait_upside_down, :landscape_left,
# :landscape_right, :face_up, :face_down
rmq.device.orientation
rmq.device.landscape?
rmq.device.portrait?

Back To Top

Actions

rmq(UILabel).send(:some_method, args)
rmq(my_view).hide
rmq(my_view).show
rmq(my_view).toggle
rmq(my_view).toggle_enabled
rmq(my_text_field).focus # or .become_first_responder

Back To Top

Events and Gestures

On / Off

To add an event, use .on, to remove it it, use .off

# Simple example
rmq(UIView).on(:tap){|sender| rmq(sender).hide}

# Adding an Event during creation
view_q = rmq.append(UIView).on(:tap) do |sender, event|
# do something here
end

# removing an Event
view_q.off(:tap)

# or you remove them all
view_q.off

# like everything in RMQ, this works on all items selected
rmq(UIView).off(:tap)

RubyMotionQuery::Event

In RMQ events and gestures are normalized with the same API. For example removing events or gestures is foo.off, and the appropriate thing happens.

If you see Event, just remember that’s either an event or gesture. I decided to call them Events

Type of events and gestures
# Events on controls
:touch
:touch_up
:touch_down
:touch_start
:touch_stop
:change

:touch_down_repeat
:touch_drag_inside
:touch_drag_outside
:touch_drag_enter
:touch_drag_exit
:touch_up_inside
:touch_up_outside
:touch_cancel

:value_changed

:editing_did_begin
:editing_changed
:editing_did_change
:editing_did_end
:editing_did_endonexit

:all_touch
:all_editing

:application
:system
:all

# Gestures
:tap
:pinch
:rotate
:swipe
:swipe_up
:swipe_down
:swipe_left
:swipe_right
:pan
:long_press
Interesting methods of an RubyMotionQuery::Event:
foo.sender
foo.event

foo.gesture?
foo.recognizer
foo.gesture

foo.location
foo.location_in

foo.sdk_event_or_recognizer

TODO, need many examples here

 

Events and user interaction

.userInteractionEnabled will be set to true when you add .on events (As of edge 0.7.1). If you are using an older version of RMQ, you can use .enable_interaction in a chain like so.


# this code allows you to place a tap event on an image rmq.append(UIImageView, :my_picture).enable_interaction.on(:tap) do |sender| puts "Imageview tapped" end

You can protect users from accidentally abusing an event

Some events fire asynchronous actions, or require some kind of delay between their next events. RMQ supplies a debounce option that will allow you to stammer events.

rmq(some_thing).on(:tap, debounce: 1.5) {
  puts "I won't be mashed and crashed!"
}

RubyMotionQuery::Events

The internal store of events in a UIView. It’s rmq.events, you won’t use it too often

Back To Top

Enumerablish

A rmq object is like an enumerable, but each method returns a rmq object instead of a enumerable. For example, these methods:

  • each
  • map
  • select
  • grep
  • detect
  • inject
  • first
  • last
  • []
  • <<
  • etc

all return another rmq instance, so you can chain.

You can also do rmq.length and rmq[0] like an array

.to_a gives you an actual array, so will .get (this is preferred)

Back To Top

Updating data or attributes

RMQ provides a variety of ways to update a view’s data or attributes. All of which are chain-able.

Data

You can use .data to either set or retrieve the “data” of the selected views. Depending on the view type, it will be the most common attribute, such as .text for UITextField or .image for UIImageView.

To set the data, you can do this

rmq(my_text_field).data('Foo')
rmq(my_label).data('Bar')

To get the data:

rmq(my_text_field).data
=> 'Foo'

# Returns an array if more than one view is selected
rmq(UIView).data
=> ['Foo', 'Bar']

This is chain-able. For example, let’s say you need to set a label’s title before applying a style, you could do this:

rmq.append(UILabel).data('Some long title').apply_style(:name_label)

You also use an = like so, but it’s not chain-able:

rmq(my_text_field).data = 'foo'

Attr

You can set any attribute:

rmq(my_text_field).attr(text: 'Foo')

Worse case scenario, you can use send:

rmq(UILabel).send(:some_method, args)

Back To Top

Colors


rmq.color.red rmq.color('#ffffff') rmq.color('ffffff') rmq.color(hex: '000', a: 0.5) rmq.color(128, 128, 128, 0.5) rmq.color(r: 128, g: 128, b: 128, a: 0.5) rmq.color(red: 128, green: 128, blue: 128, alpha: 0.5) rmq.color(h: 4, s: 3, b: 2, a: 1) # Add a new standard color rmq.color.add_named :pitch_black, '#000000' # Or rmq.color.add_named :pitch_black, rmq.color.black # In a stylesheet you don't need the rmq color.pitch_black color.red # have color and you need to just adjust one of the values? color(base: color.black, a: 0.5)

Added standard colors allow you to create your named colors, often in your app-wide application_stylesheet.rb or in any stylesheet’s application_setup method, for easy use and single point of change.

Back To Top

Images

RMQ provides various features for dealing with images.

If you want to load an image from your /resources folder (which is where they should be), you can either load it and cache it (imageNamed) or load it and not cache it (NSBundle.mainBundle.pathForResource):

rmq.image.resource('foo') # /resources/foo@2x.png
rmq.image.resource('foo', cached: false)
# In a stylesheet
st.background_image = image.resource('foo')

Snapshot of a view

Lastly you can get an image of a view, meaning a “screenshot” of it:

my_image_view.image = rmq.image.from_view(some_view)

Other examples

RubyMotionQuery::ImageUtils.resource('logo')
rmq.image.resource('logo')
rmq.image.resource('subfolder/foo')
rmq.image.resource_for_device('logo') # will look for 4inch or 3.5inch
rmq.image.resource('logo', cached: false)

rmq.image.resource_resizable('foo', left: 10, top: 10, right: 10, bottom: 10)

rmq.image.from_view(my_view)

# In a stylesheet you don't need the rmq
image.resource('logo')

Back To Top

Capped Images

Sometimes when you apply a background_image to a view you want the image to stretch to the size of the view without stretching the corners of the image, for example if you’re making a rounded button. The SDK has a nice feature for this, called UIImage#resizableImageWithCapInsets. It stretches the center of your image, but not the corners.

Let’s say you want to create this, like we did in Temple:

Bar

The red bar grows horizontally. But it has rounded caps. So we created this image Cap image, which is the caps, plus one pixel to stretch. Here it is blown up and I dimmed the 4 caps:

Cap image

Basically just the center line of it stretches, the other 4 quadrants do not. RMQ makes this very easy. You create a UIImageView, then in the style (or anywhere) you set the image like so:

rmq.append(UIImageView, :your_style)
# Then in your style
st.image = image.resource_resizable('your_image', top: 4, left: 4, right: 4, bottom: 4)

The top, left, etc, tell which parts of the image not to stretch. You can now resize your view and it will look great.

Back To Top

Fonts

rmq.font.system(12)
rmq.font.font_with_name('Helvetica', 18)
rmq.font.family_list # useful in console
rmq.font.for_family('Helvetica') # useful in console

# Add a new standard font
font_family = 'Helvetica Neue'
font.add_named :large, font_family, 36
font.add_named :medium, font_family, 24
font.add_named :small, font_family, 18

# then use them like so
rmq.font.large
rmq.font.small

# In a stylesheet you don't need the rmq
font.medium
font.system(14)

Back To Top

Format

A performant way to format numbers and dates.

rmq.format.number(1232, '#,##0.##')
rmq.format.date(Time.now, 'EEE, MMM d, ''yy')
rmq.format.numeric_formatter(your_format_here) # returns cached numeric formatter
rmq.format.date_formatter(your_format_here) # returns cached date formatter

See http://www.unicode.org/reports/tr35/tr35-19.html#Date_Format_Patterns for more information about date format strings.

Back To Top

Validation with RMQ

RMQ allows a RubyMotion validation library to help you to validate data in a way that makes life easy.

There are two main aspects, so you can utilize RMQ validation to meet your needs:

  • Validation Utility – Simple validation methods
  • RMQ Validation Selection Rules – Dynamically apply validation rules to input

QUICK EXAMPLES:

# Examples of the Utility
rmq.validation.valid?('https://www.infinitered.com', :url) #true
rmq.validation.valid?(98.6, :number) #true

# Examples of Selection Rules
rmq.append(UITextField, :user).validates(:email)
rmq.append(UITextField, :password).validates(:strong_password)
rmq.append(UITextField, :pin).validates(:digits).validates(:length, exact_length: 5)

rmq(UITextField).valid? # checks if selected is valid
rmq(:password).clear_validations! #removes validations on selected

All validations are tied to RMQ Debugging mode. So you can turn them off in the application by entering debug mode. You can do so by starting your project with the flag set to true rake rmq_debug=true OR by setting the flag in the code/REPL with RubyMotionQuery::RMQ.debugging = true.

This allows you to quickly disable validation in your entire application during debugging.

RMQ Validation Utility

RMQ Comes with a simple utility to validate common forms of data input. Each validation type can support options specific to that validation rule.

The following validation types are baked in for the utility:

  • :email – Email address
  • :url – URL (including http(s))
  • :dateiso – ISO Date e.g. ‘2014-03-02’
  • :number – Any Real number e.g. 62.5
  • :digits – Any Natural numbers e.g. 65
  • :ipv4 – IPV4 addresses
  • :time – Military time
  • :uszip – US Zip code with optional extended 4
  • :ukzip – UK postal code
  • :usphone – US 7 OR 10 digit phone number with dots, dashes, or spaces for separators
  • :strong_password – Any string of at least 8 characters comprised of numbers, uppercase, and lowercase input
  • :has_upper – True if any uppercase letters
  • :has_lower – True if any lowercase letters
  • :presence – True with any non-whitespace input
  • :length – Allows you to validate length restrictions on input
  • :custom – Validates using whatever is passed in the `regex` parameter

Most of these are built from a collection of regular expressions that are fed into a regex_match? method. You can send a pull-request or use the custom rule to handle your own validations.

# examples
rmq.validation.valid?('test@test.com', :email) # returns true

rmq.validation.valid?('5045558989', :usphone) # returns true

rmq.validation.valid?('K1A 0B1', :uszip) # returns false

# Length takes a wide selection of input to facilitate your length validation needs
rmq.validation.valid?('test', :length, exact_length: 4) #true
rmq.validation.valid?('test', :length, min_length: 4) #true
rmq.validation.valid?('test', :length, max_length: 4) #true
# ranges
rmq.validation.valid?('test', :length, min_length: 2, max_length: 7) #true
rmq.validation.valid?('test', :length, exact_length: 2..7) #true
# strip whitespace
rmq.validation.valid?(' test ', :length, max_length: 5, strip: true) #true

# roll your own validation rule
rmq.validation.valid?('nacho', :custom, regex: Regexp.new('nachos?')) #true (could also do /nachos?/ notation

Universal Options

Some validation optinos are unique to that particular validation rule, but all validation rules have the following Universal Options:

allow_blank

By default all these tools are strict, but you can set the allows_blank option to true, which will cause a particular validation to return true IF it the input is blank. This can be useful in situations where data is optional.

e.g. Only validate weight is numeric if they entered a value for it.

white_list

In some situations you may have an outlying exception to the validation rule. Exceptions can be validated as true using the white_list parameter:

rmq.validation.valid?(some_url_input, :url, white_list: ['http://localhost:8080', 'http://localhost:3000'])
# => true for 'http://localhost:3000' even though it's not going to pass URL (missing TLD)

RMQ Validation Selection Rules

For a more robust use of RMQ and validation, there’s an arsenal of RMQ integrated validation options and events. ANY utility rule can be used in a selection validation. Simply chain the validation you’d like applied to the UIViews you’ve selected with RMQ. For example:

# attach validation rules to UIViews
rmq.append(UITextField, :weight).validates(:digits)
rmq.append(UITextField, :name).validates(:presence)

# Later, you can check these fields to see if they have data that fits their associated validations
rmq(UITextField).valid?

Using the selection rules gets you a lot of validation help.

# Get a list of all views that were valid/invalid (updates on every .valid? call)
rmq(some_selection_criteria).invalid # returns all invalid views in the selected
rmq(some_selection_criteria).valid # returns all valid views in the selected

# you can remove validations just as easily
rmq(some_selection).clear_validations!

You can attach and fire events on the UIViews in a block.

rmq.append(UITextField).validates(:digits).
on(:valid) do |valid|
puts "This field is valid"
end.
on(:invalid) do |invalid|
puts "This field is invalid"
end

# The above will fire their events when validation gets called
rmq.all.valid?
# You can can call valid? during .on(:change) if you'd like your valid/invalid
# blocks to run instantaneously with given input.

Also, you can even get friendly error messages

# start off with a validated input
rmq.append(UITextField, :user).validates(:email)
rmq.all.valid? #run validation check

# Returns array of error messages for every invalid item in selection
rmq.all.validation_errors
# E.G ["Validation Error - input requires valid email."]

You can customize these error messages per validation rule by setting them in the stylesheet

# here in the stylesheet for this controller
def user(st)
st.validation_errors = {
email: "Please check your user email, and try again"
}
end

How do I add my own validations?

There are a few excellent ways to add your own validations. If you simply need to make exceptions for existing validations, consider looking into using white_list. If you need to use your own custom regex or validation code, then the following 2 methods are extremely useful.

custom rule

If your validation is specific to a single form, we suggest taking advantage of using the custom validation rule.

some_field = rmq.append(UITextField).validates(:custom, regex: /^test$/)
some_field.data("test")
some_field.valid?
# => true

add_validator

You can add your own validation with the add_validator method (Perhaps in your Application Stylesheet setup). Additionally, you’re not limited to the bounds of a single regex. You can use the ruby methods you know and understand, as well as multiple parameters passed in opts.

rmq.validation.add_validator(:start_with) do |value, opts|
value.start_with?(opts[:prefix])
end

You can then use your new validator in your application:

some_field = rmq.append(UITextField).validates(:start_with, prefix: 'x')
some_field.data("test")
some_field.valid? # => false
some_field.data("xenophobia")
some_field.valid? # => true

If you find yourself needing a particular validation rule often, please patch back to RMQ’s default rules with a Pull Request!

Back To Top

Utils

These are mostly used internally by rmq.

RubyMotionQuery::RMQ.is_class?(foo)
RubyMotionQuery::RMQ.is_blank?(foo)
RubyMotionQuery::RMQ.controller_for_view(view)
RubyMotionQuery::RMQ.view_to_s(view)
RubyMotionQuery::RMQ.weak_ref(foo)

Back To Top

Debugging

Adding rmq_debug=true to rake turns on some debugging features that are too slow or verbose to include in a normal build. It’s great for normal use in the simulator, but you’ll want to leave it off if you’re measuring performance.

rake rmq_debug=true

Use this to add your optional debugging code

RubyMotionQuery::RMQ.debugging?
=> true

You can even change the value from the REPL (useful for turning on and off features)

RubyMotionQuery::RMQ.debugging = true
=> true

rmq.debug.colorize – Often times, you may want high contrast on a selection of items, so you can lay them out or identify them on the screen. One common practice we use is to assign all the selected with a random background color. Since this is so common, we’ve created a shortcut method to help.

rmq(<selected items here, usually UIView or some class>).debug.colorize

Other Debugging Items

rmq.log :tree
rmq.all.log
rmq.all.log :wide

rmq(Section).log :tree
# 163792144 is the ID a button
rmq(163792144).style{|st| st.background_color = rmq.color.blue}

rmq(Section).children.and_self.log :wide

rmq(UILabel).animations.blink

# Show subview index and thus zorder of Section within Section's parent
rmq(Section).parent.children.log

Back To Top

3 - Layout and Grid

Rect system

layout

Update a view(s) frame or bounds

You can update the rectangle of a view in various ways, each way can take any of the types below (hash, array, CGRect, etc):

# In style
st.frame = {l: 10, t: 80, w: 50, h: 20}

# One or more views using .layout
rmq(view_1, view_2).layout(l: 10, t: 80, w: 50, h: 20)
rmq.append(UIButton, :button_style).layout(l: 10, t: 80, w: 50, h: 20)

# One or more views using .frame
rmq(view_1).frame = {l: 10, t: 80, w: 50, h: 20}

# Using move or resize
rmq(view_1).move(l: 10, t: 80)
rmq(view_1).resize(w: 10, h: 80)

# Animate it
rmq(my_view_1).animate{|q| q.move(l: 80)}

You can update a frame or bounds with the following:

  • hash (preferred)
  • another RubyMotionQuery::Rect
  • array
  • array of array
  • CGPoint
  • CGSize
  • CGRect

Hash

The params are always applied in this order, regardless of the hash order:

  1. grid
  2. l, t, w, h
  3. previous
  4. from_right, from_bottom
  5. right, bottom
  6. left and from_right applied together (will change width)
  7. top and from_bottom applied together (will change height)
Options for layout, move, size, frame, & bounds:
  • :full
  • :left :l
  • :right :r
  • :from_right :fr
  • :top :t
  • :bottom :b
  • :from_bottom :fb
  • :width :w
  • :height :h
  • :right_of_prev :rop
  • :left_of_prev :lop
  • :below_prev :bp
  • :above_prev :ap
  • :centered (:vertical, :horizontal, :both)

Options for nudge:

  • :left :l
  • :right :r
  • :up :u
  • :down :d

Values for each option can be:

  • signed integer
  • integer
  • float
  • string

Strings are for the grid:

  • ‘a1:b4’
  • ‘a1’
  • ‘a’
  • ‘1’
  • ‘:b4’

Previous

You can use :below_prev: 10, right_of_prev: 20, etc. Prev refers to the previous sibling of this view.

Some examples

st.frame = {l: 10, t: 20, w: 100, h: 150}
st.frame = {t: 20, h: 150, l: 10, w: 100}
rmq(my_view).layout(l: 10, t: 20)
rmq(my_view).move(h: 20)
rmq(my_view).layout(l: :prev, t: 20, w: 100, h: 150)
rmq(my_view).frame = {l: 10, below_prev: 10, w: prev, h: 150}
rmq(my_view).frame = {left: 10, top: 20, width: 100, height: 150}
rmq(my_view).frame = {l: 10, t: 10, fr: 10, fb: 10}
rmq(my_view).frame = {width: 50, height: 20, centered: :both}
rmq(my_view, my_other_view).frame = {grid: "b2", w: 100, h: 200}
# In a style
def some_style(st)
  st.frame = {l: 20, t: 20, w: 100, h: 50}
end

# Layout, move, or resize selected views
rmq(your_view).layout(left: 20, top: 20, width: 100, height: 50)
rmq(your_view).layout(l: 20)
rmq(your_view).layout(left: 20)
rmq(your_view).layout(l: 20, t: 20, r: 20, b: 20)
rmq(your_view).layout(left: 20, top: 20, width: 100, height: 50)

rmq(your_view).move(left: 20) # alias for layout
rmq(your_view).move(l: 30, t: 50) # alias for layout
rmq(your_view).resize(width: 100, height: 50) # alias for layout

# Nudge pushes them in a direction
rmq(your_view).nudge(d: 20)
rmq(your_view).nudge(down: 20)
rmq(your_view).nudge(l: 20, r: 20, u: 100, d: 50)
rmq(your_view).nudge(left: 20, right: 20, up: 100, down: 50)

# Other
rmq(your_view).resize_to_fit_subviews
rmq(your_view).resize_content_to_fit_subviews

Getting the Rect for a view or views

rect = rmq(view).frame
rect.log
puts rect
puts rect.left

# Use it directly
rmq(my_view).move(l: rmq(my_other_view).frame.l)

# If you select more than one view, you'l get an array of Rect objects
a = rmq(UIView).frame

Back To Top

Layout a screen

This example applies layouts in the stylesheet (thus why it’s st.frame =), but you could also apply them using the .layout method.

rect_example

Notice the use of :left and :from_right together, instead of :width. This is very handy. For example if you want to place your view inside its superview (parent) and give a 5 pixel border, you would do this:

st.frame = {l: 5, t: 5, fr: 5, fb: 5}

8, 9, and 10 are interesting. I placed 8 on the bottom, then put 9 above_prev: 5, and then 10 above_prev: 5. You could do this differently, but that’s pretty slick.

Back To Top

Distribute

rmq(UIButton).distribute
rmq(UIButton).distribute(:vertical)
rmq(UIButton).distribute(:horizontal)
rmq(UIButton).distribute(:vertical, margin: 20)
rmq(my_view, my_other_view, third_view).distribute(:vertical, margin: 10)
rmq(UIButton).distribute(:vertical, margins: [5,5,10,5,10,5,10,20])

Back To Top

Grid

You can use grid or grids for layout. There is an app grid at: rmq.app.grid

You can also have a grid per stylesheet. If none exists, the app grid will be used. rmq.stylesheet.grid (this will return app’s if nil).

If you want to create a grid for your stylesheet, you can just dup the app one like so (inside the stylesheet): self.grid = rmq.app.grid.dup. Then you can mod it: self.grid.num_columns = 6

The grid applies to the entire controller root view. It’s always fullscreen.


grid_example_01


If you want your view to be from b2 to d3, you can do any of the following:

# a b c d e
# ....................
# 1 . a1 b1 c1 d1 e1
# .....------------...
# 2 . a2 |b2 c2 d2| e2
# .....| |...
# 3 . a3 |b3 c3 d3| e3
# .....------------...
# 4 . a4 b4 c4 d4 e4
# ....................
# 5 . a5 b5 c5 d5 e5
# ....................

st.frame = "b2:d3"
st.origin = "b2:d3"
st.frame = {grid: "b2", w: 100, h: 200}
my_view.frame = some_grid['b2:d3']
my_view.origin = some_grid['b2:d3']
rmq.append(UIView).layout('b2:d3')

Create a new grid inside a stylesheet by duping the app’s grid

self.grid = rmq.app.grid.dup
# Then change the number of columns
self.grid.num_columns = 6

Create a new grid in a stylesheet (from scratch)

# Commonly in the setup for that stylesheet
# this custom grid works best for this view
self.grid = RubyMotionQuery::Grid.new({
content_left_margin: 10,
content_right_margin: 10,
content_top_margin: 70,
content_bottom_margin: 10,
num_columns: 10,
column_gutter: 10,
num_rows: 16,
row_gutter: 10
})

Modify an existing grid

rmq.app.grid.num_rows = 4

A good place modify the app grid is in your application_stylesheet:


class ApplicationStylesheet < RubyMotionQuery::Stylesheet def application_setup rmq.app.grid.tap do |g| g.content_left_margin = 10 g.content_right_margin = 10 g.content_top_margin = 70 g.content_bottom_margin = 10 g.num_columns = 15 g.column_gutter = 5 g.num_rows = 20 g.row_gutter = 5 end end end

Align all your buttons left on the c column

rmq(UIButon).layout('c')

Log your grid

rmq.app.grid.log

# {:num_columns=>10, :num_rows=>13, :column_gutter=>10, :content_left_margin=>5,
# :content_right_margin=>5, :content_top_margin=>5, :content_bottom_margin=>5,
# :row_gutter=>10, :status_bar_bottom=>20, :nav_bar_bottom=>64}

# a b c d e f g h i j
# 0 . . . . . . . . . .
# 1 . . . . . . . . . .
# 2 . . . . . . . . . .
# 3 . . . . . . . . . .
# 4 . . . . . . . . . .
# 5 . . . . . . . . . .
# 6 . . . . . . . . . .
# 7 . . . . . . . . . .
# 8 . . . . . . . . . .
# 9 . . . . . . . . . .
# 10 . . . . . . . . . .
# 11 . . . . . . . . . .
# 12 . . . . . . . . . .

Padding

Padding is a useful when using grids. It adds additional padding, pushing a view inwards, it is applied after everything else is applied. A padding of 5 will take the frame and move all 4 sides inwards 5.

Here is an example:

rmq(view1).layout('b6:h10')
rmq(view1).append(UIView).layout(grid:'b9:h10', padding: 2)

padding_example

See how the grid is applied, then padding is added around the view. You can also set each side independently, or only set one or a few sides:

rmq(view1).layout('b6:h10', padding: {l: 2, t: 5, r: 2, b: 5})
rmq(view1).layout('b6:h10', padding: {t: 5, b: 5})

You can set left (l), top (t), right (r), and bottom (b).

Misc

st.frame = "a1:b5"
st.frame = "a1"
rmq(my_view).layout(":b5")
st.frame = "a"
rmq(my_view, my_other_view).frame = "1"
st.frame = ":b"
st.frame = ":b5"
rmq(my_view, my_other_view).frame = {grid: "b2", w: 100, h: 200}

Some sample grid settings

This is a nice fine-grain grid, which is divisible by 2, 3, and 4:

def application_setup

rmq.app.grid.tap do |g|
g.content_left_margin = 10
g.content_right_margin = 10
g.content_top_margin = 74
g.content_bottom_margin = 10
g.num_columns = 12
g.column_gutter = 10
g.num_rows = 18
g.row_gutter = 10
end

end

12_18_grid


I coarse-grain version of the previous one. This one has a smaller margin on top and bottom which is a little nicer:

def application_setup

rmq.app.grid.tap do |g|
g.content_left_margin = 10
g.content_right_margin = 10
g.content_top_margin = 69
g.content_bottom_margin = 5
g.num_columns = 12
g.column_gutter = 10
g.num_rows = 12
g.row_gutter = 10
end
end

12_12_grid

Back To Top

Show the Grid from the REPL

The RMQ Grid is an extremely useful and dynamic way to lay out views on a device screen. You want people to see the effects of your grid system, but not the grid itself. Fortunately RMQ has a quick way to turn on and off the grid overlay from the REPL. This allows you to quickly glance over the grid while accomplishing pixel-perfect layouts.

(main)> rmq.app.grid.show
[RMQ] Tap to dismiss the grid overlay

rmq.app.grid.show

Back To Top

4 - Styles and Stylesheets

Stylesheets Overview

A very lightweight style system, designed for a low memory footprint, fast startup, and fast operation. Most everything is done at compile-time, as it’s all just ruby code. Low magic.

They are called styles and stylesheets, but they are used for layout also. Basically if it changes the way a user sees something, it belongs in a stylesheet.

  • rmq’s stylesheets are very simple, contain no (well, a little) magic, and are fast, as much as possible is done at compile time, not runtime
  • As a styling and layout layer, it has the important features without sacrificing speed or simplicity
  • You can style for different devices, device sizes, orientations, etc (see test app for an example of this)
  • Exceptions tell you exactly where in your stylesheet the exception occurred

stylesheet_diagram_overview

  • A style is simply a method in the stylesheet class. It is passed a styler
  • You can apply a style to a view or views at anytime. They override existing settings

Apply a style when creating the view

rmq.append UILabel, :title_label

Apply a style to existing views

rmq(your_view, your_other_view).apply_style(:title_label)

Apply styles inline (you should use an existing style instead)

def title_label(st)
  st.frame = {l: 10, t: 10, w: 200, h: 96}
  st.image = rmq.image.resource('logo')
end

Lots of methods exist for adding/building/adjusting views add styles. You can apply a style in multiple locations.

RMQ method sequence stack
rmq method sequence.

Reapply styles – If your view updates

If a view has multiple styles applied to it and then you make some modifications to your view or controller, you can then call reapply_styles selected view(s) so that all styles will be reapplied again in the correct order from one simple method. “Write once, reapply anytime!”

Most commonly used when new items are dynamically loaded, or if width/height of a view are changed (like on rotation).

def willAnimateRotationToInterfaceOrientation(orientation, duration: duration)
  rmq.all.reapply_styles
end

Layouts

Anything that can be applied using the grid or rect system (see Layout below). Can be applied with st.frame =

rmq(your_view).style do |st|
   st.frame = {grid: 'a1:c', h: 96}
end

Previous view

In addition to Layout’s :below_prev, :right_of_prev, etc. You can get the previous frame and view like so:

def foo(st)
   st.frame = {l: 10, t: st.prev_frame.top, w: 200, h: 96}
   st.text = st.prev_view.text
end

A simple example of controller & stylesheet

foo_controller.rb

class FooController < UIViewController
  def viewDidLoad
    super

    rmq.stylesheet = FooStylesheet
    rmq(self.view).apply_style :root_view

    rmq.append UILabel, :title_label
    rmq.append UIImageView, :logo
  end
end

foo_stylesheet.rb

class FooStylesheet < RubyMotionQuery::Stylesheet
  def root_view(st)
    st.background_color = color.white
  end

  def label(styler)
    styler.background_color = color.clear
  end

  def logo(st)
    st.frame = {t: 10, w: 200, h: 96, centered: :horizontal}
    st.image = image.resource('logo')
    st.view.someAttributeNotInStyler = 'foo'
  end

  def title_label(st)
    label st # combine 0 or more styles
    st.frame = {l: 5, below_prev: 5, w: 200, h: 20}
    st.text = 'Test label'
    st.color = color.from_hex('#3F3F3F')
  end
end

Back To Top

Application stylesheet

You’ll probably want an application-wide stylesheet. Simple create one, and then inherit it in all your stylesheets.

If you use rmq to create your app, it will automatically create this for you. Even if you don’t, I’d create a test app to see how it is all setup: rmq create test_app

application_stylesheet.rb

class ApplicationStylesheet < RubyMotionQuery::Stylesheet
  def label(styler)
    styler.background_color = color.clear
  end
end

foo_stylesheet.rb

class FooStylesheet < ApplicationStylesheet
  def bar(st)
    ...
  end
end

*** Here is a longer example

class ApplicationStylesheet < RubyMotionQuery::Stylesheet

  def application_setup

    # App grid, used by default throughout the applicaiton
    rmq.app.grid.tap do |g|
      g.content_left_margin = 10
      g.content_right_margin = 10
      g.content_top_margin = 74 
      g.content_bottom_margin = 10 
      g.num_columns =  12 
      g.column_gutter = 10 
      g.num_rows = 18 
      g.row_gutter = 10 
    end

    # Named fonts
    font_family = 'Helvetica Neue'
    font.add_named :very_large,     font_family, 56
    font.add_named :large,          font_family, 24 
    font.add_named :medium,         font_family, 18 
    font.add_named :standard,       font_family, 16 
    font.add_named :small,          font_family, 14 
    font.add_named :tiny,           font_family, 12 

    # Named colors
    color.add_named :tint, '#438AF0' 
    color.add_named :gray, '#DDDDDD' 
    color.add_named :light_gray, '#EDEDED' 
    color.add_named :very_light_gray,'#F4F4F4' 

    # Set other application-wide visual things, such as appearances:
    SVProgressHUD.appearance.hudBackgroundColor = color.light_gray
    SVProgressHUD.appearance.hudForegroundColor = color.black
  end

  # Some standard styles you can apply or use in other styles
  def standard_button(st)
    st.frame = {h: 30}
    st.background_color = color.tint
    st.color = color.white
    st.corner_radius = 5 
    st.view.setTitleColor(color.gray, forState: UIControlStateHighlighted)
  end

  def standard_button_enabled(st)
    st.background_color = color.tint
    st.color = color.white
    st.enabled = true
  end

  def standard_button_disabled(st)
    st.background_color = color.gray
    st.color = color.white
    st.enabled = false
  end

  def shadow(st)
    st.clips_to_bounds = false
    st.view.layer.tap do |l|
      l.shadowColor = color.black.CGColor
      l.shadowOpacity = 0.2
      l.shadowRadius = 1.0
      l.shadowOffset = [0.5, 1.0]
    end
  end
end

Back To Top

Stylers

Styler

A styler wraps around a view, augmenting it with styling power and sugar.

Each UIView subclass can have its own styler (many exist in RMQ, but not all yet). There is a UIViewStyler class they all inherit from, and a UIControlStyler controls inherit from. Much of the code is in UIViewStyler.

When you create a “style method”, it will be passed the view, wrapped in a styler. You can get the view using st.view.

You can see a list of styler methods using: rmq.log_stylers

List of stylers, their methods, and some examples

UIViewStyler

All stylers inherit UIViewStyler, so these are available in any view.

  • absolute_frame=(value)
  • accessibility_label=(value)
  • alpha
  • alpha=(value)
  • background_color
  • background_color=(value)
  • background_gradient=(value)
  • background_image=(value)
  • border=(value)
  • border_color
  • border_color=(value)
  • border_width
  • border_width=(value)
  • bounds
  • bounds=(value)
  • center
  • center=(value)
  • center_x
  • center_x=(value)
  • center_y
  • center_y=(value)
  • centered=(value)
  • clips_to_bounds
  • clips_to_bounds=(value)
  • content_mode
  • content_mode=(value)
  • copyWithZone
  • corner_radius
  • corner_radius=(value)
  • corner_radius=(value)
  • enabled
  • enabled=(value)
  • frame
  • frame=(value)
  • get
  • hidden
  • hidden=(value)
  • layer
  • masks_to_bounds
  • masks_to_bounds=(value)
  • opacity
  • opacity=(value)
  • opaque
  • opaque=(value)
  • parent
  • prev_frame
  • prev_view
  • right
  • right=(value)
  • rotation=(value)
  • scale=(value)
  • shadow_color
  • shadow_color=(value)
  • shadow_offset
  • shadow_offset=(value)
  • shadow_opacity
  • shadow_opacity=(value)
  • shadow_path
  • shadow_path=(value)
  • super_height
  • super_width
  • superview
  • tag
  • tint_color
  • tint_color=(value)
  • transform
  • transform=(value)
  • user_interaction_enabled
  • user_interaction_enabled=(value)
  • validation_errors=(value)
  • view
  • view=(value)
  • view_has_been_styled?
  • z_position
  • z_position=(value)
  st.frame = {l: 1, t: 2, w: 3, h: 4}
  st.frame = {left: 1, top: 2, width: 3, height: 4}
  st.frame = {from_right: 1, from_bottom: 2, width: 3, height: 4}
  st.frame = {fr: 1, fb: 2, w: 3, h: 4}
  st.center = st.superview.center
  st.center_x = 50
  st.center_y = 60

  st.enabled = true
  st.hidden = false
  st.z_position = 66
  st.opaque = false
  st.clips_to_bounds = false
  st.hidden = true
  st.content_mode = UIViewContentModeBottomLeft

  st.background_color = color.red

  st.scale = 1.5
  st.rotation = 45
  st.tint_color = color.blue
  st.layer.cornerRadius = 5
end


#### UIControlStyler

All UIControl stylers, like a UIButton, inherit from UIControlStyler

* content_horizontal_alignment
* content_horizontal_alignment=(value)
* content_vertical_alignment
* content_vertical_alignment=(value)
* highlighted
* highlighted=(value)
* selected
* selected=(value)
* state

```ruby
def ui_control_kitchen_sink(st)
  st.content_vertical_alignment = UIControlContentVerticalAlignmentFill
  st.content_horizontal_alignment = UIControlContentHorizontalAlignmentFill
  st.selected = true
  st.highlighted = true
end

UIActivityIndicatorViewStyler

  • activity_indicator_style
  • activity_indicator_style=(value)
  • animating?
  • color
  • color=(value)
  • hides_when_stopped
  • hides_when_stopped=(value)
  • is_animating?
  • start
  • start_animating
  • stop
  • stop_animating

UIButtonStyler

  • adjust_image_when_highlighted
  • adjust_image_when_highlighted=(value)
  • attributed_text
  • attributed_text=(value)
  • attributed_text_highlighted
  • attributed_text_highlighted=(value)
  • background_image
  • background_image_highlighted
  • background_image_highlighted=(value)
  • background_image_normal
  • background_image_normal=(value)
  • background_image_selected
  • background_image_selected=(value)
  • color
  • color=(value)
  • color_highlighted
  • color_highlighted=(value)
  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • font
  • font=(value)
  • highlighted
  • highlighted=(value)
  • image
  • image=(value)
  • image_edge_insets
  • image_edge_insets=(value)
  • image_highlighted
  • image_highlighted=(value)
  • image_normal
  • image_normal=(value)
  • selected
  • selected=(value)
  • state
  • text
  • text=(value)
  • text_highlighted
  • text_highlighted=(value)
  • title_edge_insets
  • title_edge_insets=(value)
st.text = 'foo'
st.font = font.system(12)
st.color = color.red
st.image_normal = image.resource('logo')
st.image_highlighted = image.resource('logo')

UIDatePickerStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • date_picker_mode
  • date_picker_mode=(value)
  • highlighted
  • highlighted=(value)
  • selected
  • selected=(value)
  • state

UIImageViewStyler

  • image
  • image=(value)
st.image = image.resource('logo')

UILabelStyler

  • adjusts_font_size
  • adjusts_font_size=(value)
  • adjusts_font_size_to_fit_width
  • adjusts_font_size_to_fit_width=(value)
  • attributed_text
  • attributed_text=(value)
  • color
  • color=(value)
  • font
  • font=(value)
  • line_break_mode
  • line_break_mode=(value)
  • number_of_lines
  • number_of_lines=(value)
  • resize_height_to_fit
  • resize_to_fit_text
  • size_to_fit
  • text
  • text=(value)
  • text_align
  • text_align=(value)
  • text_alignment
  • text_alignment=(value)
  • text_color
  • text_color=(value)
st.text = 'rmq is awesome'
st.font = font.system(12)
st.color = color.black
st.text_alignment = :center

st.resize_to_fit_text
st.size_to_fit

UINavigationBarStyler

UIPageControlStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • current_page
  • current_page=(value)
  • current_page_indicator_tint_color
  • current_page_indicator_tint_color=(value)
  • highlighted
  • highlighted=(value)
  • number_of_pages
  • number_of_pages=(value)
  • page_indicator_tint_color
  • page_indicator_tint_color=(value)
  • selected
  • selected=(value)
  • state

UIProgressViewStyler

  • progress_image
  • progress_image=(value)
  • progress_tint_color
  • progress_tint_color=(value)
  • progress_view_style
  • progress_view_style=(value)
  • track_image
  • track_image=(value)
  • track_tint_color
  • track_tint_color=(value)

UIRefreshControlStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • highlighted
  • highlighted=(value)
  • selected
  • selected=(value)
  • state

UIScrollViewStyler

  • bounces
  • bounces=(value)
  • content_inset
  • content_inset=(value)
  • content_offset
  • content_offset=(value)
  • content_size
  • content_size=(value)
  • direction_lock
  • direction_lock=(value)
  • indicator_style
  • indicator_style=(value)
  • paging
  • paging=(value)
  • scroll_enabled
  • scroll_enabled=(value)
  • scroll_indicator_insets
  • scroll_indicator_insets=(value)
  • shows_horizontal_scroll_indicator
  • shows_horizontal_scroll_indicator=(value)
  • shows_vertical_scroll_indicator
  • shows_vertical_scroll_indicator=(value)
st.paging = true
st.scroll_enabled = true
st.direction_lock = false
st.content_offset = CGPointMake(5, 10)
st.content_inset = CGPointMake(-100, 0)
st.bounces = false
st.content_size = CGSizeMake(320, 500)
st.shows_horizontal_scroll_indicator = true
st.shows_vertical_scroll_indicator = false
st.scroll_indicator_insets = UIEdgeInsetsMake (10, 0, 20, 0)

UISegmentedControlStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • highlighted
  • highlighted=(value)
  • prepend_segments=(value)
  • selected
  • selected=(value)
  • state
  • unshift=(value)

UISliderStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • highlighted
  • highlighted=(value)
  • selected
  • selected=(value)
  • state

UIStepperStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • highlighted
  • highlighted=(value)
  • selected
  • selected=(value)
  • state

UISwitchStyler

  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • highlighted
  • highlighted=(value)
  • on
  • on=(value)
  • selected
  • selected=(value)
  • state
st.on = true

UITabBarStyler

UITableViewCellStyler

  • accessory_type
  • accessory_type=(value)
  • accessory_view
  • accessory_view=(value)
  • color
  • color=(value)
  • detail_color
  • detail_color=(value)
  • detail_font
  • detail_font=(value)
  • detail_text_color
  • detail_text_color=(value)
  • font
  • font=(value)
  • selection_style
  • selection_style=(value)
  • separator_inset
  • separator_inset=(value)
  • text_color
  • text_color=(value)

UITableViewStyler

  • allows_selection
  • allows_selection=(value)
  • background_image
  • bounces
  • bounces=(value)
  • content_inset
  • content_inset=(value)
  • content_offset
  • content_offset=(value)
  • content_size
  • content_size=(value)
  • direction_lock
  • direction_lock=(value)
  • indicator_style
  • indicator_style=(value)
  • paging
  • paging=(value)
  • row_height=(value)
  • scroll_enabled
  • scroll_enabled=(value)
  • scroll_indicator_insets
  • scroll_indicator_insets=(value)
  • separator_color
  • separator_color=(value)
  • separator_inset=(value)
  • separator_style
  • separator_style=(value)
  • shows_horizontal_scroll_indicator
  • shows_horizontal_scroll_indicator=(value)
  • shows_vertical_scroll_indicator
  • shows_vertical_scroll_indicator=(value)

UITextFieldStyler

  • adjusts_font_size
  • adjusts_font_size=(value)
  • adjusts_font_size_to_fit_width
  • adjusts_font_size_to_fit_width=(value)
  • allows_editing_text_attributes
  • allows_editing_text_attributes=(value)
  • attributed_placeholder
  • attributed_placeholder=(value)
  • attributed_text
  • attributed_text=(value)
  • autocapitalization_type
  • autocapitalization_type=(value)
  • autocorrection_type
  • autocorrection_type=(value)
  • background
  • background=(value)
  • border_style
  • border_style=(value)
  • clear_button_mode
  • clear_button_mode=(value)
  • clears_on_begin_editing
  • clears_on_begin_editing=(value)
  • clears_on_insertion
  • clears_on_insertion=(value)
  • color
  • color=(value)
  • content_horizontal_alignment
  • content_horizontal_alignment=(value)
  • content_vertical_alignment
  • content_vertical_alignment=(value)
  • default_text_attributes
  • default_text_attributes=(value)
  • disabled_background
  • disabled_background=(value)
  • editing
  • editing=(value)
  • enables_return_key_automatically
  • enables_return_key_automatically=(value)
  • font
  • font=(value)
  • highlighted
  • highlighted=(value)
  • keyboard_appearance
  • keyboard_appearance=(value)
  • keyboard_type
  • keyboard_type=(value)
  • left_view
  • left_view=(value)
  • left_view_mode
  • left_view_mode=(value)
  • minimum_font_size
  • minimum_font_size=(value)
  • placeholder
  • placeholder=(value)
  • return_key_type
  • return_key_type=(value)
  • right_view
  • right_view=(value)
  • right_view_mode
  • right_view_mode=(value)
  • secure_text_entry
  • secure_text_entry=(value)
  • selected
  • selected=(value)
  • spell_checking_type
  • spell_checking_type=(value)
  • state
  • text
  • text=(value)
  • text_alignment
  • text_alignment=(value)
  • text_color
  • text_color=(value)
  • typing_attributes
  • typing_attributes=(value)

UITextViewStyler

  • attributed_text
  • attributed_text=(value)
  • bounces
  • bounces=(value)
  • color
  • color=(value)
  • content_inset
  • content_inset=(value)
  • content_offset
  • content_offset=(value)
  • content_size
  • content_size=(value)
  • data_detector_types
  • data_detector_types=(value)
  • direction_lock
  • direction_lock=(value)
  • editable
  • editable=(value)
  • font
  • font=(value)
  • indicator_style
  • indicator_style=(value)
  • paging
  • paging=(value)
  • scroll_enabled
  • scroll_enabled=(value)
  • scroll_indicator_insets
  • scroll_indicator_insets=(value)
  • selectable
  • selectable=(value)
  • shows_horizontal_scroll_indicator
  • shows_horizontal_scroll_indicator=(value)
  • shows_vertical_scroll_indicator
  • shows_vertical_scroll_indicator=(value)
  • text
  • text=(value)
  • text_alignment
  • text_alignment=(value)
  • text_color
  • text_color=(value)

Examples

  st.frame = {l: 1, t: 2, w: 3, h: 4}
  st.frame = {left: 1, top: 2, width: 3, height: 4}
  st.frame = {from_right: 1, from_bottom: 2, width: 3, height: 4}
  st.frame = {fr: 1, fb: 2, w: 3, h: 4}
  st.center = st.superview.center
  st.center_x = 50
  st.center_y = 60

  st.enabled = true
  st.hidden = false
  st.z_position = 66
  st.opaque = false
  st.clips_to_bounds = false
  st.hidden = true
  st.content_mode = UIViewContentModeBottomLeft

  st.background_color = color.red

  st.scale = 1.5
  st.rotation = 45
  st.tint_color = color.blue
  st.layer.cornerRadius = 5
end
UIControlStyler
st.content_vertical_alignment = UIControlContentVerticalAlignmentFill
st.content_horizontal_alignment = UIControlContentHorizontalAlignmentFill
st.selected = true
st.highlighted = true
UILabelStyler
st.text = 'rmq is awesome'
st.font = font.system(12)
st.color = color.black
st.text_alignment = :center

st.resize_to_fit_text
st.size_to_fit
UIButtonStyler
st.text = 'foo'
st.font = font.system(12)
st.color = color.red
st.image_normal = image.resource('logo')
st.image_highlighted = image.resource('logo')
UIImageViewStyler
st.image = image.resource('logo')
UIScrollViewStyler
st.paging = true
st.scroll_enabled = true
st.direction_lock = false
st.content_offset = CGPointMake(5, 10)
st.content_inset = CGPointMake(-100, 0)
st.bounces = false
st.content_size = CGSizeMake(320, 500)
st.shows_horizontal_scroll_indicator = true
st.shows_vertical_scroll_indicator = false
st.scroll_indicator_insets = UIEdgeInsetsMake (10, 0, 20, 0)
UISwitchStyler
  • on=(value)
st.on = true

Back To Top

Add your own styler

In the example app, look in /app/stylers, you can just copy that whole folder to start. Then add methods to the appropriate class.

Here is an example of adding a method to all stylers:

module RubyMotionQuery
  module Stylers
    class UIViewStyler

      def border_width=(value)
        @view.layer.borderWidth = value
      end
      def border_width
        @view.layer.borderWidth
      end

    end
  end
end

You can also include all of your custom stylers in one file, which works well if you don’t have a lot.

Back To Top

Example Stylesheet

This stylesheet uses normal rect layout (left, right, etc). It could also be done using the grid.

I purposely used a variety of different things in this stylesheet to show different examples. In real life, I personally would use fr:, not from_right:. Both are fine, but I’d be consistent.

class MainStylesheet < ApplicationStylesheet
  def setup
    # Add stylesheet specific setup stuff here.
    # Add application specific setup stuff in application_stylesheet.rb

    @padding = 10
  end

  def root_view(st)
    st.background_color = color.white
  end

  def logo(st)
    st.frame = {top: 77, width: 200, height: 95.5, centered: :horizontal }
    st.image = image.resource("logo")  # for logo@2x.png or logo.png
  end

  def make_labels_blink_button(st)
    st.frame = {fr: @padding, t: 180, w: 150, h: 20}
    # That is exactly the sames as st.frame = {from_right: @padding, top: 180, width: 150, height: 20}
    # Each frame/bounds hash option has an abbreviation

    # ipad? (and landscape?, etc) is just a convenience methods for
    # rmq.device.ipad?

    # Here is a complete example of different formatting for orientatinos
    # and devices
    #  if ipad?
    #    if landscape?
    #      st.frame = {l: 20, t: 120, w: 150, h: four_inch? ? 20 : 30}
    #    else
    #      st.frame = {l: 90, t: 120, w: 150, h: four_inch? ? 25 : 35}
    #    end
    #  else
    #    if landscape?
    #      st.frame = {l: 20, t: 20, w: 150, h: four_inch? ? 22 : 32}
    #    else
    #      st.frame = {l: 90, t: 20, w: 150, h: four_inch? ? 30 : 40}
    #    end
    #  end

    # If you don't want something to be reapplied when reapply_styles is called
    unless st.view_has_been_styled?
      st.text = "Blink labels"
      st.font = font.system(10)
      st.color = color.white

      # You can hardcode colors in styles like this, but it's better to use named colors
      st.background_color = color("ed1160") 
    end
  end

  def make_buttons_throb_button(st)
    st.frame = {from_right: @padding, below_prev: @padding, width: 150, height: 20}
    st.text = "Throb buttons"
    st.color = color.black
  end

  def animate_move_button(st)
    st.scale = 1.0
    st.frame = {fr: @padding, bp: 5, w: 150, h: 20}
    st.text = "Animate move and scale"
    st.font = font.system(10)
    st.color = color.white
    st.background_color = color("ed1160")
    st.z_position = 99
    st.color = color.white
  end

  def section(st)
    st.frame = {fb: @padding, w: 270, h: 110}

    st.frame = if landscape? && iphone?
      {left: @padding }
    else
      {centered: :horizontal}
    end

    st.z_position = 1
    st.background_color = color.battleship_gray # This is a named color
  end

  def section_label(st)
    label st # This is an example of calling a style that you created in the ApplicationStylesheet
    st.color = color.white
  end

  def popup_section(st)
    t = (landscape? && iphone?) ? 100 : 180
    st.frame = {l: @padding, t: t, w: 100 + (@padding * 2), h: 60}
    st.background_color = color.blue
  end

  def popup_text_label(st)
    label st # This is an example of calling a style that you created in the ApplicationStylesheet
    st.text_alignment = :left
    st.color = color.black
    st.font = font.system(10)
    st.text = "This is a Popup, woot!"

    # If the styler doesn't have the method, you can add it or
    # just use st.view to get the actual object
    st.view.lineBreakMode = NSLineBreakByWordWrapping
  end
end

Back To Top

5 - Animate

Animate

The most basic:

rmq.animate do
  rmq(UIButton).nudge r: 40
end

A better way to do that is select something first. In this case q is an RMQ instance selecting all UIButtons:

rmq(UIButton).animate do |q|
  q.nudge r: 40
end

Some more options, this time it is animating a selected view:

rmq(my_view).animate(
  duration: 0.3,
  animations: lambda{|q|
    q.move left: 20
  }
)
# As an example, this is the implementation of .animations.throb
rmq(selectors).animate(
  duration: 0.1,
  animations: -> (q) {
    q.style {|st| st.scale = 1.1}
  },
  completion: -> (did_finish, q) {
    q.animate(
      duration: 0.4,
      animations: -> (cq) {
        cq.style {|st| st.scale = 1.0}
      }
    )
  }
)

# You can pass any options that animateWithDuration allows: options: YOUR_OPTIONS

Back To Top

Canned Animations

rmq(my_view).animations.fade_in
rmq(my_view).animations.fade_in(duration: 0.8)
rmq(my_view).animations.fade_out
rmq(my_view).animations.fade_out(duration: 0.8)

rmq(my_view).animations.blink
rmq(my_view).animations.throb
rmq(my_view).animations.sink_and_throb
rmq(my_view).animations.land_and_sink_and_throb
rmq(my_view).animations.drop_and_spin # has optional param 'remove_view: true'

rmq(my_view).animations.slide_in # default from_direction: :right
rmq(my_view).animations.slide_in(from_direction: :left) # acceptable directions :left, :right, :top, :bottom

rmq(my_view).animations.slide_out # default to_direction: :left, also accepts :right, :top, and :bottom
rmq(my_view).animations.slide_out(remove_view: true) # removes the view from superview after animation
rmq(my_view).animations.slide_out(to_direction: :top, remove_view: true) #combining options example

rmq.animations.start_spinner
rmq.animations.stop_spinner

Fade In

fade_in

 

Fade Out

fade_out

Blink

blink

Throb

throb

Sink and Throb

sink_and_throb

Land and Sink and Throb

land_sink_throb

Drop and Spin

drop_spin

Slide In
slide_in

Slide Out
slide_out

Back To Top

6 - I want to do X, how do I do it?

Capped Images

Sometimes when you apply a background_image to a view you want the image to stretch to the size of the view without stretching the corners of the image, for example if you’re making a rounded button. The SDK has a nice feature for this, called UIImage#resizableImageWithCapInsets. It stretches the center of your image, but not the corners.

Let’s say you want to create this, like we did in Temple:

Bar

The red bar grows horizontally. But it has rounded caps. So we created this image Cap image, which is the caps, plus one pixel to stretch. Here it is blown up and I dimmed the 4 caps:

Cap image

Basically just the center line of it stretches, the other 4 quadrants do not. RMQ makes this very easy. You create a UIImageView, then in the style (or anywhere) you set the image like so:

rmq.append(UIImageView, :your_style)
# Then in your style
st.image = image.resource_resizable('your_image', top: 4, left: 4, right: 4, bottom: 4)

The top, left, etc, tell which parts of the image not to stretch. You can now resize your view and it will look great.

Back To Top

Layout a screen

This example applies layouts in the stylesheet (thus why it’s st.frame =), but you could also apply them using the .layout method.

rect_example

Notice the use of :left and :from_right together, instead of :width. This is very handy. For example if you want to place your view inside its superview (parent) and give a 5 pixel border, you would do this:

st.frame = {l: 5, t: 5, fr: 5, fb: 5}

8, 9, and 10 are interesting. I placed 8 on the bottom, then put 9 above_prev: 5, and then 10 above_prev: 5. You could do this differently, but that’s pretty slick.

Back To Top

Show the Grid from the REPL

The RMQ Grid is an extremely useful and dynamic way to lay out views on a device screen. You want people to see the effects of your grid system, but not the grid itself. Fortunately RMQ has a quick way to turn on and off the grid overlay from the REPL. This allows you to quickly glance over the grid while accomplishing pixel-perfect layouts.

(main)> rmq.app.grid.show
[RMQ] Tap to dismiss the grid overlay

rmq.app.grid.show

Back To Top

How do I create my own custom view?

RMQ calls 4 methods when you create, append, build, or style a view using rmq. rmq_build is the one you most want to use

# Called if rmq creates the view, if you pass an existing view in, this wont' be called
def rmq_created
end

# Called anytime rmq does a create, append, or build
def rmq_build
end

# Called if rmq appended your view to its parent view (superview). If you just create a view and don't append it, this won't be called
def rmq_appended
end

# Called after rmq styles your view
def rmq_style_applied
end

If you append a view like so:

rmq.append(UILabel)

The 4 methods will be called in this order:

  • rmq_created
  • rmq_build
  • rmq_appended
  • rmq_style_applied

In the following example an instance of YourView is created, :your_style is applied
after rmq_build is called on the instance that was just created. In that
order. This allows you to override subview’s styles. So the view’s author can style with good defaults, then when you add view to your view you can change them.

# Your view
class YourView < UIView
  def rmq_build
    rmq(self).tap do |q|
      q.append(UILabel, :section_title)
      q.append(UIButton, :buy_button).on(:tap) do |sender|
        # do  something
      end
    end
  end
end

# In your controller
rmq.append(YourView, :your_style)

Back To Top

How do I add my own styler?

In the example app, look in /app/stylers, you can just copy that whole folder to start. Then add methods to the appropriate class.

Here is an example of adding a method to all stylers:

module RubyMotionQuery
  module Stylers
    class UIViewStyler

      def border_width=(value)
        @view.layer.borderWidth = value
      end
      def border_width
        @view.layer.borderWidth
      end

    end
  end
end

Back To Top

I want to create a “waiting” spinner

rmq.animations.start_spinner

# Code that accesses the network or takes a long time

rmq.animations.stop_spinner

Back To Top

7 - Plugins

8 - Videos

Creating an Image Browser app in RubyMotion and RubyMotionQuery

Back To Top

9 - Misc

A recommended project structure

  • app
    • controllers
      • your_controller.rb
      • your_other_controller.rb
    • models
    • shared
    • stylers
      • ui_view_styler.rb
      • ui_button_styler.rb
      • etc
    • stylesheets
      • application_stylesheet.rb (inherit from RubyMotionQuery::Stylesheet)
      • your_stylesheet.rb (inherit from ApplicationStylesheet)
      • your_other_stylesheet.rb (inherit from ApplicationStylesheet)
      • your_view_stylesheet.rb (module, included an any controller’s stylesheet that needs it)
    • views
  • lib
  • resource
  • spec
    • controllers
    • lib
    • models
    • shared
    • stylers
    • views

Back To Top

License

RMQ is available under the MIT license. See the LICENSE file for more info.

The documentation is licensed under a Creative Commons Attribution-Share Alike 3.0 license. The videos are licensed by their respective authors. The RMQ logo, and other artwork are copyrighted by Todd Werth, all rights reserved.

Back To Top

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Back To Top