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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbf506f0e146d719368d97d86a04b4fede0df97b
4
- data.tar.gz: 6d8b171e1add24ee6c63d27ee4057c6051f944a4
3
+ metadata.gz: dddb35f12d5cf8cc9de4cb3b39df6644497bf550
4
+ data.tar.gz: 93e202d15df61498e5432194d8e2466d37ece16d
5
5
  SHA512:
6
- metadata.gz: 3204fde1f0f0e9c903aa2fdda8a638f4c91544e2a81b404b728eb9bd100200ce8c81fa5d69cc6d2a57002a1182d968d331b8164e629fa30227b6f426e2ccdb18
7
- data.tar.gz: 8fc6a988442c02b7ba501b7c360a1825d20ed40899b6707d3d4b179cb8e51c865f2552ab59357ea0a64368341a3345be2918aabdb7dc0f9f618c6ca11fcb825c
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
- ## Equation-style AutoLayout Constraints
6
+ #### [Documentation](https://github.com/joenoon/rm-extensions/wiki/RMExtensions-Documentation)
7
7
 
8
- AutoLayout is a great way to lay out views, but writing constraints manually is
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, [joenoon](https://github.com/joenoon)
40
+ [Joe Noon](https://github.com/joenoon)
@@ -8,26 +8,22 @@ module RMExtensions
8
8
  @rmext_observation_proxy ||= ObservationProxy.new(self)
9
9
  end
10
10
 
11
- # observe an object.key. takes a block that will be called with the
12
- # new value upon change.
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
- # like +rmext_observe_passive+ but additionally fires the callback immediately.
22
- def rmext_observe(object, key, &block)
23
- rmext_observe_passive(object, key, &block)
24
- block.call(object.send(key)) unless block.nil?
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, &block)
43
- rmext_observation_proxy.on(event, &block)
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, &block)
43
+ def rmext_off(event, inContext:context, withBlock:block)
48
44
  if @rmext_observation_proxy
49
- @rmext_observation_proxy.off(event, &block)
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 args on this object
61
- def rmext_trigger(event, *args)
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, *args)
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 for #{@desc}"
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 for #{@desc}"
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, &block)
113
- target.addObserver(self, forKeyPath:key_path, options:DEFAULT_OPTIONS, context:nil) unless registered?(target, key_path)
114
- add_observer_block(target, key_path, &block)
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
- key_paths = @targets[target]
127
- if !key_paths.nil? && key_paths.has_key?(key_path.to_s)
128
- key_paths.removeObject(key_path.to_s)
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.clone
134
- while keys.size > 0
135
- target = keys.pop
137
+ keys = [] + @targets.keys
138
+ while target = keys.pop
136
139
  target_hash = @targets[target]
137
- paths = target_hash.keys.clone
138
- while paths.size > 0
139
- key_path = paths.pop
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, &block)
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.to_s] ||= NSHashTable.weakObjectsHashTable
154
- @targets[target][key_path.to_s].addObject block
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
- rmext_on_main_q do
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
- blocks = key_paths[key_path] || []
165
- blocks.each do |block|
166
- args = [ change[NSKeyValueChangeNewKey] ]
167
- args << change[NSKeyValueChangeIndexesKey] if collection?(change)
168
- block.call(*args)
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 collection?(change)
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
- @events[event.to_s] ||= []
180
- @events[event.to_s].addObject block
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, &block)
184
- return if event.nil? || block.nil? || !@events.key?(event.to_s)
185
- @events[event.to_s].removeObject block
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, *args)
194
- return if event.nil? || !@events.key?(event.to_s)
195
- @events[event.to_s].each do |block|
196
- block.call(*args)
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
@@ -1,3 +1,3 @@
1
1
  module RMExtensions
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  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.2.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-26 00:00:00.000000000 Z
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
@@ -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.