rm-extensions 0.2.0 → 0.3.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 +4 -4
- data/README.md +3 -282
- data/lib/motion/observation.rb +135 -66
- data/lib/rm-extensions/version.rb +1 -1
- metadata +2 -3
- data/CHANGELOG.md +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dddb35f12d5cf8cc9de4cb3b39df6644497bf550
|
4
|
+
data.tar.gz: 93e202d15df61498e5432194d8e2466d37ece16d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8494d5c4ee43c3bca403556e063b66cefc671f7b6cb56a2e5b70332a3332007f285377ae3d6beafb348d031623af22eaeda30ef38bc5955d8de0517ef3245a4
|
7
|
+
data.tar.gz: ddbc5a99d7939b0c9c2ffca586d852fb783f2216a388d638dfe20c4ea1aa656cf75dfb07ed6dba3b1fa3e33eea7b88ed1b3f128fb97f3e3b75139baa8904dabc
|
data/README.md
CHANGED
@@ -3,285 +3,9 @@ RMExtensions
|
|
3
3
|
|
4
4
|
#### Extensions and helpers for dealing with various areas of rubymotion and iOS.
|
5
5
|
|
6
|
-
|
6
|
+
#### [Documentation](https://github.com/joenoon/rm-extensions/wiki/RMExtensions-Documentation)
|
7
7
|
|
8
|
-
|
9
|
-
confusing and verbose.
|
10
|
-
|
11
|
-
Using Apple's "Visual Format Language" ASCII-inspired
|
12
|
-
strings can improve things, but it has drawbacks: **1)** It returns an array
|
13
|
-
of constraints. This means if you plan on altering one of them later, or removing
|
14
|
-
one in particular, you would have to loop through all of the constraints, testing
|
15
|
-
each one to see if its the one you want. **2)** The `options` argument adds additional
|
16
|
-
constraints. For example, if you specify NSLayoutAttributeCenterY to a horizontal string,
|
17
|
-
an additional constraint will be added for each view to set their centerY's equal to each
|
18
|
-
other. This compounds problem #1. The chances you will get an error that the layout system
|
19
|
-
cannot satisfy your constraints is probably because of these "extra" constraints. **3)**
|
20
|
-
It can't handle complex constraints, so you end up needing to supplement it with
|
21
|
-
verbose low-level constraint creation anyway.
|
22
|
-
|
23
|
-
Apple makes note of how constraints can be thought of like a linear equation:
|
24
|
-
|
25
|
-
http://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutConstraint_Class/NSLayoutConstraint/NSLayoutConstraint.html
|
26
|
-
|
27
|
-
**Remember the formula:**
|
28
|
-
|
29
|
-
```ruby
|
30
|
-
view1.attr1 == view2.attr2 * multiplier + constant @ priority
|
31
|
-
```
|
32
|
-
|
33
|
-
**We can actually use this super-simple formula to write ALL of our constraints, simple OR complex!**
|
34
|
-
|
35
|
-
Once you get the hang of the formula and visualization of how the geometry works, it becomes easy to create complex layouts with little
|
36
|
-
effort. And, in my opinion, its only slightly more verbose than the visual format language, but much clearer, and you only end up
|
37
|
-
with the exact constraints you want.
|
38
|
-
|
39
|
-
Available values for `attr1` and `attr2` are:
|
40
|
-
|
41
|
-
- left
|
42
|
-
- right
|
43
|
-
- top
|
44
|
-
- bottom
|
45
|
-
- leading
|
46
|
-
- trailing
|
47
|
-
- width
|
48
|
-
- height
|
49
|
-
- centerX
|
50
|
-
- centerY
|
51
|
-
- baseline
|
52
|
-
|
53
|
-
Available `relation` values are:
|
54
|
-
|
55
|
-
- ==
|
56
|
-
- <=
|
57
|
-
- >=
|
58
|
-
|
59
|
-
Available `priority` values are:
|
60
|
-
|
61
|
-
- required (1000 is the default)
|
62
|
-
- high (750)
|
63
|
-
- low (250)
|
64
|
-
- fit (50)
|
65
|
-
- or, you can use your own value between 1-1000
|
66
|
-
|
67
|
-
|
68
|
-
### Examples
|
69
|
-
|
70
|
-
Here is a real example. The Layout instance is created just like the motion-layout gem. `layout.view` sets the view
|
71
|
-
that will act as the 'superview' to the views set in `layout.subviews`. Thats where the similarities end with
|
72
|
-
motion-layout. With RMExtensions::Layout, there are two methods: `eq` and `eqs`, short for equation and equations.
|
73
|
-
|
74
|
-
- `layout.eq` takes one string, and returns *one constraint*
|
75
|
-
- `layout.eqs` takes one string, assumes multiple constraints are separated by newlines, and returns an *array of constraints*
|
76
|
-
|
77
|
-
```ruby
|
78
|
-
RMExtensions::Layout.new do |layout|
|
79
|
-
layout.view view
|
80
|
-
layout.subviews({
|
81
|
-
"calendar" => calendarView,
|
82
|
-
"table" => tableView,
|
83
|
-
"shadow" => line
|
84
|
-
})
|
85
|
-
|
86
|
-
layout.eqs %Q{
|
87
|
-
calendar.left == 0
|
88
|
-
calendar.right == 0
|
89
|
-
table.left == 7
|
90
|
-
table.right == -7
|
91
|
-
shadow.left == 0
|
92
|
-
shadow.right == 0
|
93
|
-
|
94
|
-
calendar.top == 0
|
95
|
-
table.top == calendar.bottom
|
96
|
-
table.bottom == 0
|
97
|
-
shadow.top == table.top
|
98
|
-
}
|
99
|
-
|
100
|
-
@calendar_height_constraint = layout.eq "calendar.height == 0"
|
101
|
-
end
|
102
|
-
```
|
103
|
-
|
104
|
-
Above, **calendar.left == 0** is short for **calendar.left == view.left * 1.0 + 0 @ 1000**. If no view2 is given, the superview ('view') is assumed.
|
105
|
-
If no multiplier is given, 1.0 is assumed. If no constant is given, 0 is assumed. If no priority is given, "required" (1000) is assumed.
|
106
|
-
The last constraint is created separately and stored in @calendar_height_constraint, because I want to be able to change the calendar's height
|
107
|
-
any time I want.
|
108
|
-
|
109
|
-
Here is another example:
|
110
|
-
|
111
|
-
```ruby
|
112
|
-
RMExtensions::Layout.new do |layout|
|
113
|
-
layout.view self
|
114
|
-
layout.subviews({
|
115
|
-
"timeLabel" => @timeLabel,
|
116
|
-
"titleLabel" => @titleLabel,
|
117
|
-
"trackingImage" => @trackingImage,
|
118
|
-
"inOutStatusInImage" => @inOutStatusInImage,
|
119
|
-
"inOutStatusOutImage" => @inOutStatusOutImage,
|
120
|
-
"plannerImage" => @plannerImage,
|
121
|
-
"shareButton" => @shareButton,
|
122
|
-
"cancelledLabel" => @cancelledLabel,
|
123
|
-
"unreadImage" => @unreadImage
|
124
|
-
})
|
125
|
-
|
126
|
-
layout.eqs %Q{
|
127
|
-
unreadImage.left == 6
|
128
|
-
unreadImage.top == 6
|
129
|
-
plannerImage.left == 14
|
130
|
-
plannerImage.centerY == 0
|
131
|
-
plannerImage.width == 30
|
132
|
-
plannerImage.height == 30
|
133
|
-
trackingImage.left == timeLabel.right + 5
|
134
|
-
inOutStatusOutImage.left == trackingImage.right + 5
|
135
|
-
inOutStatusInImage.left == inOutStatusOutImage.right + 5
|
136
|
-
timeLabel.left == plannerImage.right + 5
|
137
|
-
timeLabel.baseline == plannerImage.bottom + 1
|
138
|
-
trackingImage.centerY == timeLabel.centerY
|
139
|
-
inOutStatusOutImage.centerY == timeLabel.centerY
|
140
|
-
inOutStatusInImage.centerY == timeLabel.centerY
|
141
|
-
titleLabel.left == cancelledLabel.right
|
142
|
-
cancelledLabel.left == plannerImage.right + 5
|
143
|
-
titleLabel.top == plannerImage.top - 4
|
144
|
-
cancelledLabel.centerY == titleLabel.centerY
|
145
|
-
shareButton.right == -10
|
146
|
-
shareButton.centerY == 0
|
147
|
-
titleLabel.resistH == low
|
148
|
-
shareButton.left >= titleLabel.right + 5
|
149
|
-
timeLabel.resistH == low
|
150
|
-
shareButton.left >= inOutStatusInImage.right + 5
|
151
|
-
}
|
152
|
-
|
153
|
-
end
|
154
|
-
```
|
155
|
-
|
156
|
-
Keep in mind none of these lines are using the multiplier, and thats OK. I've only needed it on one constraint in my entire app so far,
|
157
|
-
so don't think its odd if you can't find a use for it.
|
158
|
-
|
159
|
-
There are two special cases at the moment. **titleLabel.resistH == low** is not a "real" constraint. Its a shortcut to
|
160
|
-
`setContentCompressionResistancePriority`, and since its common when dealing with autolayout, its nice to include it
|
161
|
-
in our layout code. The same is done for `setContentHuggingPriority`. The full list of "special cases" at the moment:
|
162
|
-
|
163
|
-
- view1.resistH == priority
|
164
|
-
- view1.resistV == priority
|
165
|
-
- view1.hugH == priority
|
166
|
-
- view1.hugV == priority
|
167
|
-
|
168
|
-
"priority" can be one of the values listed earlier, or your own number between 1-1000.
|
169
|
-
|
170
|
-
### Debugging constraints
|
171
|
-
|
172
|
-
- You can include a **?** on any line, and debug output will be printed when that constraint is built:
|
173
|
-
|
174
|
-
```ruby
|
175
|
-
layout.eqs %Q{
|
176
|
-
label.left == photo.right + 5 ?
|
177
|
-
}
|
178
|
-
```
|
179
|
-
|
180
|
-
- Since layout.eqs allows you to write many constraints in one string, and sometimes its nice to
|
181
|
-
keep comments next to constraints, **comments are allowed**:
|
182
|
-
|
183
|
-
```ruby
|
184
|
-
layout.eqs %Q{
|
185
|
-
commentsCount.width == likesCount.width @ low # the widths of the labels prefer to be the same
|
186
|
-
}
|
187
|
-
```
|
188
|
-
|
189
|
-
### Things to remember
|
190
|
-
|
191
|
-
- Remember you usually want negative constants for right and bottom. For example: label.right == -10 means "label's right should be 10 away from the right side of the superview". But if you accidentally said label.right == 10, you would have created "label's right should be 10 PAST the right side of the superview". It may require you to adjust your thinking.
|
192
|
-
|
193
|
-
- The formula is just shorthand for `constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:`. You should **really** read up on constraints and
|
194
|
-
understand this method, to fully understand the power and simplicity the shorthand formula gives you.
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
## Observation/KVO, Events
|
204
|
-
|
205
|
-
#### Make observations without needing to clean up/unobserve
|
206
|
-
|
207
|
-
Call from anywhere on anything:
|
208
|
-
|
209
|
-
```ruby
|
210
|
-
class MyViewController < UIViewController
|
211
|
-
def viewDidLoad
|
212
|
-
super.tap do
|
213
|
-
rmext_observe(@model, "name") do |val|
|
214
|
-
p "name is #{val}"
|
215
|
-
end
|
216
|
-
foo.rmext_on(:some_event) do |val|
|
217
|
-
p "some_event called with #{val.inspect}"
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
def test_trigger
|
222
|
-
foo.rmext_trigger(:some_event, "hello!")
|
223
|
-
end
|
224
|
-
end
|
225
|
-
```
|
226
|
-
|
227
|
-
Differences from BW::KVO and BW::Reactor::Eventable:
|
228
|
-
|
229
|
-
- No need to include a module in the class you wish to use it on
|
230
|
-
- the observation happens on a proxy object
|
231
|
-
- KVO: The default is to observe and immediately fire the supplied callback
|
232
|
-
- KVO: The callback only takes one argument, the new value
|
233
|
-
- KVO: the object observing is not retained, and when it is deallocated, the observation
|
234
|
-
will be removed automatically for you. there is typically no need to clean up manually
|
235
|
-
|
236
|
-
|
237
|
-
## Accessors
|
238
|
-
|
239
|
-
#### weak attr_accessors:
|
240
|
-
|
241
|
-
```ruby
|
242
|
-
|
243
|
-
class MyView < UIView
|
244
|
-
rmext_weak_attr_accessor :delegate
|
245
|
-
end
|
246
|
-
|
247
|
-
class MyViewController < UIViewController
|
248
|
-
def viewDidLoad
|
249
|
-
super.tap do
|
250
|
-
v = MyView.alloc.initWithFrame(CGRectZero)
|
251
|
-
view.addSubview(v)
|
252
|
-
v.delegate = self
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
```
|
258
|
-
|
259
|
-
## Queues
|
260
|
-
|
261
|
-
#### Wraps GCD:
|
262
|
-
|
263
|
-
```ruby
|
264
|
-
# note +i+ will appear in order, and the thread will never change (main)
|
265
|
-
100.times do |i|
|
266
|
-
rmext_on_main_q do
|
267
|
-
p "i: #{i} thread: #{NSThread.currentThread}"
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
# note +i+ will appear in order, and the thread will change
|
272
|
-
100.times do |i|
|
273
|
-
rmext_on_serial_q("testing") do
|
274
|
-
p "i: #{i} thread: #{NSThread.currentThread}"
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
# note +i+ will sometimes appear out of order, and the thread will change
|
279
|
-
100.times do |i|
|
280
|
-
rmext_on_concurrent_q("testing") do
|
281
|
-
p "i: #{i} thread: #{NSThread.currentThread}"
|
282
|
-
end
|
283
|
-
end
|
284
|
-
```
|
8
|
+
#### [CHANGELOG](https://github.com/joenoon/rm-extensions/wiki/CHANGELOG)
|
285
9
|
|
286
10
|
Installation
|
287
11
|
-----------------
|
@@ -294,8 +18,6 @@ And then execute:
|
|
294
18
|
|
295
19
|
$ bundle
|
296
20
|
|
297
|
-
* Currently depends on bubblewrap (for BW::KVO).
|
298
|
-
|
299
21
|
Contributing
|
300
22
|
-----------------
|
301
23
|
|
@@ -312,8 +34,7 @@ License
|
|
312
34
|
|
313
35
|
Please see [LICENSE](https://github.com/joenoon/rm-extensions/blob/master/LICENSE.txt) for licensing details.
|
314
36
|
|
315
|
-
|
316
37
|
Author
|
317
38
|
-----------------
|
318
39
|
|
319
|
-
Joe Noon
|
40
|
+
[Joe Noon](https://github.com/joenoon)
|
data/lib/motion/observation.rb
CHANGED
@@ -8,26 +8,22 @@ module RMExtensions
|
|
8
8
|
@rmext_observation_proxy ||= ObservationProxy.new(self)
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
# rmext_observe_passive(@model, "name") do |val|
|
15
|
-
# p "name is #{val}"
|
16
|
-
# end
|
17
|
-
def rmext_observe_passive(object, key, &block)
|
18
|
-
rmext_observation_proxy.observe(object, key, &block)
|
11
|
+
def rmext_observe_passive(object, keyPath:key, withBlock:block)
|
12
|
+
rmext_observe(object, keyPath:key, options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld), withBlock:block)
|
19
13
|
end
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
def rmext_observe(object, keyPath:key, withBlock:block)
|
16
|
+
rmext_observe(object, keyPath:key, options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial), withBlock:block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def rmext_observe(object, keyPath:key, options:options, withBlock:block)
|
20
|
+
rmext_observation_proxy.observe(object, keyPath:key, options:options, withBlock:block)
|
25
21
|
end
|
26
22
|
|
27
23
|
# unobserve an existing observation
|
28
|
-
def rmext_unobserve(object, key)
|
24
|
+
def rmext_unobserve(object, keyPath:key)
|
29
25
|
if @rmext_observation_proxy
|
30
|
-
@rmext_observation_proxy.unobserve(object, key)
|
26
|
+
@rmext_observation_proxy.unobserve(object, keyPath:key)
|
31
27
|
end
|
32
28
|
end
|
33
29
|
|
@@ -39,14 +35,14 @@ module RMExtensions
|
|
39
35
|
end
|
40
36
|
|
41
37
|
# register a callback when an event is trigger on this object
|
42
|
-
def rmext_on(event,
|
43
|
-
rmext_observation_proxy.on(event,
|
38
|
+
def rmext_on(event, inContext:context, withBlock:block)
|
39
|
+
rmext_observation_proxy.on(event, inContext:context, withBlock:block)
|
44
40
|
end
|
45
41
|
|
46
42
|
# remove a specific callback for an event on this object
|
47
|
-
def rmext_off(event,
|
43
|
+
def rmext_off(event, inContext:context, withBlock:block)
|
48
44
|
if @rmext_observation_proxy
|
49
|
-
@rmext_observation_proxy.off(event,
|
45
|
+
@rmext_observation_proxy.off(event, inContext:context, withBlock:block)
|
50
46
|
end
|
51
47
|
end
|
52
48
|
|
@@ -57,10 +53,10 @@ module RMExtensions
|
|
57
53
|
end
|
58
54
|
end
|
59
55
|
|
60
|
-
# trigger an event with
|
61
|
-
def rmext_trigger(event,
|
56
|
+
# trigger an event with value on this object
|
57
|
+
def rmext_trigger(event, value=nil)
|
62
58
|
if @rmext_observation_proxy
|
63
|
-
@rmext_observation_proxy.trigger(event,
|
59
|
+
@rmext_observation_proxy.trigger(event, value)
|
64
60
|
end
|
65
61
|
end
|
66
62
|
|
@@ -75,19 +71,26 @@ module RMExtensions
|
|
75
71
|
|
76
72
|
end
|
77
73
|
|
74
|
+
class ObservationResponse
|
75
|
+
attr_accessor :context, :value, :old_value, :target, :key, :indexes, :kind
|
76
|
+
end
|
77
|
+
|
78
|
+
class EventResponse
|
79
|
+
attr_accessor :context, :value, :target, :event
|
80
|
+
end
|
81
|
+
|
78
82
|
# # Proxy class used to hold the actual observation and watches for the real
|
79
83
|
# # class intended to hold the observation to be deallocated, so the
|
80
84
|
# # observation can be cleaned up.
|
81
85
|
class ObservationProxy
|
82
|
-
COLLECTION_OPERATIONS = [ NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement ]
|
83
|
-
DEFAULT_OPTIONS = NSKeyValueObservingOptionNew
|
84
86
|
|
85
87
|
def initialize(obj)
|
88
|
+
@weak_object = WeakRef.new(obj)
|
86
89
|
@desc = obj.inspect
|
87
|
-
@events =
|
90
|
+
@events = NSMapTable.weakToStrongObjectsMapTable
|
88
91
|
@targets = {}
|
89
92
|
if ::RMExtensions.debug?
|
90
|
-
p "created ObservationProxy
|
93
|
+
p "created ObservationProxy(#{@desc})"
|
91
94
|
end
|
92
95
|
end
|
93
96
|
|
@@ -95,26 +98,27 @@ module RMExtensions
|
|
95
98
|
@did_dealloc = true
|
96
99
|
cleanup
|
97
100
|
if ::RMExtensions.debug?
|
98
|
-
p "dealloc ObservationProxy
|
101
|
+
p "dealloc ObservationProxy(#{@desc})"
|
99
102
|
end
|
100
103
|
super
|
101
104
|
end
|
102
105
|
|
103
106
|
def cleanup
|
104
107
|
if ::RMExtensions.debug?
|
105
|
-
p "cleanup #{@desc}"
|
108
|
+
p "cleanup ObservationProxy(#{@desc})"
|
106
109
|
end
|
107
110
|
unobserve_all
|
108
111
|
off_all
|
109
112
|
true
|
110
113
|
end
|
111
114
|
|
112
|
-
def observe(target, key_path,
|
113
|
-
|
114
|
-
add_observer_block(target, key_path,
|
115
|
+
def observe(target, keyPath:key_path, options:options, withBlock:block)
|
116
|
+
already_registered = registered?(target, key_path)
|
117
|
+
add_observer_block(target, key_path, block)
|
118
|
+
target.addObserver(self, forKeyPath:key_path, options:options, context:nil) unless already_registered
|
115
119
|
end
|
116
120
|
|
117
|
-
def unobserve(target, key_path)
|
121
|
+
def unobserve(target, keyPath:key_path)
|
118
122
|
return unless registered?(target, key_path)
|
119
123
|
target.removeObserver(self, forKeyPath:key_path)
|
120
124
|
remove_observer_block(target, key_path)
|
@@ -122,22 +126,20 @@ module RMExtensions
|
|
122
126
|
|
123
127
|
def remove_observer_block(target, key_path)
|
124
128
|
return if target.nil? || key_path.nil?
|
125
|
-
|
126
|
-
|
127
|
-
if !
|
128
|
-
|
129
|
+
key_path = key_path.to_s
|
130
|
+
target_hash = @targets[target]
|
131
|
+
if !target_hash.nil? && target_hash.has_key?(key_path)
|
132
|
+
target_hash.delete(key_path)
|
129
133
|
end
|
130
134
|
end
|
131
135
|
|
132
136
|
def unobserve_all
|
133
|
-
keys = @targets.keys
|
134
|
-
while keys.
|
135
|
-
target = keys.pop
|
137
|
+
keys = [] + @targets.keys
|
138
|
+
while target = keys.pop
|
136
139
|
target_hash = @targets[target]
|
137
|
-
paths = target_hash.keys
|
138
|
-
while paths.
|
139
|
-
key_path
|
140
|
-
target.removeObserver(self, forKeyPath:key_path)
|
140
|
+
paths = [] + target_hash.keys
|
141
|
+
while key_path = paths.pop
|
142
|
+
unobserve(target, keyPath:key_path)
|
141
143
|
end
|
142
144
|
end
|
143
145
|
@targets.removeAllObjects
|
@@ -147,42 +149,80 @@ module RMExtensions
|
|
147
149
|
!target.nil? && !@targets[target].nil? && @targets[target].has_key?(key_path.to_s)
|
148
150
|
end
|
149
151
|
|
150
|
-
def add_observer_block(target, key_path,
|
152
|
+
def add_observer_block(target, key_path, block)
|
151
153
|
return if target.nil? || key_path.nil? || block.nil?
|
154
|
+
key_path = key_path.to_s
|
152
155
|
@targets[target] ||= {}
|
153
|
-
@targets[target][key_path
|
154
|
-
@targets[target][key_path
|
156
|
+
@targets[target][key_path] ||= []
|
157
|
+
@targets[target][key_path].addObject block
|
155
158
|
end
|
156
159
|
|
157
160
|
# NSKeyValueObserving Protocol
|
158
161
|
|
159
162
|
def observeValueForKeyPath(key_path, ofObject:target, change:change, context:context)
|
160
|
-
|
163
|
+
m_desc = nil
|
164
|
+
if ::RMExtensions.debug?
|
165
|
+
m_desc = "~~> ObservationProxy(#{@desc})#observeValueForKeyPath(#{key_path}, ofObject:#{target.inspect.split(" ").first}>, ...)"
|
166
|
+
p "called", m_desc
|
167
|
+
end
|
168
|
+
action = proc do
|
161
169
|
next if @did_dealloc
|
162
170
|
next if target.nil?
|
163
|
-
key_paths = @targets[target]
|
164
|
-
|
165
|
-
blocks
|
166
|
-
|
167
|
-
|
168
|
-
|
171
|
+
key_paths = @targets[target]
|
172
|
+
next if key_paths.nil?
|
173
|
+
blocks = key_paths[key_path]
|
174
|
+
next if blocks.nil?
|
175
|
+
blocks = [] + blocks # get a new array that can be popped
|
176
|
+
if ::RMExtensions.debug?
|
177
|
+
p "blocks.size", blocks.size, m_desc
|
178
|
+
end
|
179
|
+
while blk = blocks.pop
|
180
|
+
res = ObservationResponse.new
|
181
|
+
res.context = @weak_object
|
182
|
+
res.value = change[NSKeyValueChangeNewKey]
|
183
|
+
res.old_value = change[NSKeyValueChangeOldKey]
|
184
|
+
res.target = target
|
185
|
+
res.key = key_path
|
186
|
+
res.indexes = change[NSKeyValueChangeIndexesKey]
|
187
|
+
res.kind = change[NSKeyValueChangeKindKey]
|
188
|
+
blk.call(res)
|
169
189
|
end
|
170
190
|
end
|
191
|
+
if NSThread.currentThread.isMainThread
|
192
|
+
if ::RMExtensions.debug?
|
193
|
+
p "inline execution", m_desc
|
194
|
+
end
|
195
|
+
action.call
|
196
|
+
else
|
197
|
+
if ::RMExtensions.debug?
|
198
|
+
p "dispatch execution", m_desc
|
199
|
+
end
|
200
|
+
rmext_on_main_q(&action)
|
201
|
+
end
|
171
202
|
end
|
172
203
|
|
173
|
-
def
|
174
|
-
COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
|
175
|
-
end
|
176
|
-
|
177
|
-
def on(event, &block)
|
204
|
+
def on(event, inContext:context, withBlock:block)
|
178
205
|
return if event.nil? || block.nil?
|
179
|
-
|
180
|
-
|
206
|
+
event = event.to_s
|
207
|
+
context ||= self.class
|
208
|
+
unless context_events = @events.objectForKey(context)
|
209
|
+
context_events = {}
|
210
|
+
@events.setObject(context_events, forKey:context)
|
211
|
+
end
|
212
|
+
unless context_event_blocks = context_events.objectForKey(event)
|
213
|
+
context_event_blocks = []
|
214
|
+
context_events.setObject(context_event_blocks, forKey:event)
|
215
|
+
end
|
216
|
+
context_event_blocks.addObject block
|
181
217
|
end
|
182
218
|
|
183
|
-
def off(event,
|
184
|
-
return if event.nil? || block.nil?
|
185
|
-
|
219
|
+
def off(event, inContext:context, withBlock:block)
|
220
|
+
return if event.nil? || block.nil?
|
221
|
+
event = event.to_s
|
222
|
+
context ||= self.class
|
223
|
+
return unless context_events = @events.objectForKey(context)
|
224
|
+
return unless context_event_blocks = context_events.objectForKey(event)
|
225
|
+
context_event_blocks.removeObject block
|
186
226
|
nil
|
187
227
|
end
|
188
228
|
|
@@ -190,10 +230,39 @@ module RMExtensions
|
|
190
230
|
@events.removeAllObjects
|
191
231
|
end
|
192
232
|
|
193
|
-
def trigger(event,
|
194
|
-
|
195
|
-
|
196
|
-
|
233
|
+
def trigger(event, value)
|
234
|
+
m_desc = nil
|
235
|
+
if ::RMExtensions.debug?
|
236
|
+
m_desc = "~~> ObservationProxy(#{@desc})#trigger(#{event}, #{value.inspect.split(" ").first }>)"
|
237
|
+
p "called", m_desc
|
238
|
+
end
|
239
|
+
rmext_on_main_q do
|
240
|
+
next if @did_dealloc
|
241
|
+
next if event.nil?
|
242
|
+
event = event.to_s
|
243
|
+
keyEnumerator = @events.keyEnumerator
|
244
|
+
contexts = []
|
245
|
+
while context = keyEnumerator.nextObject
|
246
|
+
contexts.push context
|
247
|
+
end
|
248
|
+
while context = contexts.pop
|
249
|
+
if context_events = @events.objectForKey(context)
|
250
|
+
if event_blocks = context_events[event]
|
251
|
+
blocks = [] + event_blocks
|
252
|
+
if ::RMExtensions.debug?
|
253
|
+
p "blocks.size", blocks.size, m_desc
|
254
|
+
end
|
255
|
+
while blk = blocks.pop
|
256
|
+
res = EventResponse.new
|
257
|
+
res.context = context
|
258
|
+
res.value = value
|
259
|
+
res.target = @weak_object
|
260
|
+
res.event = event
|
261
|
+
blk.call(res)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
197
266
|
end
|
198
267
|
nil
|
199
268
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rm-extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Noon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Extensions and helpers for dealing with various areas of rubymotion
|
14
14
|
email:
|
@@ -18,7 +18,6 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- .gitignore
|
21
|
-
- CHANGELOG.md
|
22
21
|
- Gemfile
|
23
22
|
- LICENSE.txt
|
24
23
|
- README.md
|
data/CHANGELOG.md
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
## 0.2.0
|
2
|
-
|
3
|
-
[Commit history](https://github.com/joenoon/rm-extensions/compare/v0.1.10...v0.2.0)
|
4
|
-
|
5
|
-
* Experimental changes. Changelog to move to wiki soon.
|
6
|
-
|
7
|
-
## 0.1.10
|
8
|
-
|
9
|
-
[Commit history](https://github.com/joenoon/rm-extensions/compare/v0.1.9...v0.1.10)
|
10
|
-
|
11
|
-
* Layout: compact constraint(s) passed to #remove so a nil can be passed to reduce code
|
12
|
-
* Layout: adjust how to describe existing constraints
|
13
|
-
|
14
|
-
## 0.1.9
|
15
|
-
|
16
|
-
[Commit history](https://github.com/joenoon/rm-extensions/compare/v0.1.8...v0.1.9)
|
17
|
-
|
18
|
-
* Util: Added #rmext_ivar, which is a shortcut to instance_variable_get/instance_variable_set
|
19
|
-
* Layout: Added #reopen to yield the instance to a block for further processing
|
20
|
-
* Layout: Added an internal "constraint table" to keep track of normalized equations and
|
21
|
-
NSLayoutConstraint objects. equations can now be modified simply by re-applying the same
|
22
|
-
equation with a different constant.
|
23
|
-
* Layout: Added #xeq to remove a constraint by equation
|
24
|
-
* Layout: Added #remove(constraint(s)) to remove NSLayoutConstraint obects from the view
|
25
|
-
and also from the internal constraint table.
|
26
|
-
|
27
|
-
## 0.1.8
|
28
|
-
|
29
|
-
[Commit history](https://github.com/joenoon/rm-extensions/compare/v0.1.7...v0.1.8)
|
30
|
-
|
31
|
-
* Added RMExtensions::Layout#clear! which is a shortcut to view.removeConstraints(view.constraints).
|
32
|
-
It must be called after the `view` is set.
|
33
|
-
* Added 'max' as a valid priority shortcut, since it reads better than 'required' in many cases.
|
34
|
-
* Comments are now allowed on a line by themselves inside a `eqs` string, to make for easier commenting,
|
35
|
-
or to easily comment out an equation.
|
36
|
-
* Added a special `last_visible` view identifier which can be used instead of an actual view name
|
37
|
-
inside an equation. It will refer to the last view a constraint was applied to that was not hidden.
|
38
|
-
This can be used to more easily lay out constraints when some views should not be anchored against.
|
39
|
-
For example, you have 3 labels, A, B, and C, that you want stacked vertically:
|
40
|
-
A.top == 0
|
41
|
-
B.top == A.bottom + 5
|
42
|
-
C.top == B.bottom + 5
|
43
|
-
If B is hidden, there would be 10px in between A and C. Instead, you can let RMExtensions::Layout handle
|
44
|
-
this for you:
|
45
|
-
A.top == 0 # last_visible is set to A
|
46
|
-
B.top == last_visible.bottom + 5 # B is hidden, so last_visible remains A
|
47
|
-
C.top == last_visible.bottom + 5 # C now aligns to the bottom of A
|
48
|
-
It is usually easier and cleaner to build all possible views and mark some hidden than it is to have
|
49
|
-
many if/else view creation and constraint construction specific to what should be displayed.
|