rmx 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|