rmx 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +18 -0
- data/app/app_delegate.rb +5 -0
- data/lib/motion/RMXActionSheet.rb +14 -0
- data/lib/motion/RMXAutoLayoutLabel.rb +19 -0
- data/lib/motion/RMXAutoLayoutScrollView.rb +53 -0
- data/lib/motion/RMXCommonMethods.rb +27 -0
- data/lib/motion/RMXEventsFromProxy.rb +103 -0
- data/lib/motion/RMXExecutionBlock.rb +51 -0
- data/lib/motion/RMXKeyboardHelpers.rb +54 -0
- data/lib/motion/RMXLongTask.rb +133 -0
- data/lib/motion/RMXNavigationController.rb +133 -0
- data/lib/motion/RMXObjectExtensions.rb +10 -0
- data/lib/motion/RMXSegmentedController.rb +71 -0
- data/lib/motion/RMXSetAttributes.rb +34 -0
- data/lib/motion/RMXStrongToWeakHash.rb +46 -0
- data/lib/motion/RMXSynchronizedStrongToWeakHash.rb +40 -0
- data/lib/motion/RMXTableHandler.rb +212 -0
- data/lib/motion/RMXTableViewCell.rb +85 -0
- data/lib/motion/RMXTableViewCellInnerContentView.rb +16 -0
- data/lib/motion/RMXTableViewController.rb +65 -0
- data/lib/motion/RMXUnsafeUnretainedHolder.rb +30 -0
- data/lib/motion/RMXView.rb +84 -0
- data/lib/motion/RMXViewController.rb +65 -0
- data/lib/motion/RMXViewControllerPresentation.rb +127 -0
- data/lib/motion/RMXWeakHolder.rb +36 -0
- data/lib/motion/RMXWeakToStrongHash.rb +39 -0
- data/lib/motion/accessors.rb +29 -0
- data/lib/motion/base.rb +12 -0
- data/lib/motion/env.rb +9 -0
- data/lib/motion/events.rb +61 -0
- data/lib/motion/layout.rb +357 -0
- data/lib/motion/ui.rb +55 -0
- data/lib/motion/util.rb +155 -0
- data/lib/rmx/version.rb +3 -0
- data/lib/rmx.rb +50 -0
- data/resources/Default-568h@2x.png +0 -0
- data/rmx.gemspec +19 -0
- data/spec/main_spec.rb +240 -0
- metadata +86 -0
@@ -0,0 +1,357 @@
|
|
1
|
+
class RMX
|
2
|
+
class Layout
|
3
|
+
|
4
|
+
ATTRIBUTE_LOOKUP = {
|
5
|
+
"left" => NSLayoutAttributeLeft,
|
6
|
+
"right" => NSLayoutAttributeRight,
|
7
|
+
"top" => NSLayoutAttributeTop,
|
8
|
+
"bottom" => NSLayoutAttributeBottom,
|
9
|
+
"leading" => NSLayoutAttributeLeading,
|
10
|
+
"trailing" => NSLayoutAttributeTrailing,
|
11
|
+
"width" => NSLayoutAttributeWidth,
|
12
|
+
"height" => NSLayoutAttributeHeight,
|
13
|
+
"centerX" => NSLayoutAttributeCenterX,
|
14
|
+
"centerY" => NSLayoutAttributeCenterY,
|
15
|
+
"baseline" => NSLayoutAttributeBaseline,
|
16
|
+
nil => NSLayoutAttributeNotAnAttribute
|
17
|
+
}
|
18
|
+
|
19
|
+
ATTRIBUTE_LOOKUP_INVERSE = ATTRIBUTE_LOOKUP.invert
|
20
|
+
|
21
|
+
RELATED_BY_LOOKUP = {
|
22
|
+
"<=" => NSLayoutRelationLessThanOrEqual,
|
23
|
+
"==" => NSLayoutRelationEqual,
|
24
|
+
">=" => NSLayoutRelationGreaterThanOrEqual
|
25
|
+
}
|
26
|
+
|
27
|
+
RELATED_BY_LOOKUP_INVERSE = RELATED_BY_LOOKUP.invert
|
28
|
+
|
29
|
+
PRIORITY_LOOKUP = {
|
30
|
+
"max" => UILayoutPriorityRequired, # = 1000
|
31
|
+
"required" => UILayoutPriorityRequired, # = 1000
|
32
|
+
"high" => UILayoutPriorityDefaultHigh, # = 750
|
33
|
+
"med" => 500,
|
34
|
+
"low" => UILayoutPriorityDefaultLow, # = 250
|
35
|
+
"fit" => UILayoutPriorityFittingSizeLevel # = 50
|
36
|
+
}
|
37
|
+
|
38
|
+
PRIORITY_LOOKUP_INVERSE = PRIORITY_LOOKUP.invert
|
39
|
+
|
40
|
+
AXIS_LOOKUP = {
|
41
|
+
"h" => UILayoutConstraintAxisHorizontal,
|
42
|
+
"v" => UILayoutConstraintAxisVertical
|
43
|
+
}
|
44
|
+
|
45
|
+
# keeps track of views that are not #hidden? as constraints are built, so the
|
46
|
+
# special `last_visible` view name can be used in equations.
|
47
|
+
# exposed for advanced layout needs.
|
48
|
+
attr_accessor :visible_items
|
49
|
+
|
50
|
+
# Example:
|
51
|
+
# RMX::Layout.new do |layout|
|
52
|
+
# ...
|
53
|
+
# end
|
54
|
+
def initialize(&block)
|
55
|
+
@block_owner = block.owner if block
|
56
|
+
@visible_items = []
|
57
|
+
@constraints = {}
|
58
|
+
unless block.nil?
|
59
|
+
block.call(self)
|
60
|
+
block = nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# reopens the RMX::Layout instance for additional processing, ex:
|
65
|
+
# @layout.reopen do |layout|
|
66
|
+
# ...
|
67
|
+
# end
|
68
|
+
# note: you would need to store your instance somewhere on creation to be able to reopen it later, ex:
|
69
|
+
# @layout = RMX::Layout.new do |layout|
|
70
|
+
# ...
|
71
|
+
# end
|
72
|
+
def reopen
|
73
|
+
if block_given?
|
74
|
+
yield self
|
75
|
+
end
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def clear!
|
80
|
+
@view.removeConstraints(@view.constraints)
|
81
|
+
end
|
82
|
+
|
83
|
+
def remove(constraint)
|
84
|
+
constraints = [ constraint ].flatten.compact
|
85
|
+
@view.removeConstraints(constraints)
|
86
|
+
@constraints.keys.each do |key|
|
87
|
+
@constraints.delete(key) if constraints.include?(@constraints.fetch(key))
|
88
|
+
end
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
def view(view)
|
93
|
+
@view = view
|
94
|
+
end
|
95
|
+
|
96
|
+
def view=(v)
|
97
|
+
view(v)
|
98
|
+
end
|
99
|
+
|
100
|
+
def subviews(subviews)
|
101
|
+
@subviews = subviews
|
102
|
+
@subviews.values.each do |subview|
|
103
|
+
subview.translatesAutoresizingMaskIntoConstraints = false
|
104
|
+
@view.addSubview(subview)
|
105
|
+
end
|
106
|
+
@subviews
|
107
|
+
end
|
108
|
+
|
109
|
+
def subviews=(views)
|
110
|
+
subviews(views)
|
111
|
+
end
|
112
|
+
|
113
|
+
# takes a string one or more equations separated by newlines and
|
114
|
+
# processes each. returns an array of constraints
|
115
|
+
def eqs(str)
|
116
|
+
str.split("\n").map(&:strip).select { |x| !x.empty? }.map do |line|
|
117
|
+
eq(line)
|
118
|
+
end.compact
|
119
|
+
end
|
120
|
+
|
121
|
+
# Constraints are of the form "view1.attr1 <relation> view2.attr2 * multiplier + constant @ priority"
|
122
|
+
# processes one equation string
|
123
|
+
def eq(str, remove=false)
|
124
|
+
parts = str.split("#", 2).first.split(" ").select { |x| !x.empty? }
|
125
|
+
return if parts.empty?
|
126
|
+
|
127
|
+
item = nil
|
128
|
+
item_attribute = nil
|
129
|
+
related_by = nil
|
130
|
+
to_item = nil
|
131
|
+
to_item_attribute = nil
|
132
|
+
multiplier = 1.0
|
133
|
+
constant = 0.0
|
134
|
+
|
135
|
+
debug = parts.delete("?")
|
136
|
+
|
137
|
+
# first part should always be view1.attr1
|
138
|
+
part = parts.shift
|
139
|
+
item, item_attribute = part.split(".", 2)
|
140
|
+
|
141
|
+
# second part should always be relation
|
142
|
+
related_by = parts.shift
|
143
|
+
|
144
|
+
# now things get more complicated
|
145
|
+
|
146
|
+
# look for priority
|
147
|
+
if idx = parts.index("@")
|
148
|
+
priority = parts[idx + 1]
|
149
|
+
parts.delete_at(idx)
|
150
|
+
parts.delete_at(idx)
|
151
|
+
end
|
152
|
+
|
153
|
+
# look for negative or positive constant
|
154
|
+
if idx = parts.index("-")
|
155
|
+
constant = "-#{parts[idx + 1]}"
|
156
|
+
parts.delete_at(idx)
|
157
|
+
parts.delete_at(idx)
|
158
|
+
elsif idx = parts.index("+")
|
159
|
+
constant = parts[idx + 1]
|
160
|
+
parts.delete_at(idx)
|
161
|
+
parts.delete_at(idx)
|
162
|
+
end
|
163
|
+
|
164
|
+
# look for multiplier
|
165
|
+
if idx = parts.index("*")
|
166
|
+
multiplier = parts[idx + 1]
|
167
|
+
parts.delete_at(idx)
|
168
|
+
parts.delete_at(idx)
|
169
|
+
end
|
170
|
+
|
171
|
+
# now we need to_item, to_item_attribute
|
172
|
+
|
173
|
+
if part = parts.shift
|
174
|
+
# if part includes a . it could be either view2.attr2 or a float like 10.5
|
175
|
+
l, r = part.split(".", 2)
|
176
|
+
if !r || (r && r =~ /\d/)
|
177
|
+
# assume a solo constant was on the right side
|
178
|
+
constant = part
|
179
|
+
else
|
180
|
+
# assume view2.attr2
|
181
|
+
to_item, to_item_attribute = l, r
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# if we dont have to_item and the item_attribute is something that requires a to_item, then
|
186
|
+
# assume superview
|
187
|
+
if !to_item
|
188
|
+
unless item_attribute == "height" || item_attribute == "width"
|
189
|
+
to_item = "view"
|
190
|
+
to_item_attribute = item_attribute
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# normalize
|
195
|
+
|
196
|
+
if item == "last_visible"
|
197
|
+
item = @visible_items.first || "view"
|
198
|
+
end
|
199
|
+
|
200
|
+
res_item = view_for_item(item)
|
201
|
+
res_constant = Float(PRIORITY_LOOKUP[constant] || constant)
|
202
|
+
|
203
|
+
if res_item
|
204
|
+
case item_attribute
|
205
|
+
when "resistH"
|
206
|
+
return res_item.setContentCompressionResistancePriority(res_constant, forAxis:UILayoutConstraintAxisHorizontal)
|
207
|
+
when "resistV"
|
208
|
+
return res_item.setContentCompressionResistancePriority(res_constant, forAxis:UILayoutConstraintAxisVertical)
|
209
|
+
when "hugH"
|
210
|
+
return res_item.setContentHuggingPriority(res_constant, forAxis:UILayoutConstraintAxisHorizontal)
|
211
|
+
when "hugV"
|
212
|
+
return res_item.setContentHuggingPriority(res_constant, forAxis:UILayoutConstraintAxisVertical)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
if to_item == "last_visible"
|
217
|
+
to_item = @visible_items.detect { |x| x != item } || "view"
|
218
|
+
end
|
219
|
+
|
220
|
+
res_item_attribute = ATTRIBUTE_LOOKUP[item_attribute]
|
221
|
+
res_related_by = RELATED_BY_LOOKUP[related_by]
|
222
|
+
res_to_item = to_item ? view_for_item(to_item) : nil
|
223
|
+
res_to_item_attribute = ATTRIBUTE_LOOKUP[to_item_attribute]
|
224
|
+
res_multiplier = Float(multiplier)
|
225
|
+
res_priority = priority ? Integer(PRIORITY_LOOKUP[priority] || priority) : nil
|
226
|
+
|
227
|
+
errors = []
|
228
|
+
errors.push("Invalid view1: #{item}") unless res_item
|
229
|
+
errors.push("Invalid attr1: #{item_attribute}") unless res_item_attribute
|
230
|
+
errors.push("Invalid relatedBy: #{related_by}") unless res_related_by
|
231
|
+
errors.push("Invalid view2: #{to_item}") if to_item && !res_to_item
|
232
|
+
errors.push("Invalid attr2: #{to_item_attribute}") unless res_to_item_attribute
|
233
|
+
|
234
|
+
internal_ident = "#{item}.#{item_attribute} #{related_by} #{to_item}.#{to_item_attribute} * #{multiplier} @ #{priority}"
|
235
|
+
|
236
|
+
if errors.size > 0 || debug
|
237
|
+
p "======================== constraint debug ========================"
|
238
|
+
p "given:"
|
239
|
+
p " #{str}"
|
240
|
+
p "interpreted:"
|
241
|
+
p " item: #{item}"
|
242
|
+
p " item_attribute: #{item_attribute}"
|
243
|
+
p " related_by: #{related_by}"
|
244
|
+
p " to_item: #{to_item}"
|
245
|
+
p " to_item_attribute: #{to_item_attribute}"
|
246
|
+
p " multiplier: #{multiplier}"
|
247
|
+
p " constant: #{constant}"
|
248
|
+
p " priority: #{priority || "required"}"
|
249
|
+
p " internal_ident: #{internal_ident}"
|
250
|
+
end
|
251
|
+
|
252
|
+
if errors.size > 0
|
253
|
+
raise(errors.join(", "))
|
254
|
+
end
|
255
|
+
|
256
|
+
unless res_item.hidden?
|
257
|
+
@visible_items.unshift(item)
|
258
|
+
end
|
259
|
+
|
260
|
+
if remove
|
261
|
+
if constraint = @constraints[internal_ident]
|
262
|
+
if debug
|
263
|
+
p "status:"
|
264
|
+
p " existing (for removal)"
|
265
|
+
end
|
266
|
+
@view.removeConstraint(constraint)
|
267
|
+
else
|
268
|
+
raise "RMX::Layout could not find constraint to remove for internal_ident: `#{internal_ident}` (note: this is an internal representation of the constraint, not the exact string given). Make sure the constraint was created first."
|
269
|
+
end
|
270
|
+
elsif constraint = @constraints[internal_ident]
|
271
|
+
if debug
|
272
|
+
p "status:"
|
273
|
+
p " existing (for modification)"
|
274
|
+
end
|
275
|
+
constraint.constant = res_constant
|
276
|
+
else
|
277
|
+
constraint = NSLayoutConstraint.constraintWithItem(res_item,
|
278
|
+
attribute:res_item_attribute,
|
279
|
+
relatedBy:res_related_by,
|
280
|
+
toItem:res_to_item,
|
281
|
+
attribute:res_to_item_attribute,
|
282
|
+
multiplier:res_multiplier,
|
283
|
+
constant:res_constant)
|
284
|
+
if debug
|
285
|
+
p "status:"
|
286
|
+
p " created"
|
287
|
+
end
|
288
|
+
@constraints[internal_ident] = constraint
|
289
|
+
if res_priority
|
290
|
+
constraint.priority = res_priority
|
291
|
+
end
|
292
|
+
@view.addConstraint(constraint)
|
293
|
+
end
|
294
|
+
|
295
|
+
if debug
|
296
|
+
p "implemented:"
|
297
|
+
p " #{constraint.description}"
|
298
|
+
end
|
299
|
+
|
300
|
+
constraint
|
301
|
+
end
|
302
|
+
|
303
|
+
# removes the constraint matching equation string. constant is not considered.
|
304
|
+
# if no matching constraint is found, it will raise an exception.
|
305
|
+
def xeq(str)
|
306
|
+
eq(str, true)
|
307
|
+
end
|
308
|
+
|
309
|
+
def describe_constraint(constraint)
|
310
|
+
self.class.describe_constraint(@subviews, constraint)
|
311
|
+
end
|
312
|
+
|
313
|
+
def describe_view
|
314
|
+
self.class.describe_view(@subviews, @view)
|
315
|
+
end
|
316
|
+
|
317
|
+
# transforms an NSLayoutConstraint into a string. this string is for debugging and produces
|
318
|
+
# a verbose translation. its not meant to be copied directly as an equation.
|
319
|
+
# pass the subviews hash just as you would to Layout#subviews=, followed by the NSLayoutConstraint
|
320
|
+
def self.describe_constraint(subviews, constraint)
|
321
|
+
subviews_inverse = subviews.invert
|
322
|
+
item = subviews_inverse[constraint.firstItem] || "view"
|
323
|
+
item_attribute = ATTRIBUTE_LOOKUP_INVERSE[constraint.firstAttribute]
|
324
|
+
related_by = RELATED_BY_LOOKUP_INVERSE[constraint.relation]
|
325
|
+
to_item = subviews_inverse[constraint.secondItem] || "view"
|
326
|
+
to_item_attribute = ATTRIBUTE_LOOKUP_INVERSE[constraint.secondAttribute]
|
327
|
+
multiplier = constraint.multiplier
|
328
|
+
constant = constraint.constant
|
329
|
+
priority = PRIORITY_LOOKUP_INVERSE[constraint.priority] || constraint.priority
|
330
|
+
"#{constraint.description}\n#{item}.#{item_attribute} #{related_by} #{to_item}.#{to_item_attribute} * #{multiplier} + #{constant} @ #{priority}"
|
331
|
+
end
|
332
|
+
|
333
|
+
# transforms a view's NSLayoutConstraints into strings.
|
334
|
+
# pass the subviews hash just as you would to Layout#subviews=, followed by the view to
|
335
|
+
# describe
|
336
|
+
def self.describe_view(subviews, view)
|
337
|
+
view.constraints.map do |constraint|
|
338
|
+
describe_constraint(subviews, constraint)
|
339
|
+
end.join("\n")
|
340
|
+
end
|
341
|
+
|
342
|
+
private
|
343
|
+
|
344
|
+
def view_for_item(item)
|
345
|
+
if item == "view"
|
346
|
+
@view
|
347
|
+
elsif item == "topLayoutGuide"
|
348
|
+
@block_owner && @block_owner.topLayoutGuide
|
349
|
+
elsif item == "bottomLayoutGuide"
|
350
|
+
@block_owner && @block_owner.bottomLayoutGuide
|
351
|
+
elsif v = (@subviews && @subviews[item])
|
352
|
+
v
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|
data/lib/motion/ui.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
class RMX
|
2
|
+
|
3
|
+
def self.app
|
4
|
+
UIApplication.sharedApplication
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.ios_version
|
8
|
+
@ios_version ||= UIDevice.currentDevice.systemVersion.split(".").take(2).join(".").to_f
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.screen_pixel
|
12
|
+
1.0 / UIScreen.mainScreen.scale
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.keyboardWillChangeFrame(notification)
|
16
|
+
@keyboardWillChangeFrameNotification = notification
|
17
|
+
processKeyboardWillChange
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.processKeyboardWillChange
|
21
|
+
return unless notification = @keyboardWillChangeFrameNotification
|
22
|
+
info = notification.userInfo
|
23
|
+
keyboardFrame = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue
|
24
|
+
bounds = UIScreen.mainScreen.bounds
|
25
|
+
animationDuration = info.objectForKey(UIKeyboardAnimationDurationUserInfoKey).doubleValue
|
26
|
+
# below the screen # above the screen # left of the screen # right of the screen
|
27
|
+
currentKeyboardHeight = if keyboardFrame.origin.y >= bounds.size.height || keyboardFrame.origin.y <= bounds.origin.y - keyboardFrame.size.height || keyboardFrame.origin.x <= bounds.origin.x - keyboardFrame.size.width || keyboardFrame.origin.x >= bounds.size.width
|
28
|
+
0
|
29
|
+
else
|
30
|
+
keyboardFrame.size.height
|
31
|
+
end
|
32
|
+
# p "================>"
|
33
|
+
if currentKeyboardHeight != @currentKeyboardHeight
|
34
|
+
@currentKeyboardHeight = currentKeyboardHeight
|
35
|
+
# p "currentKeyboardHeight", currentKeyboardHeight
|
36
|
+
# p "keyboardFrame", keyboardFrame
|
37
|
+
# p "UIScreen.mainScreen.bounds", UIScreen.mainScreen.bounds
|
38
|
+
NSNotificationCenter.defaultCenter.postNotificationName("rmxKeyboardChanged", object:nil, userInfo:{
|
39
|
+
:height => currentKeyboardHeight,
|
40
|
+
:animationDuration => animationDuration
|
41
|
+
})
|
42
|
+
end
|
43
|
+
@keyboardWillChangeFrameNotification = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.currentKeyboardHeight
|
47
|
+
@currentKeyboardHeight || 0
|
48
|
+
end
|
49
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector:'keyboardWillChangeFrame:', name:UIKeyboardWillChangeFrameNotification, object:nil)
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def RMX(_object)
|
54
|
+
RMX.new(_object)
|
55
|
+
end
|
data/lib/motion/util.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
class RMX
|
2
|
+
|
3
|
+
def self.safe_block(block)
|
4
|
+
weak_block_owner_holder = RMXWeakHolder.new(block.owner)
|
5
|
+
block.weak!
|
6
|
+
proc do |*args|
|
7
|
+
if wbo = weak_block_owner_holder.value
|
8
|
+
block.call(*args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raises an exception when called from a thread other than the main thread.
|
14
|
+
# Good for development and experimenting.
|
15
|
+
def self.assert_main_thread!
|
16
|
+
raise "Expected main thread. #{Dispatch::Queue.current.description}" unless NSThread.currentThread.isMainThread
|
17
|
+
end
|
18
|
+
|
19
|
+
# call the block immediately if called on the main thread,
|
20
|
+
# otherwise call it async on the main queue
|
21
|
+
def self.inline_or_on_main_q(&block)
|
22
|
+
if NSThread.currentThread.isMainThread
|
23
|
+
block.call
|
24
|
+
else
|
25
|
+
Dispatch::Queue.main do
|
26
|
+
block.call
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# call the block immediately if called on the main thread with the given args,
|
32
|
+
# otherwise call it async on the main queue.
|
33
|
+
# silently ignores nil blocks to avoid if !block.nil? checks, useful for async callbacks
|
34
|
+
# that optionally take a callback
|
35
|
+
def self.block_on_main_q(block, *args)
|
36
|
+
unless block.nil?
|
37
|
+
inline_or_on_main_q do
|
38
|
+
block.call(*args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def require_queue!(queue, file, line)
|
44
|
+
unless Dispatch::Queue.current.description == queue.description
|
45
|
+
raise "WRONG QUEUE: was: #{Dispatch::Queue.current.description}, expected: #{queue.description}. #{@object.value.inspect} #{file}:#{line}, #{caller.inspect}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def own_methods
|
50
|
+
if object = unsafe_unretained_object
|
51
|
+
(object.methods - (object.superclass.methods)).sort
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Shortcut to instance_variable_get and instance_variable_get:
|
56
|
+
# 1 arg for instance_variable_get
|
57
|
+
# 1 arg and block for instance_variable_get || instance_variable_set
|
58
|
+
# 2 args for instance_variable_set
|
59
|
+
def ivar(*args, &block)
|
60
|
+
if object = unsafe_unretained_object
|
61
|
+
key = args[0]
|
62
|
+
val = nil
|
63
|
+
if args.size == 1
|
64
|
+
if block
|
65
|
+
val = object.instance_variable_get("@#{key}")
|
66
|
+
if val.nil?
|
67
|
+
val = block.call
|
68
|
+
object.instance_variable_set("@#{key}", val)
|
69
|
+
val
|
70
|
+
end
|
71
|
+
else
|
72
|
+
val = object.instance_variable_get("@#{key}")
|
73
|
+
end
|
74
|
+
elsif args.size == 2
|
75
|
+
val = args[1]
|
76
|
+
object.instance_variable_set("@#{key}", val)
|
77
|
+
else
|
78
|
+
raise "RMX#ivar called with invalid arguments: #{args.inspect}"
|
79
|
+
end
|
80
|
+
val
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def nil_instance_variables!
|
85
|
+
if object = unsafe_unretained_object
|
86
|
+
ivars = [] + object.instance_variables
|
87
|
+
while ivar = ivars.pop
|
88
|
+
object.instance_variable_set(ivar, nil)
|
89
|
+
end
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def debounce(unique_id, opts={}, &block)
|
95
|
+
if (seconds = opts[:seconds]) && seconds > 0
|
96
|
+
debounce_seconds(seconds, unique_id, opts[:now], &block)
|
97
|
+
else
|
98
|
+
debounce_runloop(unique_id, opts[:now], &block)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def debounce_runloop(unique_id, run_immediately=false, &block)
|
103
|
+
if object = unsafe_unretained_object
|
104
|
+
lookup = Thread.current["rmx_debounce_runloop"] ||= {}
|
105
|
+
key = [ object, unique_id ]
|
106
|
+
lookup[key] ||= begin
|
107
|
+
block.call if run_immediately
|
108
|
+
CFRunLoopPerformBlock(
|
109
|
+
CFRunLoopGetCurrent(),
|
110
|
+
KCFRunLoopDefaultMode,
|
111
|
+
lambda do
|
112
|
+
lookup.delete(key)
|
113
|
+
block.call
|
114
|
+
end
|
115
|
+
)
|
116
|
+
true
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def debounce_seconds(seconds, unique_id, run_immediately=false, &block)
|
123
|
+
if object = unsafe_unretained_object
|
124
|
+
lookup = Thread.current["rmx_debounce_seconds"] ||= {}
|
125
|
+
key = [ object, unique_id ]
|
126
|
+
lookup[key] ||= begin
|
127
|
+
block.call if run_immediately
|
128
|
+
units = CFGregorianUnits.new
|
129
|
+
units.seconds = seconds
|
130
|
+
CFRunLoopAddTimer(
|
131
|
+
CFRunLoopGetCurrent(),
|
132
|
+
CFRunLoopTimerCreateWithHandler(
|
133
|
+
KCFAllocatorDefault,
|
134
|
+
CFAbsoluteTimeAddGregorianUnits(
|
135
|
+
CFAbsoluteTimeGetCurrent(),
|
136
|
+
nil,
|
137
|
+
units
|
138
|
+
),
|
139
|
+
0,
|
140
|
+
0,
|
141
|
+
0,
|
142
|
+
lambda do |timer|
|
143
|
+
lookup.delete(key)
|
144
|
+
block.call
|
145
|
+
end
|
146
|
+
),
|
147
|
+
KCFRunLoopDefaultMode
|
148
|
+
)
|
149
|
+
true
|
150
|
+
end
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/rmx/version.rb
ADDED
data/lib/rmx.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "rmx/version"
|
2
|
+
|
3
|
+
unless defined?(Motion::Project::Config)
|
4
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
5
|
+
end
|
6
|
+
|
7
|
+
Motion::Project::App.setup do |app|
|
8
|
+
%w(
|
9
|
+
RMXObjectExtensions
|
10
|
+
base
|
11
|
+
env
|
12
|
+
layout
|
13
|
+
util
|
14
|
+
accessors
|
15
|
+
events
|
16
|
+
ui
|
17
|
+
RMXCommonMethods
|
18
|
+
RMXWeakHolder
|
19
|
+
RMXUnsafeUnretainedHolder
|
20
|
+
RMXWeakToStrongHash
|
21
|
+
RMXStrongToWeakHash
|
22
|
+
RMXSynchronizedStrongToWeakHash
|
23
|
+
RMXEventsFromProxy
|
24
|
+
RMXExecutionBlock
|
25
|
+
RMXSetAttributes
|
26
|
+
RMXViewControllerPresentation
|
27
|
+
RMXKeyboardHelpers
|
28
|
+
RMXNavigationController
|
29
|
+
RMXTableViewController
|
30
|
+
RMXViewController
|
31
|
+
RMXSegmentedController
|
32
|
+
RMXView
|
33
|
+
RMXActionSheet
|
34
|
+
RMXAutoLayoutLabel
|
35
|
+
RMXAutoLayoutScrollView
|
36
|
+
RMXTableHandler
|
37
|
+
RMXTableViewCell
|
38
|
+
RMXTableViewCellInnerContentView
|
39
|
+
).reverse.each do |x|
|
40
|
+
app.files.unshift(File.join(File.dirname(__FILE__), "motion/#{x}.rb"))
|
41
|
+
end
|
42
|
+
FileUtils.mkdir_p(File.expand_path("../build", File.dirname(__FILE__)))
|
43
|
+
env_filename = File.expand_path("../build/env.rb", File.dirname(__FILE__))
|
44
|
+
rmx_env = ENV['rmx_env'] == '1' ? ENV.to_hash : {}
|
45
|
+
File.open(env_filename, "w") do |f|
|
46
|
+
f.puts %Q{class RMX\n Env = #{rmx_env.inspect}\nend\n}
|
47
|
+
end
|
48
|
+
App.log "RMX::Env", rmx_env.inspect
|
49
|
+
app.files.unshift(env_filename)
|
50
|
+
end
|
Binary file
|
data/rmx.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rmx/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "rmx"
|
8
|
+
gem.version = RMX::VERSION
|
9
|
+
gem.authors = ["Joe Noon"]
|
10
|
+
gem.email = ["joenoon@gmail.com"]
|
11
|
+
gem.description = %q{Extensions and helpers for dealing with various areas of rubymotion}
|
12
|
+
gem.summary = %q{Extensions and helpers for dealing with various areas of rubymotion}
|
13
|
+
gem.homepage = "https://github.com/joenoon/rmx"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|