Class: RMExtensions::Layout

Inherits:
Object
  • Object
show all
Defined in:
lib/motion/layout.rb

Constant Summary collapse

ATTRIBUTE_LOOKUP =
{
  "left" => NSLayoutAttributeLeft,
  "right" => NSLayoutAttributeRight,
  "top" => NSLayoutAttributeTop,
  "bottom" => NSLayoutAttributeBottom,
  "leading" => NSLayoutAttributeLeading,
  "trailing" => NSLayoutAttributeTrailing,
  "width" => NSLayoutAttributeWidth,
  "height" => NSLayoutAttributeHeight,
  "centerX" => NSLayoutAttributeCenterX,
  "centerY" => NSLayoutAttributeCenterY,
  "baseline" => NSLayoutAttributeBaseline,
  nil => NSLayoutAttributeNotAnAttribute
}
ATTRIBUTE_LOOKUP_INVERSE =
ATTRIBUTE_LOOKUP.invert
{
  "<=" => NSLayoutRelationLessThanOrEqual,
  "==" => NSLayoutRelationEqual,
  ">=" => NSLayoutRelationGreaterThanOrEqual
}
RELATED_BY_LOOKUP.invert
PRIORITY_LOOKUP =
{
  "required" => UILayoutPriorityRequired, # = 1000
  "high" => UILayoutPriorityDefaultHigh, # = 750
  "low" => UILayoutPriorityDefaultLow, # = 250
  "fit" => UILayoutPriorityFittingSizeLevel # = 50
}
PRIORITY_LOOKUP_INVERSE =
PRIORITY_LOOKUP.invert
AXIS_LOOKUP =
{
  "h" => UILayoutConstraintAxisHorizontal,
  "v" => UILayoutConstraintAxisVertical
}

Instance Method Summary collapse

Constructor Details

#initializeLayout

Returns a new instance of Layout.



43
44
45
46
47
# File 'lib/motion/layout.rb', line 43

def initialize
  if block_given?
    yield self
  end
end

Instance Method Details

#describe(constraint) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/motion/layout.rb', line 218

def describe(constraint)
  subviews_inverse = subviews.invert
  item = subviews_inverse[constraint.firstItem]
  item_attribute = ATTRIBUTE_LOOKUP_INVERSE[constraint.firstAttribute]
  related_by = RELATED_BY_LOOKUP_INVERSE[constraint.relation]
  to_item = subviews_inverse[constraint.secondItem]
  to_item_attribute = ATTRIBUTE_LOOKUP_INVERSE[constraint.secondAttribute]
  multiplier = constraint.multiplier
  constant = constraint.constant
  priority = PRIORITY_LOOKUP_INVERSE[constraint.priority] || constraint.priority
  "#{item}.#{item_attribute} #{related_by} #{to_item}.#{to_item_attribute} * #{multiplier} + #{constant} @ #{priority}"
end

#eq(str) ⇒ Object

Constraints are of the form “view1.attr1 <relation> view2.attr2 * multiplier + constant @ priority”



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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
# File 'lib/motion/layout.rb', line 77

def eq(str)
  item = nil
  item_attribute = nil
  related_by = nil
  to_item = nil
  to_item_attribute = nil
  multiplier = nil
  constant = nil
  
  parts = str.split("#", 2).first.split(" ").select { |x| !x.empty? }

  debug = parts.delete("?")

  # first part should always be view1.attr1
  part = parts.shift
  item, item_attribute = part.split(".", 2)

  # second part should always be relation
  related_by = parts.shift

  # now things get more complicated

  # look for priority
  if idx = parts.index("@")
    priority = parts[idx + 1]
    parts.delete_at(idx)
    parts.delete_at(idx)
  end

  # look for negative or positive constant
  if idx = parts.index("-")
    constant = "-#{parts[idx + 1]}"
    parts.delete_at(idx)
    parts.delete_at(idx)
  elsif idx = parts.index("+")
    constant = parts[idx + 1]
    parts.delete_at(idx)
    parts.delete_at(idx)
  end

  # look for multiplier
  if idx = parts.index("*")
    multiplier = parts[idx + 1]
    parts.delete_at(idx)
    parts.delete_at(idx)
  end

  # now we need to_item, to_item_attribute

  if part = parts.shift
    # if part includes a . it could be either view2.attr2 or a float like 10.5
    l, r = part.split(".", 2)
    if !r || (r && r =~ /\d/)
      # assume a solo constant was on the right side
      constant = part
    else
      # assume view2.attr2
      to_item, to_item_attribute = l, r
    end
  end

  # if we dont have to_item and the item_attribute is something that requires a to_item, then
  # assume superview
  if !to_item
    unless item_attribute == "height" || item_attribute == "width"
      to_item = "view"
      to_item_attribute = item_attribute
    end
  end

  # normalize

  res_item = view_for_item(item)
  res_constant = constant ? Float(PRIORITY_LOOKUP[constant] || constant) : 0.0

  if res_item
    case item_attribute
    when "resistH"
      return res_item.setContentCompressionResistancePriority(res_constant, forAxis:UILayoutConstraintAxisHorizontal)
    when "resistV"
      return res_item.setContentCompressionResistancePriority(res_constant, forAxis:UILayoutConstraintAxisVertical)
    when "hugH"
      return res_item.setContentHuggingPriority(res_constant, forAxis:UILayoutConstraintAxisHorizontal)
    when "hugV"
      return res_item.setContentHuggingPriority(res_constant, forAxis:UILayoutConstraintAxisVertical)
    end
  end

  res_item_attribute = ATTRIBUTE_LOOKUP[item_attribute]
  res_related_by = RELATED_BY_LOOKUP[related_by]
  res_to_item = to_item ? view_for_item(to_item) : nil
  res_to_item_attribute = ATTRIBUTE_LOOKUP[to_item_attribute]
  res_multiplier = multiplier ? Float(multiplier) : 1.0
  res_priority = priority ? Integer(PRIORITY_LOOKUP[priority] || priority) : nil

  errors = []
  errors.push("Invalid view1: #{item}") unless res_item
  errors.push("Invalid attr1: #{item_attribute}") unless res_item_attribute
  errors.push("Invalid relatedBy: #{related_by}") unless res_related_by
  errors.push("Invalid view2: #{to_item}") if to_item && !res_to_item
  errors.push("Invalid attr2: #{to_item_attribute}") unless res_to_item_attribute

  if errors.size > 0 || debug
    p "======================== constraint debug ========================"
    p "given:"
    p "  #{str}"
    p "interpreted:"
    p "  item:                #{item}"
    p "  item_attribute:      #{item_attribute}"
    p "  related_by:          #{related_by}"
    p "  to_item:             #{to_item}"
    p "  to_item_attribute:   #{to_item_attribute}"
    p "  multiplier:          #{multiplier}"
    p "  constant:            #{constant}"
    p "  priority:            #{priority}"
  end

  if errors.size > 0
    raise(errors.join(", "))
  end

  constraint = NSLayoutConstraint.constraintWithItem(res_item,
     attribute:res_item_attribute,
     relatedBy:res_related_by,
        toItem:res_to_item,
     attribute:res_to_item_attribute,
    multiplier:res_multiplier,
      constant:res_constant)
  if res_priority
    constraint.priority = res_priority
  end

  if debug
    p "implemented:"
    p "  #{constraint.description}"
  end

  @view.addConstraint(constraint)
  constraint
end

#eqs(str) ⇒ Object



70
71
72
73
74
# File 'lib/motion/layout.rb', line 70

def eqs(str)
  str.split("\n").map(&:strip).select { |x| !x.empty? }.map do |line|
    eq(line)
  end
end

#subviews(subviews) ⇒ Object



57
58
59
60
61
62
63
64
# File 'lib/motion/layout.rb', line 57

def subviews(subviews)
  @subviews = subviews
  @subviews.values.each do |subview|
    subview.translatesAutoresizingMaskIntoConstraints = false
    @view.addSubview(subview)
  end
  @subviews
end

#subviews=(views) ⇒ Object



66
67
68
# File 'lib/motion/layout.rb', line 66

def subviews=(views)
  subviews(views)
end

#view(view) ⇒ Object



49
50
51
# File 'lib/motion/layout.rb', line 49

def view(view)
  @view = view
end

#view=(v) ⇒ Object



53
54
55
# File 'lib/motion/layout.rb', line 53

def view=(v)
  view(v)
end