-
Notifications
You must be signed in to change notification settings - Fork 83
Teacup
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.
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
In tradiditional iOS development, UIViewController
s 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:
- From a
UIViewController
you can declare alayout
block in your class body and it will be used to instantiate your views during theviewDidLoad
method. - From a
UIViewController
or aUIView
class you can create or modify views using theTeacup::Layout
module methodslayout
andsubview
.
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).
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
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.
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
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.
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.