motion-wiretap 1.1.5 → 1.1.6

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: 8a0ba74d89d7665f5da90026f2547c5a6b76ca5f
4
- data.tar.gz: aad024532ba78acde032d7b77f9beabdd37e1212
3
+ metadata.gz: 7d28f1e832294f594579798c62718339c9822a31
4
+ data.tar.gz: aeeec36e2613e003f5d90788ffad2b9a06c4e56c
5
5
  SHA512:
6
- metadata.gz: d534884d01c3d1f90ebcb09550007cdfb1fab5351162c2e6e5d43d2aa1ede4e87f533a8ac822f74cef120ea2f6fea0ef612e439a818cb70e1cd664c73a6d04f5
7
- data.tar.gz: aef50be40d464e1642d8da25eebfc3fa1046180e870b5a750ed67d8db1ddb90efb77fae5281df137d1cac0ac03149e00ee920d628fe0c382195d21c5d87691aa
6
+ metadata.gz: 19c5b44d1a0b39827bf145baa0664f099ea239e487936a24246609aac88422a56cd2f8809c31043281618153c3fdf78748044a4a6808dc6e22721c004e534ee2
7
+ data.tar.gz: 6043ff1c69787e815f115d616399d19ad1296dc9fc9b08b3b2a67ce50f0febf8d9fc9795bbb566a577c51a20f4cf8a68cb55a6c6d7fdd67f0829c8aecc4944c2
data/README.md CHANGED
@@ -50,7 +50,9 @@ like this style, but the default is to have a non-polluting system.
50
50
 
51
51
  Wiretaps can be composed all sorts of ways: you can filter them, map them,
52
52
  combine multiple wiretaps into a single value, use them to respond to
53
- notifications or control events.
53
+ notifications or control events. When you compose them in this way you only
54
+ need to retain the "bottom most" wiretap. Examples follow.
55
+
54
56
 
55
57
  ### Let's start with something practical!
56
58
 
@@ -60,17 +62,17 @@ In these examples I will use all three ways of creating a Wiretap
60
62
  ```ruby
61
63
  # assign the label to the text view; changes to the text view will be reflected
62
64
  # on the label.
63
- MW(@label, :text).bind_to(MW(@text_view, :text))
65
+ @wiretap = MW(@label, :text).bind_to(MW(@text_view, :text))
64
66
 
65
67
  # assign the attributedText of the label to the text view, doing some
66
68
  # highlighting in-between.
67
- @label.wiretap(:attributedText).bind_to(@text_view.wiretap(:text).map do |text|
69
+ @wiretap = @label.wiretap(:attributedText).bind_to(@text_view.wiretap(:text).map do |text|
68
70
  NSAttributedString.alloc.initWithString(text, attributes: { NSForegroundColorAttributeName => UIColor.blueColor })
69
71
  end)
70
72
 
71
73
  # This code will set the 'enabled' property depending on whether the username
72
74
  # and password are not empty.
73
- Motion.wiretap(@login_button, :enabled).bind_to(
75
+ @wiretap = Motion.wiretap(@login_button, :enabled).bind_to(
74
76
  Motion.wiretap([
75
77
  Motion.wiretap(@username_field, :text),
76
78
  Motion.wiretap(@password_field, :text),
@@ -81,6 +83,10 @@ Motion.wiretap(@login_button, :enabled).bind_to(
81
83
  )
82
84
  ```
83
85
 
86
+ See how in the example above I only retain the "final" signal (the return value
87
+ from `#combine` is what gets retained by `@wiretap`). That's what I mean by the
88
+ "bottom most" wiretap. Don't worry, the intermediate wiretaps get retained.
89
+
84
90
  ### Types of wiretaps
85
91
 
86
92
  - Key-Value Observation / KVO
@@ -102,35 +108,35 @@ end
102
108
  person = Person.new
103
109
  # you need to store the wiretap object in memory; when the object is
104
110
  # deallocated, it will no longer receive updates.
105
- wiretap = Motion.wiretap(person, :name)
111
+ @wiretap = Motion.wiretap(person, :name)
106
112
  # react to change events
107
- wiretap.listen do |name|
113
+ @wiretap.listen do |name|
108
114
  puts "name is now #{name}"
109
115
  end
110
116
  person.name = 'Joe'
111
117
  # puts => "name is now Joe"
112
118
 
113
119
  # Since listening is very common, you can easily shorthand this to:
114
- wiretap = Motion.wiretap(person, :name) do |name|
120
+ @wiretap = Motion.wiretap(person, :name) do |name|
115
121
  puts "name is now #{name}"
116
122
  end
117
123
 
118
124
  # bind the property of one object to the value of another
119
125
  person_1 = Person.new
120
126
  person_2 = Person.new
121
- wiretap = Motion.wiretap(person_1, :name)
122
- wiretap.bind_to(person_2, :name) # creates a new Wiretap object for person_2; changes to person_2.name will affect person_1
127
+ @wiretap = Motion.wiretap(person_1, :name)
128
+ @wiretap.bind_to(person_2, :name) # creates a new Wiretap object for person_2; changes to person_2.name will affect person_1
123
129
  person_2.name = 'Bob'
124
130
  person_1.name # => "Bob"
125
131
 
126
132
  # cancel a wiretap
127
- wiretap.cancel!
133
+ @wiretap.cancel!
128
134
  person_2.name = 'Jane'
129
135
  person_1.name
130
136
  # => "BOB"
131
137
 
132
138
  # bind the property of one object to the value of another, but change it using `map`
133
- wiretap = Motion.wiretap(person_1, :name).bind_to(Motion.wiretap(person_2, :name).map { |value| value.upcase })
139
+ @wiretap = Motion.wiretap(person_1, :name).bind_to(Motion.wiretap(person_2, :name).map { |value| value.upcase })
134
140
  person_2.name = 'Bob'
135
141
  person_1.name # => "BOB"
136
142
  ```
@@ -138,70 +144,82 @@ person_1.name # => "BOB"
138
144
  ### Working with arrays
139
145
 
140
146
  ```ruby
141
- # combine the values `name` and `email`
147
+ # combine the values `name` and `email`, which means they'll be sent together
148
+ # when either one changes
142
149
  person = Person.new
143
- taps = Motion.wiretap([
150
+ @info = nil
151
+ @taps = Motion.wiretap([
144
152
  Motion.wiretap(person, :name),
145
153
  Motion.wiretap(person, :email),
146
154
  ]).combine do |name, email|
147
- puts "#{name} <#{email}>"
155
+ @info = "#{name} <#{email}>"
148
156
  end
149
157
 
150
158
  person.name = 'Kazuo'
151
- # puts => "Kazuo <>"
159
+ # @info => "Kazuo <>"
152
160
  person.email = 'kaz@example.com'
153
- # puts => "Kazuo <kaz@example.com>"
161
+ # @info => "Kazuo <kaz@example.com>"
154
162
 
155
- # reduce/inject
163
+ # With reduce you get a memo and a value, and you should return a new value. Be
164
+ # careful about mutable structures: the initial memo is retained, not copied.
156
165
  person_1 = Person.new
157
166
  person_2 = Person.new
158
- taps = Motion.wiretap([
167
+ @names = []
168
+ @taps = Motion.wiretap([
159
169
  Motion.wiretap(person_1, :name),
160
170
  Motion.wiretap(person_2, :name),
161
- ]).reduce do |memo, name|
162
- memo ||= []
171
+ ]).reduce([]) do |memo, name|
163
172
  memo + [name]
164
- end
165
- wiretap.listen do |names|
166
- puts names.inspect
173
+ end.listen do |names|
174
+ @names = names.inspect
167
175
  end
168
176
  person_1.name = 'Mr. White'
169
- person_1.name = 'Mr. Blue'
170
- # => ["Mr. White", "Mr. Blue"]
171
-
172
- # you can provide an initial 'memo' (default: nil)
173
- Motion.wiretap([
174
- Motion.wiretap(person_1, :name),
175
- Motion.wiretap(person_2, :name),
176
- ]).reduce([]) do |memo, name|
177
- memo + [name] # you should not change memo in place, the same one will be used on every change event
178
- end
177
+ # @names => ["Mr. White", nil]
178
+ person_2.name = 'Mr. Blue'
179
+ # @names => ["Mr. White", "Mr. Blue"]
179
180
  ```
180
181
 
181
182
  ### Monitoring jobs
182
183
 
184
+ There is a "short form" and "long form" to these wiretaps. The "short form" is
185
+ something like `Motion.wiretap(proc) do (block) end`. The `proc` will be
186
+ executed, and when it's done, the `block` will execute.
187
+
188
+ The "long form" is different only in that the block is passed to the `and_then`
189
+ method, not the initializer: `Motion.wiretap(proc).and_then do ... end`. In
190
+ this form, you must call `start` on the wiretap:
191
+
183
192
  ```ruby
184
- # Monitor for background job completion:
185
- Motion.wiretap(-> do
193
+ # Monitor for background job completion, short form:
194
+ @wiretap = Motion.wiretap(-> do
195
+ this_will_take_forever!
196
+ end) do
197
+ puts "done!"
198
+ end
199
+
200
+ # Monitor for background job completion, long form:
201
+ @wiretap = Motion.wiretap(-> do
186
202
  this_will_take_forever!
187
203
  end).and_then do
188
204
  puts "done!"
189
- end.start
205
+ end.start # you must call 'start' explicitly
190
206
 
191
- # Same, but specifying the thread to run on. The completion block will be called
192
- # on this thread, too. The queue conveniently accepts a completion handler
193
- # (delegates to the `Wiretap#and_then` method).
194
- Motion.wiretap(-> do
207
+ # Same again, but specifying the thread to run on. The completion block will be
208
+ # called on this thread, too. The queue conveniently accepts a completion
209
+ # handler (delegates to the `Wiretap#and_then` method).
210
+ @wiretap = Motion.wiretap(-> do
195
211
  this_will_take_forever!
196
212
  end).queue(Dispatch::Queue.concurrent).and_then do
197
213
  puts "done!"
198
- end.start
214
+ end
215
+ @wiretap.start
199
216
 
200
217
  # Send a stream of values from a block. A lambda is passed in that will forward
201
218
  # change events to the `listen` block
202
- Motion.wiretap(-> (on_change) do
219
+ @wiretap = Motion.wiretap(-> (on_change) do
203
220
  5.times do |count|
204
221
  on_change.call count
222
+ sleep(1)
205
223
  end
206
224
  end).listen do |index|
207
225
  puts "...#{index}"
@@ -211,17 +229,50 @@ end.start
211
229
  # => puts "...0", "...1", "...2", "...3", "...4", "done!"
212
230
  ```
213
231
 
214
- ### Notifications
232
+ ### Gestures
233
+
234
+ Possible gestures:
235
+
236
+ :tap, :pinch, :rotate, :swipe, :pan, :press
237
+
238
+ Options:
239
+
240
+ :taps (:tap, :press)
241
+ :fingers (:tap, :swipe, :pan, :press)
242
+ :direction (:direction)
243
+ :min_fingers (:pan)
244
+ :max_fingers (:pan)
245
+ :duration (:press)
215
246
 
216
247
  ```ruby
217
- notification = Motion.wiretap('NotificationName') do
218
- puts 'notification received!'
248
+ @wiretap = Motion.wiretap(view).on(:tap) do |gesture|
249
+ point = gesture.locationInView(view)
250
+ puts "you tapped #{point.x}, #{point.y}"
219
251
  end
220
- # the notification observer will be removed when the Wiretap is dealloc'd
221
252
  ```
222
253
 
223
- ### Gestures
254
+ ### Control events
255
+
256
+ Possible events:
257
+
258
+ :touch, :touch_up, :touch_down, :touch_start, :touch_stop, :change, :begin,
259
+ :end, :touch_down_repeat, :touch_drag_inside, :touch_drag_outside,
260
+ :touch_drag_enter, :touch_drag_exit, :touch_up_inside, :touch_up_outside,
261
+ :touch_cancel, :value_changed, :editing_did_begin, :editing_changed,
262
+ :editing_did_change, :editing_did_end, :editing_did_end_on_exit
263
+ :all_touch, :all_editing, :application, :system, :all
224
264
 
225
265
  ```ruby
266
+ @wiretap = Motion.wiretap(control).on(:touch) do |event|
267
+ end
268
+ ```
226
269
 
227
- ```
270
+ ### Notifications
271
+
272
+ ```ruby
273
+ @wiretap = Motion.wiretap('NotificationName') do |object, user_info|
274
+ puts 'notification received!'
275
+ end
276
+
277
+ NSNotificationCenter.defaultCenter.postNotificationName('NotificationName', object: nil, userInfo: info)
278
+ ```
@@ -23,7 +23,7 @@ module MotionWiretap
23
23
  end
24
24
  end
25
25
 
26
- recognizer = UITapGestureRecognizer.alloc.initWithTarget(target, action: :handle_gesture)
26
+ recognizer = UITapGestureRecognizer.alloc.initWithTarget(target, action: 'handle_gesture:')
27
27
  recognizer.numberOfTapsRequired = taps if taps
28
28
  recognizer.numberOfTouchesRequired = fingers if fingers
29
29
  return recognizer
@@ -31,13 +31,13 @@ module MotionWiretap
31
31
 
32
32
  # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
33
33
  def pinch(target)
34
- recognizer = UIPinchGestureRecognizer.alloc.initWithTarget(target, action: :handle_gesture)
34
+ recognizer = UIPinchGestureRecognizer.alloc.initWithTarget(target, action: 'handle_gesture:')
35
35
  return recognizer
36
36
  end
37
37
 
38
38
  # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
39
39
  def rotate(target)
40
- recognizer = UIRotationGestureRecognizer.alloc.initWithTarget(target, action: :handle_gesture)
40
+ recognizer = UIRotationGestureRecognizer.alloc.initWithTarget(target, action: 'handle_gesture:')
41
41
  return recognizer
42
42
  end
43
43
 
@@ -71,7 +71,7 @@ module MotionWiretap
71
71
  direction = UISwipeGestureRecognizerDirectionDown
72
72
  end
73
73
 
74
- recognizer = UISwipeGestureRecognizer.alloc.initWithTarget(target, action: :handle_gesture)
74
+ recognizer = UISwipeGestureRecognizer.alloc.initWithTarget(target, action: 'handle_gesture:')
75
75
  recognizer.direction = direction if direction
76
76
  recognizer.numberOfTouchesRequired = fingers if fingers
77
77
  return recognizer
@@ -103,7 +103,7 @@ module MotionWiretap
103
103
  min_fingers ||= fingers
104
104
  max_fingers ||= fingers
105
105
 
106
- recognizer = UIPanGestureRecognizer.alloc.initWithTarget(target, action: :handle_gesture)
106
+ recognizer = UIPanGestureRecognizer.alloc.initWithTarget(target, action: 'handle_gesture:')
107
107
  recognizer.maximumNumberOfTouches = min_fingers if min_fingers
108
108
  recognizer.minimumNumberOfTouches = max_fingers if max_fingers
109
109
  return recognizer
@@ -131,7 +131,7 @@ module MotionWiretap
131
131
  end
132
132
  end
133
133
 
134
- recognizer = UILongPressGestureRecognizer.alloc.initWithTarget(target, action: :handle_gesture)
134
+ recognizer = UILongPressGestureRecognizer.alloc.initWithTarget(target, action: 'handle_gesture:')
135
135
  recognizer.minimumPressDuration = duration if duration
136
136
  recognizer.numberOfTapsRequired = taps if taps
137
137
  recognizer.numberOfTouchesRequired = fingers if fingers
@@ -4,6 +4,11 @@ module MotionWiretap
4
4
 
5
5
  class WiretapView < WiretapTarget
6
6
 
7
+ def initialize(target, &block)
8
+ super
9
+ @gesture_recognizers = []
10
+ end
11
+
7
12
  def on(recognizer, options=nil, &block)
8
13
  if recognizer
9
14
  case recognizer
@@ -24,6 +29,7 @@ module MotionWiretap
24
29
  end
25
30
 
26
31
  self.target.addGestureRecognizer(recognizer)
32
+ @gesture_recognizers << recognizer
27
33
  end
28
34
 
29
35
  listen(&block)
@@ -31,14 +37,27 @@ module MotionWiretap
31
37
  return self
32
38
  end
33
39
 
34
- def handle_gesture
35
- trigger_changed
40
+ def handle_gesture(gesture)
41
+ trigger_changed(gesture)
42
+ end
43
+
44
+ def teardown
45
+ remove_gesture = (-> (recognizer) {
46
+ self.target.removeGestureRecognizer(recognizer)
47
+ }).weak!
48
+ @gesture_recognizers.each(&remove_gesture)
49
+ super
36
50
  end
37
51
 
38
52
  end
39
53
 
40
54
  class WiretapControl < WiretapView
41
55
 
56
+ def initialize(target, &block)
57
+ super
58
+ @control_events = []
59
+ end
60
+
42
61
  # control_event can be any UIControlEventConstant, or any symbol found in
43
62
  # wiretap_control_events.rb, or an array of UIControlEventConstants or
44
63
  # symbols. Since UIView implements `on` to accept a gesture, this method
@@ -46,7 +65,8 @@ module MotionWiretap
46
65
  def on(control_event, options={}, &block)
47
66
  begin
48
67
  control_event = ControlEvents.convert(control_event)
49
- self.target.addTarget(self, action: :handle_event, forControlEvents: control_event)
68
+ self.target.addTarget(self, action: 'handle_event:', forControlEvents: control_event)
69
+ @control_events << control_event
50
70
  rescue ControlEventNotFound
51
71
  super(control_event, options, &block)
52
72
  else
@@ -56,8 +76,16 @@ module MotionWiretap
56
76
  return self
57
77
  end
58
78
 
59
- def handle_event
60
- trigger_changed
79
+ def handle_event(event)
80
+ trigger_changed(event)
81
+ end
82
+
83
+ def teardown
84
+ remove_event = (-> (event) {
85
+ self.target.removeTarget(self, action: 'handle_event:', forControlEvents: event)
86
+ }).weak!
87
+ @control_events.each(&remove_event)
88
+ super
61
89
  end
62
90
 
63
91
  end
@@ -1,3 +1,3 @@
1
1
  module MotionWiretap
2
- VERSION = '1.1.5'
2
+ VERSION = '1.1.6'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-wiretap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Colin T.A. Gray