Skip to content
ballantyne edited this page Jan 10, 2013 · 4 revisions

Styles and Stylesheets

The Teacup::Stylesheet class supports named-styles and class-based styles, with no support for nesting styles (e.g. MyClass > :stylename > :stylename). There has been talk of applying styles by view class and supporting nesting of styles, and having rules applied using CSS-like precedence calculations, but that is simply beyond the scope of "initial launch".

These ideas are not off the table, but they are tabled for now. We would like a cool down period where we use the Stylesheets in their simple form for now, and later we will evaluate how people are actually using these, and see how we can make things better for them. Iterative development FTW!

These stylesheet examples make heavy use of sugarcube, so check that out if some of this looks like magic.

Example
app/styles/stylesheets.rb
Teacup::Stylesheet.new :main do
  style :shadow,
    layer: {
      shadowColor: :black.uicolor,  # sugarcube shorthand
      shadowOffset: 3,
      shadowRadius: 1,
    }

  style :icon, extends: :shadow,
    frame: [[2, 2], [40, 40]]

  # curlies are optional as long as you are careful about trailing commas
  style :title, {
    font: :bold.uifont(14),  # again, from sugarcube
    frame: [[44, 0], [276, 20]]
  }

  style :info,
    font: :small.uifont,
    font_color: :gray.uicolor,
    # orientation styles are applied automatically
    portrait: {
      width: 80
    },
    landscape: {
      width: 100
    }

  style :date, extends: :info,
    textAlignment: :left.uialignment,
    frame: [[44, 20], [138, 12]]

  style :ok_button,
    frame: [[0, 310], [20, 10]],
    title: "OK"
end

ViewControllers

In tradiditional iOS development, UIViewControllers are closely associated with NIB (.xib) files. We have brought this paradigm into teacup, but instead of using Xcode to create your GUI, you use a layout block coupled with a stylesheet. You can assign the views to instance variables directly (no need to worry about connections).

The best way to think of a layout block is as a lightweight .nib file. There are two ways to deal with a layout:

  1. From a UIViewController you can declare a layout block in your class body and it will be used to instantiate your views during the viewDidLoad method.
  2. From a UIViewController or a UIView class you can create or modify views using the Teacup::Layout module methods layout and subview.

layout blocks

This is what you'll see a lot of; it's the preferred place to create and manage a view hierarchy when using teacup. It is only available on UIViewController classes.

It consists, usually, of two parts: the stylesheet declaration (what stylesheet does the controller use) and the layout block (what views are created).

Example
app/main_view_controller.rb
class MainViewController < UIViewController
  stylesheet :main

  layout :root do  # layout block, with stylename of the controller's `self.view` object
    subview(UIView, :shadow) do  # subview accepts an instance or a class name, plus a stylename
      subview(UILabel, text:"a button")            # it also accepts a hash, if you don't want to use a stylesheet
      subview(UITextField, :name, delegate: self)  # since you cannot access the controller from within a Stylesheet,
    end                                            # you might need to assign delegates & dataSources here.

    @my_button = subview(UIButton.rounded_rect, title:"ok")  # this block will be run in the context of the MainViewController instance
  end

layout and subview methods

For this section, I will switch into a UIView subclass. But keep in mind, these methods are available from a UIViewController as well.

It is common that a UIView subclass needs to manage a view hierarchy, but because there is no viewDidLoad method, there is no good place to manage a layout block. Instead, I recommend using this pattern. Create your subviews in initWithFrame (or any designated initializer), and use layout and subview on those objects.

app/icon_label.rb
class IconLabel < UIView
  attr_accessor :image

  def text=(val)
    @label.text = val
    val
  end
  
  def text
    @label.text
  end

  def image=(val)
    @image.image = val
    val
  end
  
  def image
    @image.image
  end

  def initWithFrame(frame)
    super.tap  # I do this in my init blocks so that I never forget to return self
      frame_width = CGRectGetWidth(frame)
      frame_height = CGRectGetHeight(frame)
      image_dimension = frame_height
      label_margin = 3
      label_x = image_dimension + label_margin

      @image = subview(UIImageView,
                      frame: [[0, 0], [image_dimension, image_dimension]]
                      )
      @label = subview(UILabel,
                      frame: [[label_x, 0, frame_width - label_x, frame_height]]
                      )

      # the equivalent "traditional" code is not much longer, but stylistically I think the code above is better
      @image = UIImageView.new
      @image.frame = [[0, 0], [image_dimension, image_dimension]]
      self.addSubview(@image)

      @label = UILabel.new
      @label.frame = [[label_x, 0, frame_width - label_x, frame_height]]
      self.addSubview(@label)
    end
  end
end

Controller/Layout/Stylesheet hybrids

But I am le tired! I do not want to create all these classes!

As @ConradIrwin put it "modularity is fashionable". You do not have to break up your code in the way we have here. You can, if it floats your boat, put all this code back in your controller, and you can still benefit from what we think is a more rubyesque style of coding.

Example

app/controller.rb
class MainViewController < UIViewController

  def viewDidLoad
    subview(UILabel,
      text: "I am le tired",
      fontColor: UIColor.blueColor
      )
  end

end

The traditional rubymotion code to do this looks like this:

class MainViewController < UIViewController

  def viewDidLoad
    label = UILabel.alloc.init
    label.text = "I am le tired"
    label.fontColor = UIColor.blueColor
    self.view.addSubview(label)
  end

end

Just as many lines of code - it is purely a matter of taste which you prefer.