motion-wiretap 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
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