module Teacup::Layout
Teacup::Layout
defines a layout and subview function that can be used to declare and configure the layout of views and the view hierarchy in your application.
This module is included into UIView
and UIViewController
directly so these functions are available in the places you need them.
In order to use layout() in a UIViewController
most effectively you will want to define a stylesheet method that returns a stylesheet.
@example
class MyViewController < UIViewController stylesheet :logo layout(:my_view) do layout UIImage, :logo end end
Public Class Methods
# File lib/teacup/layout.rb, line 40 def self.included(base) base.extend LayoutClass end
Public Instance Methods
Calling this method uses Nick Quaranto's motion-layout gem to provide ASCII art style access to autolayout. It assigns all the subviews by stylename, and assigns `self.view` as the target view. Beyond that, it's up to you to implement the layout methods:
auto do metrics 'margin' => 20 vertical "|-[top]-margin-[bottom]-|" horizontal "|-margin-[top]-margin-|" horizontal "|-margin-[bottom]-margin-|" end
# File lib/teacup/layout.rb, line 241 def auto(layout_view=top_level_view, layout_subviews={}, &layout_block) raise "gem install 'motion-layout'" unless defined? Motion::Layout styled_subviews = layout_view.teacup_subviews.select { |v| v.stylename } styled_subviews.each do |view| if ! layout_subviews[view.stylename.to_s] layout_subviews[view.stylename.to_s] = view end end Motion::Layout.new do |layout| layout.view layout_view layout.subviews layout_subviews layout.instance_eval(&layout_block) end end
Alter the layout of a view
@param instance The first parameter is the view that you want to
layout.
@param name The second parameter is optional, and is the
stylename to apply to the element. When using stylesheets any properties defined in the current stylesheet (see {stylesheet}) for this element will be immediately applied.
@param classes The third parameter is optional, and is a list
of style classes.
@param properties The fourth parameter is optional, and is a Hash
of properties to apply to the view directly.
@param &block If a block is passed, it is evaluated such that
any calls to {subview} that occur within that block cause created subviews to be added to *this* view instead of to the top-level view.
For example, to alter the width and height of a carousel:
@example
layout(carousel, width: 500, height: 100)
Or to layout the carousel in the default style:
@example
layout(carousel, :default_carousel)
You can also use this method with {subview}, for example to add a new image to a carousel:
@example
layout(carousel) do subview(UIImage, backgroundColor: UIColor.colorWithImagePattern(image) end
# File lib/teacup/layout.rb, line 113 def layout(view_or_class, *teacup_settings, &block) view = Teacup.to_instance(view_or_class) # prevents the calling of restyle! until we return to this method should_restyle = Teacup.should_restyle_and_block teacup_style = Style.new teacup_settings.each do |setting| case setting when Symbol, String view.stylename = setting when Hash # override settings, but apply them to teacup_style so that it remains # a Teacup::Style object Teacup::merge_defaults(setting, teacup_style, teacup_style) when Enumerable view.style_classes = setting when nil # skip. this is so that helper methods that accept arguments like # stylename can pass those on to this method without having to # introspect the values (just set the default value to `nil`) # # long story short: tests will fail `nil` is not ignore here else raise "The argument #{setting.inspect} is not supported in Teacup::Layout::layout()" end end if view.is_a? Teacup::View view.style(teacup_style.build(view)) else Teacup.apply_hash view, teacup_style.build(view) end # assign the 'teacup_next_responder', which is queried for a stylesheet if # one is not explicitly assigned to the view. If the view already has a # teacup_next_responder assigned, it's because another object is already # responsible for managing the view's stylesheet. if view.teacup_next_responder.nil? && view.is_a?(Layout) view.teacup_next_responder = self end if block_given? superview_chain << view begin # yield will not work if this is defined in the context of the # UIViewController `layout` class method. yield view rescue NoMethodError => e NSLog("Exception executing layout(#{view.inspect}) in #{self.inspect} (stylesheet=#{stylesheet})") raise e end superview_chain.pop end if should_restyle Teacup.should_restyle! view.restyle! end view end
Returns the stylesheet. If the stylesheet was assigned by name, the stylesheet object with this name is returned.
@return Teacup::Stylesheet
# File lib/teacup/layout.rb, line 65 def stylesheet if @stylesheet.is_a? Symbol @stylesheet = Teacup::Stylesheet[@stylesheet] end return @stylesheet || self.class.stylesheet end
Assign a Stylesheet
or Stylesheet
name (Symbol)
@return val
@example
stylesheet = Stylesheet.new do style :root, backgroundColor: UIColor.blueColor end controller.stylesheet = stylesheet # or use a stylename view.stylesheet = :stylesheet_name
# File lib/teacup/layout.rb, line 57 def stylesheet= val @stylesheet = val end
Add a new subview to the view heirarchy.
By default the subview will be added at the top level of the view heirarchy, though if this function is executed within a block passed to {layout} or {subview}, then this view will be added as a subview of the instance being layed out by the block.
This is particularly useful when coupled with the {UIViewController.heirarchy} function that allows you to declare your view heirarchy.
@param class_or_instance The UIView
subclass (or instance thereof) that you want
to add. If you pass a class, an instance will be created by calling {new}.
@param *args Arguments to pass to {layout} to instruct teacup how to
lay out the newly added subview.
@param &block A block to execute with the current view context set to
your new element, see {layout} for more details.
@return instance The instance that was added to the view heirarchy.
For example, to specify that a controller should contain some labels:
@example
MyViewController < UIViewController layout(:my_view) do subview(UILabel, text: 'Test') subview(UILabel, :styled_label) end end
If you need to add a new image at runtime, you can also do that:
@example
layout(carousel) do subview(UIImage, backgroundColor: UIColor.colorWithImagePattern(image) end
# File lib/teacup/layout.rb, line 216 def subview(class_or_instance, *args, &block) instance = Teacup.to_instance(class_or_instance) (superview_chain.last || top_level_view).addSubview(instance) layout(instance, *args, &block) instance end
# File lib/teacup/layout.rb, line 258 def top_level_view raise "No default view has been defined for #{self.class}. Implement `top_level_view`." end
Protected Instance Methods
Get's the current stack of views in nested calls to layout.
The view at the end of the stack is the one into which subviews are currently being attached.
# File lib/teacup/layout.rb, line 268 def superview_chain @superview_chain ||= [] end