Module: Teacup::View

Included in:
NSView, NSWindow, UIView
Defined in:
lib/teacup/teacup_view.rb,
lib/teacup/core_extensions/view_getters.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#debugObject

Enable debug messages for this object



20
21
22
# File 'lib/teacup/teacup_view.rb', line 20

def debug
  @debug
end

#style_classesObject

A list of style classes that will be merged in (lower priority than stylename)



13
14
15
# File 'lib/teacup/teacup_view.rb', line 13

def style_classes
  @style_classes
end

#stylenameObject

The current stylename that is used to look up properties in the stylesheet.



10
11
12
# File 'lib/teacup/teacup_view.rb', line 10

def stylename
  @stylename
end

#teacup_next_responderObject

Any class that includes Teacup::Layout gets a ‘layout` method, which assigns itself as the ’teacup_next_responder’.



17
18
19
# File 'lib/teacup/teacup_view.rb', line 17

def teacup_next_responder
  @teacup_next_responder
end

Instance Method Details

#_teacup_check_stylename(name_or_class) ⇒ Object



48
49
50
51
52
53
54
55
56
57
# File 'lib/teacup/core_extensions/view_getters.rb', line 48

def _teacup_check_stylename(name_or_class)
  if name_or_class.is_a? Class
    return self.is_a?(name_or_class)
  elsif stylename == name_or_class
    return true
  elsif stylesheet.is_a?(Teacup::Stylesheet)
    return stylesheet.extends_style?(self.stylename, name_or_class)
  end
  return false
end

#add_style_class(stylename) ⇒ Object



64
65
66
67
68
69
# File 'lib/teacup/teacup_view.rb', line 64

def add_style_class(stylename)
  unless style_classes.include? stylename
    style_classes << stylename
    restyle!
  end
end

#add_uniq_constraints(constraint) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/teacup/teacup_view.rb', line 347

def add_uniq_constraints(constraint)
  @teacup_constraints ||= {}

  if constraint.is_a? Array
    constraint.each do |constraint|
      add_uniq_constraints(constraint)
    end
  elsif constraint.is_a? Hash
    constraint.each do |sym, relative_to|
      @teacup_constraints[sym] = relative_to
    end
  elsif constraint.is_a?(Teacup::Constraint) || constraint.is_a?(Symbol)
    @teacup_constraints[constraint] = true
  else
    raise "Unsupported constraint: #{constraint.inspect}"
  end
end

#animate_to_style(style) ⇒ Object

Animate a change to new styles

This is equivalent to wrapping a call to .style() inside UIView.beginAnimations.

Parameters:

  • Hash

    the new styles and options for the animation



313
314
315
316
317
# File 'lib/teacup/teacup_view.rb', line 313

def animate_to_style(style)
  teacup_animation(options) do
    self.style(style)
  end
end

#animate_to_stylename(stylename, options = {}) ⇒ Object

Animate a change to a new stylename.

This is equivalent to wrapping a call to .stylename= inside UIView.beginAnimations.

Parameters:

  • Symbol

    the new stylename

  • Options

    the options for the animation (may include the duration and the curve)



281
282
283
284
285
286
287
# File 'lib/teacup/teacup_view.rb', line 281

def animate_to_stylename(stylename, options={})
  return if self.stylename == stylename

  teacup_animation(options) do
    self.stylename = stylename
  end
end

#animate_to_styles(style_classes, options = {}) ⇒ Object

Animate a change to a new list of style_classes.

This is equivalent to wrapping a call to .style_classes= inside UIView.beginAnimations.

Parameters:

  • Symbol

    the new stylename

  • Options

    the options for the animation (may include the duration and the curve)



298
299
300
301
302
303
304
# File 'lib/teacup/teacup_view.rb', line 298

def animate_to_styles(style_classes, options={})
  return if self.style_classes == style_classes

  teacup_animation(options) do
    self.style_classes = style_classes
  end
end

#apply_constraintsObject



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/teacup/teacup_view.rb', line 244

def apply_constraints
  if @teacup_added_constraints
    @teacup_added_constraints.each do |constraint|
      self.removeConstraint(constraint)
    end
  end
  @teacup_added_constraints = nil
  all_constraints = get_ns_constraints

  return if all_constraints.empty?

  @teacup_added_constraints = []
  all_constraints.each do |ns_constraint|
    @teacup_added_constraints << ns_constraint
    self.addConstraint(ns_constraint)
  end
end

#apply_style_properties(properties) ⇒ Object



336
337
338
# File 'lib/teacup/teacup_view.rb', line 336

def apply_style_properties(properties)
  Teacup.apply_hash self, properties
end

#apply_stylename(stylename) ⇒ Object

Applies styles pulled from a stylesheet, but does not assign those styles to any property. This is a one-shot use method, meant to be used as initialization or to apply styles that should not be reapplied during a rotation.



266
267
268
269
270
# File 'lib/teacup/teacup_view.rb', line 266

def apply_stylename(stylename)
  if stylesheet && stylesheet.is_a?(Teacup::Stylesheet)
    style(stylesheet.query(stylename, self))
  end
end

#get_ns_constraintsObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/teacup/teacup_view.rb', line 134

def get_ns_constraints
  # gets the array of Teacup::Constraint objects
  my_constraints = (@teacup_constraints || []).map { |constraint, relative_to|
    if constraint.is_a?(Teacup::Constraint)
      constraint
    else
      if relative_to == true
        Teacup::Constraint.from_sym(constraint)
      else
        Teacup::Constraint.from_sym(constraint, relative_to)
      end
    end
  }.flatten.map do |original_constraint|
    constraint = original_constraint.copy

    view_class = self.class

    controller = nil
    target = self
    while target && controller.nil?
      if target.nextResponder.is_a?(UIViewController)
        controller = target.nextResponder
        target = nil
      else
        target = target.nextResponder
      end
    end

    case original_constraint.target
    when view_class
      constraint.target = original_constraint.target
    when :self
      constraint.target = self
    when :superview
      constraint.target = self.superview
    when Symbol, String
      container = self
      constraint.target = nil
      while container && constraint.target.nil?
        constraint.target = container.viewWithStylename(original_constraint.target)
        container = container.superview
      end
    end

    case original_constraint.relative_to
    when nil
      constraint.relative_to = nil
    when view_class
      constraint.relative_to = original_constraint.relative_to
    when :self
      constraint.relative_to = self
    when :superview
      constraint.relative_to = self.superview
    when :top_layout_guide
      if controller.respondsToSelector(:topLayoutGuide)
        constraint.relative_to = controller.topLayoutGuide
      else
        if controller
          NSLog("topLayoutGuide is only supported in >= iOS 7. Reverting to nil bound")
        end
        constraint.relative_to = nil
      end
    when :bottom_layout_guide
      if controller.respondsToSelector(:bottomLayoutGuide)
        constraint.relative_to = controller.bottomLayoutGuide
      else
        if controller
          NSLog("bottomLayoutGuide is only supported in >= iOS 7. Reverting to nil bound")
        end
        constraint.relative_to = nil
      end
    when Symbol, String
      # TODO: this re-checks lots of views - everytime it goes up to the
      # superview, it checks all the leaves again.
      container = self
      constraint.relative_to = nil
      while container && constraint.relative_to.nil?
        constraint.relative_to = container.viewWithStylename(original_constraint.relative_to)
        container = container.superview
      end
    end

    if original_constraint.relative_to && ! constraint.relative_to
      container = self
      puts "Searching for #{original_constraint.relative_to.inspect} in the tree:"
      tab = ''
      while container
        tab << '->'
        puts "#{tab} #{container.stylename.inspect}"
        container = container.superview
      end
      raise "Could not find #{original_constraint.relative_to.inspect}"
    end

    # the return value, for the map
    constraint.nslayoutconstraint
  end

  unless my_constraints.empty?
    self.setTranslatesAutoresizingMaskIntoConstraints(false)
  end

  # now add all che child constraints
  teacup_subviews.each do |subview|
    my_constraints.concat(subview.get_ns_constraints)
  end

  my_constraints
end

#remove_style_class(stylename) ⇒ Object



71
72
73
74
75
# File 'lib/teacup/teacup_view.rb', line 71

def remove_style_class(stylename)
  if style_classes.delete(stylename)
    restyle!
  end
end

#reset_constraintsObject



340
341
342
343
344
345
# File 'lib/teacup/teacup_view.rb', line 340

def reset_constraints
  @teacup_constraints = nil
  self.teacup_subviews.each do |subview|
    subview.reset_constraints
  end
end

#restyle!(orientation = nil) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/teacup/teacup_view.rb', line 122

def restyle!(orientation=nil)
  if Teacup.should_restyle?
    if stylesheet && stylesheet.is_a?(Teacup::Stylesheet)
      style_classes.each do |stylename|
        style(stylesheet.query(stylename, self, orientation))
      end
      style(stylesheet.query(self.stylename, self, orientation))
    end
    teacup_subviews.each { |subview| subview.restyle!(orientation) }
  end
end

#style(properties) ⇒ Object

Apply style properties to this element.

Takes a hash of properties such as may have been read from a stylesheet or passed as parameters to Layout#layout, and applies them to the element.

Does a little bit of magic (that may be split out as ‘sugarcube’) to make properties work as you’d expect.

If you try and assign something in properties that is not supported, a warning message will be emitted.

Parameters:

  • Hash

    the properties to set.



332
333
334
# File 'lib/teacup/teacup_view.rb', line 332

def style(properties)
  apply_style_properties(properties)
end

#stylesheetObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/teacup/teacup_view.rb', line 98

def stylesheet
  if @stylesheet.is_a? Symbol
    @stylesheet = Teacup::Stylesheet[@stylesheet]
  end
  # is a stylesheet assigned explicitly?
  retval = @stylesheet
  return retval if retval

  # the 'teacup_next_responder' is assigned in the `layout` method, and links
  # any views created there to the custom class (could be a controller, could
  # be any class that includes Teacup::Layout).  That responder is checked
  # next, but only if it wouldn't result in a circular loop.
  if ! retval && @teacup_next_responder && teacup_next_responder != self
    retval = @teacup_next_responder.stylesheet
  end

  # lastly, go up the chain; either a controller or superview
  if ! retval && nextResponder && nextResponder.respond_to?(:stylesheet)
    retval = nextResponder.stylesheet
  end

  return retval
end

#stylesheet=(new_stylesheet) ⇒ Object

Alter the stylesheet of this view.

This will cause new styles to be applied using the current stylename, and will recurse into subviews.

If you would prefer that a given UIView object does not inherit the stylesheet from its parents, override the ‘stylesheet’ method to return the correct value at all times.

Parameters:

  • Teacup::Stylesheet

    stylesheet.



87
88
89
90
91
92
93
94
95
96
# File 'lib/teacup/teacup_view.rb', line 87

def stylesheet=(new_stylesheet)
  should_restyle = Teacup.should_restyle_and_block

  @stylesheet = new_stylesheet

  if should_restyle
    Teacup.should_restyle!
    restyle!
  end
end

#teacup_subviewsObject

Subviews or empty collection of views to cater for issue with iOS7 dp3



23
24
25
# File 'lib/teacup/teacup_view.rb', line 23

def teacup_subviews
  self.subviews || []
end

#viewsWithStylename(name_or_class) ⇒ Object

get all subviews by stylename or class my_view.viewsWithStylename :button => [#<UIButton..>, #<UIButton…>] my_view.viewsWithStylename UIButton => [#<UIButton..>, #<UIButton…>]



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/teacup/core_extensions/view_getters.rb', line 28

def viewsWithStylename name_or_class
  retval = []
  retval << self if self._teacup_check_stylename(name_or_class)

  search_views = [].concat(self.subviews)
  # ewww, a traditional for loop! the search_views array is modified in place,
  # and `each` and other methods don't like that.
  index = 0
  while index < search_views.length
    view = search_views[index]
    if view._teacup_check_stylename(name_or_class)
      retval << view
    end
    search_views.concat(view.subviews)
    index += 1
  end

  return retval
end

#viewWithStylename(name_or_class) ⇒ Object

get one subview by stylename or class. If the receiver matches, it will be returned my_view.viewWithStylename :button => #<UIButton..> my_view.viewWithStylename UIButton => #<UIButton..>



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/teacup/core_extensions/view_getters.rb', line 10

def viewWithStylename name_or_class
  return self if self._teacup_check_stylename(name_or_class)

  view = self.teacup_subviews.find { |view| view._teacup_check_stylename(name_or_class) }
  return view if view

  # found_subview will get assigned to the view we want, but the subview is
  # what is returned.
  found_subview = nil
  view = self.teacup_subviews.find { |subview| found_subview = subview.viewWithStylename(name_or_class) }
  return found_subview if view

  return nil  # couldn't find it
end