motion-wiretap 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +120 -50
- data/lib/motion-wiretap/all/signal.rb +12 -4
- data/lib/motion-wiretap/all/wiretap.rb +63 -84
- data/lib/motion-wiretap/ios/wiretap_factory.rb +30 -0
- data/lib/motion-wiretap/ios/wiretap_gestures.rb +6 -6
- data/lib/motion-wiretap/ios/wiretap_ios.rb +9 -5
- data/lib/motion-wiretap/osx/wiretap_factory.rb +24 -0
- data/lib/motion-wiretap/version.rb +1 -1
- data/lib/{motion-wiretap/all/wiretap_exts.rb → motion-wiretap-polluting/all/polluting_all.rb} +1 -1
- data/lib/{motion-wiretap/ios/wiretap_exts_ios.rb → motion-wiretap-polluting/ios/polluting_ios.rb} +0 -0
- data/lib/{motion-wiretap/osx/wiretap_exts_osx.rb → motion-wiretap-polluting/osx/polluting_osx.rb} +0 -0
- data/lib/motion-wiretap-polluting.rb +17 -0
- data/motion-wiretap.gemspec +2 -1
- metadata +17 -24
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 446d7bb23634641466df4e37ffa85daaac5fdc8c
|
4
|
+
data.tar.gz: f7b9033df4a62d4f235b47f4d5f39e1efa0bac11
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 17981158a67d7424e538351070ac732a7591e364c515882e176f83156f6ad850d5922bbf0754cdc1a704e089f15a15fbc676c9b58b02c381d48dc76cf8f8ed34
|
7
|
+
data.tar.gz: 196a340c6afe69833db31d76f51751ede875d75d38e6e9b294fde372031a5c2de60dc17b689174dd7aea6fe07ca8d53186b9dbf192bde535b99d36fb4893fa1d
|
data/README.md
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
MotionWiretap
|
2
|
+
-------------
|
3
3
|
|
4
|
-
An iOS / OS X
|
4
|
+
An iOS / OS X library heavily inspired by ReactiveCocoa.
|
5
5
|
|
6
6
|
gem install motion-wiretap
|
7
7
|
|
8
|
+
Run the iOS specs using `rake spec`
|
8
9
|
Run the OSX specs using `rake spec platform=osx`
|
9
10
|
|
10
11
|
First things first
|
11
12
|
------------------
|
12
13
|
|
13
|
-
You **MUST**
|
14
|
-
|
14
|
+
You **MUST** retain any wiretaps you create, otherwise they will be garbage
|
15
|
+
collected immediately. I don't yet have a clever way to avoid this unfortunate
|
16
|
+
state of affairs, other than keeping some global list of wiretaps. UG to that.
|
17
|
+
|
18
|
+
How does ReactiveCocoa do it, anyone know? I'd love to mimic that.
|
19
|
+
|
20
|
+
This isn't a terrible thing, though, since most wiretaps require that you call
|
21
|
+
the `cancel!` method. Again, I'm not sure how ReactiveCocoa accomplishes the
|
22
|
+
"auto shutdown" of signals that rely on notifications and key-value observation.
|
15
23
|
|
16
24
|
Showdown
|
17
25
|
--------
|
@@ -24,7 +32,61 @@ Open these side by side to see the comparison:
|
|
24
32
|
Usage
|
25
33
|
-----
|
26
34
|
|
27
|
-
|
35
|
+
Creating a wiretap is done using the factory method `Motion.wiretap()`.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
@wiretap = Motion.wiretap(obj, :property)
|
39
|
+
```
|
40
|
+
|
41
|
+
If you want to use a more literate style, you can include the
|
42
|
+
`motion-wiretap-polluting` to have a `wiretap` method added to your objects. *I*
|
43
|
+
like this style, but the default is to have a non-polluting system.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
@wiretap = obj.wiretap(:property)
|
47
|
+
```
|
48
|
+
|
49
|
+
Wiretaps can be composed all sorts of ways: you can filter them, map them,
|
50
|
+
combine multiple wiretaps into a single value, use them to respond to
|
51
|
+
notifications or control events.
|
52
|
+
|
53
|
+
### Let's start with something practical!
|
54
|
+
|
55
|
+
This first example will use `motion-wiretap-polluting` methods, because that's
|
56
|
+
my preferred aesthetic style, but the rest of this document will focus on the
|
57
|
+
non-polluting version.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
# assign the label to the text view; changes to the text view will be reflected
|
61
|
+
# on the label.
|
62
|
+
@label.wiretap(:text).bind_to(@text_view.wiretap(:text))
|
63
|
+
|
64
|
+
# assign the attributedText of the label to the text view, doing some
|
65
|
+
# highlighting in-between
|
66
|
+
@label.wiretap(:attributedText).bind_to(@text_view.wiretap(:text).map do |text|
|
67
|
+
NSAttributedString.alloc.initWithString(text, attributes: { NSForegroundColorAttributeName => UIColor.blueColor })
|
68
|
+
end)
|
69
|
+
|
70
|
+
# bind the `enabled` property to check on whether `username_field.text` and
|
71
|
+
# `password_field.text` are blank
|
72
|
+
@login_button.wiretap(:enabled).bind_to(
|
73
|
+
[
|
74
|
+
@username_field.wiretap(:text),
|
75
|
+
@password_field.wiretap(:text),
|
76
|
+
].wiretap.combine do |username, password|
|
77
|
+
username && username.length > 0 && password && password.length > 0
|
78
|
+
end
|
79
|
+
)
|
80
|
+
```
|
81
|
+
|
82
|
+
### Types of wiretaps
|
83
|
+
|
84
|
+
- Key-Value Observation / KVO
|
85
|
+
- Arrays (map/reduce)
|
86
|
+
- Jobs (event stream, completion)
|
87
|
+
- NSNotificationCenter
|
88
|
+
- UIView Gestures
|
89
|
+
- UIControl events
|
28
90
|
|
29
91
|
### Key-Value Observation
|
30
92
|
|
@@ -37,16 +99,26 @@ end
|
|
37
99
|
|
38
100
|
# listen for changes
|
39
101
|
person = Person.new
|
40
|
-
|
102
|
+
# you need to store the wiretap object in memory; when the object is
|
103
|
+
# deallocated, it will no longer receive updates.
|
104
|
+
wiretap = Motion.wiretap(person, :name)
|
105
|
+
# react to change events
|
106
|
+
wiretap.listen do |name|
|
41
107
|
puts "name is now #{name}"
|
42
108
|
end
|
43
109
|
person.name = 'Joe'
|
44
110
|
# puts => "name is now Joe"
|
45
111
|
|
112
|
+
# Since listening is very common, you can easily shorthand this to:
|
113
|
+
wiretap = Motion.wiretap(person, :name) do |name|
|
114
|
+
puts "name is now #{name}"
|
115
|
+
end
|
116
|
+
|
46
117
|
# bind the property of one object to the value of another
|
47
118
|
person_1 = Person.new
|
48
119
|
person_2 = Person.new
|
49
|
-
wiretap =
|
120
|
+
wiretap = Motion.wiretap(person_1, :name)
|
121
|
+
wiretap.bind_to(person_2, :name) # creates a new Wiretap object for person_2; changes to person_2.name will affect person_1
|
50
122
|
person_2.name = 'Bob'
|
51
123
|
person_1.name # => "Bob"
|
52
124
|
|
@@ -57,20 +129,20 @@ person_1.name
|
|
57
129
|
# => "BOB"
|
58
130
|
|
59
131
|
# bind the property of one object to the value of another, but change it using `map`
|
60
|
-
wiretap =
|
132
|
+
wiretap = Motion.wiretap(person_1, :name).bind_to(person_2.wiretap(:name).map { |value| value.upcase })
|
61
133
|
person_2.name = 'Bob'
|
62
134
|
person_1.name # => "BOB"
|
63
135
|
```
|
64
136
|
|
65
137
|
### Working with arrays
|
66
138
|
|
67
|
-
```
|
139
|
+
```ruby
|
68
140
|
# combine the values `name` and `email`
|
69
141
|
person = Person.new
|
70
|
-
[
|
71
|
-
|
72
|
-
|
73
|
-
].
|
142
|
+
taps = Motion.wiretap([
|
143
|
+
Motion.wiretap(person, :name),
|
144
|
+
Motion.wiretap(person, :email),
|
145
|
+
]).combine do |name, email|
|
74
146
|
puts "#{name} <#{email}>"
|
75
147
|
end
|
76
148
|
|
@@ -82,22 +154,25 @@ person.email = 'kaz@example.com'
|
|
82
154
|
# reduce/inject
|
83
155
|
person_1 = Person.new
|
84
156
|
person_2 = Person.new
|
85
|
-
[
|
86
|
-
|
87
|
-
|
88
|
-
].
|
157
|
+
taps = Motion.wiretap([
|
158
|
+
Motion.wiretap(person_1, :name),
|
159
|
+
Motion.wiretap(person_2, :name),
|
160
|
+
]).reduce do |memo, name|
|
89
161
|
memo ||= []
|
90
162
|
memo + [name]
|
91
163
|
end
|
164
|
+
wiretap.listen do |names|
|
165
|
+
puts names.inspect
|
166
|
+
end
|
92
167
|
person_1.name = 'Mr. White'
|
93
168
|
person_1.name = 'Mr. Blue'
|
94
|
-
# => [
|
169
|
+
# => ["Mr. White", "Mr. Blue"]
|
95
170
|
|
96
171
|
# you can provide an initial 'memo' (default: nil)
|
97
|
-
[
|
98
|
-
|
99
|
-
|
100
|
-
].
|
172
|
+
Motion.wiretap([
|
173
|
+
Motion.wiretap(person_1, :name),
|
174
|
+
Motion.wiretap(person_2, :name),
|
175
|
+
]).reduce([]) do |memo, name|
|
101
176
|
memo + [name] # you should not change memo in place, the same one will be used on every change event
|
102
177
|
end
|
103
178
|
```
|
@@ -105,23 +180,29 @@ end
|
|
105
180
|
### Monitoring jobs
|
106
181
|
|
107
182
|
```ruby
|
108
|
-
#
|
109
|
-
->
|
183
|
+
# Monitor for background job completion:
|
184
|
+
Motion.wiretap(-> do
|
110
185
|
this_will_take_forever!
|
111
|
-
|
186
|
+
end).and_then do
|
112
187
|
puts "done!"
|
113
|
-
end
|
188
|
+
end.start
|
114
189
|
|
115
|
-
#
|
116
|
-
|
190
|
+
# Same, but specifying the thread to run on. The completion block will be called
|
191
|
+
# on this thread, too. The queue conveniently accepts a completion handler
|
192
|
+
# (delegates to the `Wiretap#and_then` method).
|
193
|
+
Motion.wiretap(-> do
|
194
|
+
this_will_take_forever!
|
195
|
+
end).queue(Dispatch::Queue.concurrent).and_then do
|
196
|
+
puts "done!"
|
197
|
+
end.start
|
117
198
|
|
118
|
-
#
|
119
|
-
# events to the `listen` block
|
120
|
-
-> (on_change) do
|
199
|
+
# Send a stream of values from a block. A lambda is passed in that will forward
|
200
|
+
# change events to the `listen` block
|
201
|
+
Motion.wiretap(-> (on_change) do
|
121
202
|
5.times do |count|
|
122
203
|
on_change.call count
|
123
204
|
end
|
124
|
-
end
|
205
|
+
end).listen do |index|
|
125
206
|
puts "...#{index}"
|
126
207
|
end.and_then do
|
127
208
|
puts "done!"
|
@@ -129,28 +210,17 @@ end.start
|
|
129
210
|
# => puts "...0", "...1", "...2", "...3", "...4", "done!"
|
130
211
|
```
|
131
212
|
|
132
|
-
###
|
213
|
+
### Notifications
|
133
214
|
|
134
215
|
```ruby
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
@username_field.wiretap(:text),
|
140
|
-
@password_field.wiretap(:text),
|
141
|
-
].wiretap.combine do |username, password|
|
142
|
-
username && username.length > 0 && password && password.length > 0
|
143
|
-
end
|
144
|
-
)
|
216
|
+
notification = Motion.wiretap('NotificationName') do
|
217
|
+
puts 'notification received!'
|
218
|
+
end
|
219
|
+
# the notification observer will be removed when the Wiretap is dealloc'd
|
145
220
|
```
|
146
221
|
|
147
|
-
###
|
222
|
+
### Gestures
|
148
223
|
|
149
224
|
```ruby
|
150
|
-
notification = "NotificationName".wiretap do
|
151
|
-
puts "notification received!"
|
152
|
-
end
|
153
225
|
|
154
|
-
# rememder: it's *important* to cancel all wiretaps!
|
155
|
-
notification.cancel!
|
156
226
|
```
|
@@ -1,20 +1,28 @@
|
|
1
|
-
module
|
1
|
+
module MotionWiretap
|
2
2
|
|
3
3
|
# a Wiretap::Signal is much like a Promise in functional programming. A
|
4
4
|
# Signal is triggered with a new value, or it is completed, or canceled with
|
5
5
|
# an error event.
|
6
|
-
class Signal
|
6
|
+
class Signal < Wiretap
|
7
|
+
attr :value
|
7
8
|
|
8
|
-
def initialize
|
9
|
+
def initialize(value=nil)
|
10
|
+
super()
|
11
|
+
@value = value
|
12
|
+
trigger_changed(@value)
|
9
13
|
end
|
10
14
|
|
11
15
|
def next(value)
|
16
|
+
@value = value
|
17
|
+
trigger_changed(@value)
|
12
18
|
end
|
13
19
|
|
14
20
|
def complete
|
21
|
+
trigger_completed
|
15
22
|
end
|
16
23
|
|
17
|
-
def error
|
24
|
+
def error(error)
|
25
|
+
trigger_error(error)
|
18
26
|
end
|
19
27
|
|
20
28
|
end
|
@@ -1,11 +1,8 @@
|
|
1
|
-
|
2
1
|
module MotionWiretap
|
3
2
|
|
4
3
|
class Wiretap
|
5
4
|
|
6
5
|
def initialize(&block)
|
7
|
-
# MotionWiretap.wiretaps << self # signal will be removed when it is completed
|
8
|
-
|
9
6
|
@is_torn_down = false
|
10
7
|
@is_completed = false
|
11
8
|
@is_error = false
|
@@ -18,6 +15,11 @@ module MotionWiretap
|
|
18
15
|
listen &block if block
|
19
16
|
end
|
20
17
|
|
18
|
+
def dealloc
|
19
|
+
self.cancel!
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
21
23
|
# this is the preferred way to turn off a wiretap; child classes override
|
22
24
|
# `teardown`, which is only ever called once.
|
23
25
|
def cancel!
|
@@ -26,7 +28,8 @@ module MotionWiretap
|
|
26
28
|
teardown
|
27
29
|
end
|
28
30
|
|
29
|
-
#
|
31
|
+
# Overridden by subclasses to turn off observation, unregister
|
32
|
+
# notifications, etc.
|
30
33
|
def teardown
|
31
34
|
end
|
32
35
|
|
@@ -53,7 +56,7 @@ module MotionWiretap
|
|
53
56
|
def listen(wiretap=nil, &block)
|
54
57
|
raise "Block or Wiretap is expected in #{self.class.name}##{__method__}" unless block || wiretap
|
55
58
|
raise "Only Block *or* Wiretap is expected in #{self.class.name}##{__method__}" if block && wiretap
|
56
|
-
@listener_handlers << (block
|
59
|
+
@listener_handlers << (block ? block.weak! : wiretap)
|
57
60
|
self
|
58
61
|
end
|
59
62
|
|
@@ -81,11 +84,11 @@ module MotionWiretap
|
|
81
84
|
def and_then(wiretap=nil, &block)
|
82
85
|
raise "Block or Wiretap is expected in #{self.class.name}##{__method__}" unless block || wiretap
|
83
86
|
raise "Only Block *or* Wiretap is expected in #{self.class.name}##{__method__}" if block && wiretap
|
84
|
-
@completion_handlers << (block
|
87
|
+
@completion_handlers << (block ? block.weak! : wiretap)
|
85
88
|
if @is_completed
|
86
89
|
trigger_completed_on(block || wiretap)
|
87
90
|
end
|
88
|
-
self
|
91
|
+
return self
|
89
92
|
end
|
90
93
|
|
91
94
|
def trigger_completed
|
@@ -114,11 +117,11 @@ module MotionWiretap
|
|
114
117
|
def on_error(wiretap=nil, &block)
|
115
118
|
raise "Block or Wiretap is expected in #{self.class.name}##{__method__}" unless block || wiretap
|
116
119
|
raise "Only Block *or* Wiretap is expected in #{self.class.name}##{__method__}" if block && wiretap
|
117
|
-
@error_handlers << (block
|
120
|
+
@error_handlers << (block ? block.weak! : wiretap)
|
118
121
|
if @is_error
|
119
122
|
trigger_error_on(block || wiretap, @is_error)
|
120
123
|
end
|
121
|
-
self
|
124
|
+
return self
|
122
125
|
end
|
123
126
|
|
124
127
|
def trigger_error(error)
|
@@ -156,6 +159,10 @@ module MotionWiretap
|
|
156
159
|
|
157
160
|
# Returns a Wiretap that combines all the values into one value (the values
|
158
161
|
# are all passed in at the same time)
|
162
|
+
# @example
|
163
|
+
# wiretap.combine do |one, two|
|
164
|
+
# one ? two : nil
|
165
|
+
# end
|
159
166
|
def combine(&block)
|
160
167
|
return WiretapCombiner.new(self, block)
|
161
168
|
end
|
@@ -163,15 +170,19 @@ module MotionWiretap
|
|
163
170
|
# Returns a Wiretap that passes each value through the block, and also the
|
164
171
|
# previous return value (memo).
|
165
172
|
# @example
|
173
|
+
# # returns the total of all the prices
|
166
174
|
# wiretap.reduce(0) do |memo, item|
|
167
175
|
# memo + item.price
|
168
176
|
# end
|
169
|
-
# # returns the total of all the prices
|
170
177
|
def reduce(memo=nil, &block)
|
171
178
|
return WiretapReducer.new(self, memo, block)
|
172
179
|
end
|
173
180
|
|
174
181
|
# Returns a Wiretap that passes each value through the provided block
|
182
|
+
# @example
|
183
|
+
# wiretap.map do |item|
|
184
|
+
# item.to_s
|
185
|
+
# end
|
175
186
|
def map(&block)
|
176
187
|
return WiretapMapper.new(self, block)
|
177
188
|
end
|
@@ -186,11 +197,6 @@ module MotionWiretap
|
|
186
197
|
super(&block)
|
187
198
|
end
|
188
199
|
|
189
|
-
def teardown
|
190
|
-
@target = nil
|
191
|
-
super
|
192
|
-
end
|
193
|
-
|
194
200
|
end
|
195
201
|
|
196
202
|
class WiretapKvo < WiretapTarget
|
@@ -201,6 +207,7 @@ module MotionWiretap
|
|
201
207
|
@property = property
|
202
208
|
@value = nil
|
203
209
|
@initial_is_set = false
|
210
|
+
@bound_to = []
|
204
211
|
super(target, &block)
|
205
212
|
|
206
213
|
@target.addObserver(self,
|
@@ -214,8 +221,7 @@ module MotionWiretap
|
|
214
221
|
@target.removeObserver(self,
|
215
222
|
forKeyPath: @property.to_s
|
216
223
|
)
|
217
|
-
@
|
218
|
-
@value = nil
|
224
|
+
@bound_to.each &:cancel!
|
219
225
|
super
|
220
226
|
end
|
221
227
|
|
@@ -224,10 +230,13 @@ module MotionWiretap
|
|
224
230
|
end
|
225
231
|
|
226
232
|
def bind_to(wiretap)
|
233
|
+
@bound_to << wiretap
|
227
234
|
wiretap.listen do |value|
|
228
|
-
@target.send("#{@property}=", value)
|
235
|
+
@target.send("#{@property}=".to_sym, value)
|
229
236
|
end
|
230
237
|
wiretap.trigger_changed
|
238
|
+
|
239
|
+
return self
|
231
240
|
end
|
232
241
|
|
233
242
|
def observeValueForKeyPath(path, ofObject: target, change: change, context: context)
|
@@ -256,26 +265,26 @@ module MotionWiretap
|
|
256
265
|
|
257
266
|
# gets assigned to the wiretap value if it's a Wiretap, or the object
|
258
267
|
# itself if it is anything else.
|
259
|
-
@
|
268
|
+
@values = []
|
260
269
|
@initial_is_set = true
|
261
270
|
# maps the wiretap object (which is unique)
|
262
271
|
@wiretaps = {}
|
263
272
|
|
264
|
-
targets.each_with_index do |wiretap,index|
|
273
|
+
targets.each_with_index do |wiretap, index|
|
265
274
|
unless wiretap.is_a? Wiretap
|
266
|
-
@
|
275
|
+
@values << wiretap
|
267
276
|
# not a wiretap, so doesn't need to be "completed"
|
268
277
|
@uncompleted -= 1
|
269
278
|
else
|
270
279
|
raise "You cannot store a Wiretap twice in the same sequence (for now - do you really need this?)" if @wiretaps.key?(wiretap)
|
271
280
|
@wiretaps[wiretap] = index
|
272
281
|
|
273
|
-
@
|
282
|
+
@values << wiretap.value
|
274
283
|
|
275
284
|
wiretap.listen do |value|
|
276
285
|
indx = @wiretaps[wiretap]
|
277
|
-
@
|
278
|
-
trigger_changed(*@
|
286
|
+
@values[index] = wiretap.value
|
287
|
+
trigger_changed(*@values)
|
279
288
|
end
|
280
289
|
|
281
290
|
wiretap.on_error do |error|
|
@@ -292,36 +301,41 @@ module MotionWiretap
|
|
292
301
|
end
|
293
302
|
end
|
294
303
|
|
295
|
-
def cancel!
|
296
|
-
@wiretaps.each do |wiretap,index|
|
297
|
-
wiretap.cancel!
|
298
|
-
end
|
299
|
-
super
|
300
|
-
end
|
301
|
-
|
302
304
|
def teardown
|
303
|
-
|
305
|
+
cancel = (->(wiretap){ wiretap.cancel! }).weak!
|
306
|
+
@wiretaps.keys.each &cancel
|
304
307
|
super
|
305
308
|
end
|
306
309
|
|
307
310
|
def trigger_changed(*values)
|
308
|
-
values = @
|
311
|
+
values = @values if values.length == 0
|
309
312
|
super(*values)
|
310
313
|
end
|
311
314
|
|
312
315
|
end
|
313
316
|
|
314
|
-
class
|
317
|
+
class WiretapChild < Wiretap
|
315
318
|
|
316
|
-
def initialize(parent
|
319
|
+
def initialize(parent)
|
317
320
|
@parent = parent
|
318
|
-
@filter = filter
|
319
|
-
|
320
321
|
@parent.listen(self)
|
321
|
-
|
322
322
|
super()
|
323
323
|
end
|
324
324
|
|
325
|
+
def teardown
|
326
|
+
@parent.cancel!
|
327
|
+
super
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
class WiretapFilter < WiretapChild
|
333
|
+
|
334
|
+
def initialize(parent, filter)
|
335
|
+
@filter = filter
|
336
|
+
super(parent)
|
337
|
+
end
|
338
|
+
|
325
339
|
# passes the values through the filter before passing up to the parent
|
326
340
|
# implementation
|
327
341
|
def trigger_changed(*values)
|
@@ -330,22 +344,14 @@ module MotionWiretap
|
|
330
344
|
end
|
331
345
|
end
|
332
346
|
|
333
|
-
def teardown
|
334
|
-
@parent = nil
|
335
|
-
super
|
336
|
-
end
|
337
|
-
|
338
347
|
end
|
339
348
|
|
340
|
-
class WiretapCombiner <
|
349
|
+
class WiretapCombiner < WiretapChild
|
341
350
|
|
342
351
|
def initialize(parent, combiner)
|
343
|
-
@parent = parent
|
344
352
|
@combiner = combiner
|
345
353
|
|
346
|
-
|
347
|
-
|
348
|
-
super()
|
354
|
+
super(parent)
|
349
355
|
end
|
350
356
|
|
351
357
|
# passes the values through the combiner before passing up to the parent
|
@@ -354,23 +360,15 @@ module MotionWiretap
|
|
354
360
|
super(@combiner.call(*values))
|
355
361
|
end
|
356
362
|
|
357
|
-
def teardown
|
358
|
-
@parent = nil
|
359
|
-
super
|
360
|
-
end
|
361
|
-
|
362
363
|
end
|
363
364
|
|
364
|
-
class WiretapReducer <
|
365
|
+
class WiretapReducer < WiretapChild
|
365
366
|
|
366
367
|
def initialize(parent, memo, reducer)
|
367
|
-
@parent = parent
|
368
368
|
@reducer = reducer
|
369
369
|
@memo = memo
|
370
370
|
|
371
|
-
|
372
|
-
|
373
|
-
super()
|
371
|
+
super(parent)
|
374
372
|
end
|
375
373
|
|
376
374
|
# passes each value through the @reducer, passing in the return value of the
|
@@ -379,22 +377,14 @@ module MotionWiretap
|
|
379
377
|
super(values.inject(@memo, &@reducer))
|
380
378
|
end
|
381
379
|
|
382
|
-
def teardown
|
383
|
-
@parent = nil
|
384
|
-
super
|
385
|
-
end
|
386
|
-
|
387
380
|
end
|
388
381
|
|
389
|
-
class WiretapMapper <
|
382
|
+
class WiretapMapper < WiretapChild
|
390
383
|
|
391
384
|
def initialize(parent, mapper)
|
392
|
-
@parent = parent
|
393
385
|
@mapper = mapper
|
394
386
|
|
395
|
-
|
396
|
-
|
397
|
-
super()
|
387
|
+
super(parent)
|
398
388
|
end
|
399
389
|
|
400
390
|
# passes the values through the mapper before passing up to the parent
|
@@ -403,22 +393,17 @@ module MotionWiretap
|
|
403
393
|
super(*values.map { |value| @mapper.call(value) })
|
404
394
|
end
|
405
395
|
|
406
|
-
def teardown
|
407
|
-
@parent = nil
|
408
|
-
super
|
409
|
-
end
|
410
|
-
|
411
396
|
end
|
412
397
|
|
413
398
|
class WiretapProc < WiretapTarget
|
414
399
|
|
415
|
-
def initialize(target, queue,
|
400
|
+
def initialize(target, queue, and_then)
|
416
401
|
@started = false
|
417
402
|
super(target)
|
418
|
-
and_then(&
|
403
|
+
and_then(&and_then) if and_then
|
419
404
|
queue(queue) if queue
|
420
405
|
|
421
|
-
start if
|
406
|
+
start if and_then
|
422
407
|
end
|
423
408
|
|
424
409
|
def start
|
@@ -448,7 +433,7 @@ module MotionWiretap
|
|
448
433
|
@notification = notification
|
449
434
|
@object = object
|
450
435
|
|
451
|
-
NSNotificationCenter.defaultCenter.addObserver(self, selector: 'notify:', name
|
436
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector: 'notify:', name: @notification, object: @object)
|
452
437
|
listen(&block) if block
|
453
438
|
end
|
454
439
|
|
@@ -457,16 +442,10 @@ module MotionWiretap
|
|
457
442
|
end
|
458
443
|
|
459
444
|
def teardown
|
460
|
-
NSNotificationCenter.defaultCenter.removeObserver(self, name
|
445
|
+
NSNotificationCenter.defaultCenter.removeObserver(self, name: @notification, object: @object)
|
461
446
|
super
|
462
447
|
end
|
463
448
|
|
464
449
|
end
|
465
450
|
|
466
|
-
module_function
|
467
|
-
|
468
|
-
def wiretaps
|
469
|
-
@wiretaps ||= []
|
470
|
-
end
|
471
|
-
|
472
451
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Motion
|
2
|
+
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def wiretap(target, property=nil, &block)
|
6
|
+
case target
|
7
|
+
when NSString
|
8
|
+
MotionWiretap::WiretapNotification.new(target, property, block)
|
9
|
+
when Proc
|
10
|
+
MotionWiretap::WiretapProc.new(target, property, block)
|
11
|
+
when NSArray
|
12
|
+
MotionWiretap::WiretapArray.new(target, &block)
|
13
|
+
when UIControl
|
14
|
+
if property.nil?
|
15
|
+
MotionWiretap::WiretapControl.new(target, &block)
|
16
|
+
else
|
17
|
+
MotionWiretap::WiretapKvo.new(target, property, &block)
|
18
|
+
end
|
19
|
+
when UIView
|
20
|
+
if property.nil?
|
21
|
+
MotionWiretap::WiretapView.new(target, &block)
|
22
|
+
else
|
23
|
+
MotionWiretap::WiretapKvo.new(target, property, &block)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
MotionWiretap::WiretapKvo.new(target, property, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -23,7 +23,7 @@ module MotionWiretap
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
recognizer = UITapGestureRecognizer.alloc.initWithTarget(target, action:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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
|
@@ -2,7 +2,11 @@ module MotionWiretap
|
|
2
2
|
class GestureNotFound < Exception
|
3
3
|
end
|
4
4
|
|
5
|
-
class WiretapView <
|
5
|
+
class WiretapView < WiretapTarget
|
6
|
+
|
7
|
+
def dummy
|
8
|
+
UIButton.new.enabled = true
|
9
|
+
end
|
6
10
|
|
7
11
|
def on(recognizer, options=nil, &block)
|
8
12
|
if recognizer
|
@@ -26,12 +30,12 @@ module MotionWiretap
|
|
26
30
|
self.target.addGestureRecognizer(recognizer)
|
27
31
|
end
|
28
32
|
|
29
|
-
|
33
|
+
listen(&block)
|
30
34
|
|
31
35
|
return self
|
32
36
|
end
|
33
37
|
|
34
|
-
def handle_gesture
|
38
|
+
def handle_gesture
|
35
39
|
trigger_changed
|
36
40
|
end
|
37
41
|
|
@@ -46,7 +50,7 @@ module MotionWiretap
|
|
46
50
|
def on(control_event, options={}, &block)
|
47
51
|
begin
|
48
52
|
control_event = ControlEvents.convert(control_event)
|
49
|
-
self.target.addTarget(self, action:
|
53
|
+
self.target.addTarget(self, action: :handle_event, forControlEvents: control_event)
|
50
54
|
rescue ControlEventNotFound
|
51
55
|
super(control_event, options, &block)
|
52
56
|
else
|
@@ -56,7 +60,7 @@ module MotionWiretap
|
|
56
60
|
return self
|
57
61
|
end
|
58
62
|
|
59
|
-
def handle_event
|
63
|
+
def handle_event
|
60
64
|
trigger_changed
|
61
65
|
end
|
62
66
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Motion
|
2
|
+
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def wiretap(target, property=nil, &block)
|
6
|
+
case target
|
7
|
+
when NSString
|
8
|
+
MotionWiretap::WiretapNotification.new(target, property, block)
|
9
|
+
when Proc
|
10
|
+
MotionWiretap::WiretapProc.new(target, property, block)
|
11
|
+
when NSArray
|
12
|
+
MotionWiretap::WiretapArray.new(target, &block)
|
13
|
+
when NSView
|
14
|
+
if property.nil?
|
15
|
+
MotionWiretap::WiretapView.new(target, &block)
|
16
|
+
else
|
17
|
+
MotionWiretap::WiretapKvo.new(target, property, &block)
|
18
|
+
end
|
19
|
+
when NSObject
|
20
|
+
MotionWiretap::WiretapKvo.new(target, property, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/{motion-wiretap/ios/wiretap_exts_ios.rb → motion-wiretap-polluting/ios/polluting_ios.rb}
RENAMED
File without changes
|
data/lib/{motion-wiretap/osx/wiretap_exts_osx.rb → motion-wiretap-polluting/osx/polluting_osx.rb}
RENAMED
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "The motion-wiretap gem must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
|
6
|
+
require 'motion-wiretap'
|
7
|
+
|
8
|
+
|
9
|
+
Motion::Project::App.setup do |app|
|
10
|
+
# scans app.files until it finds app/ (the default)
|
11
|
+
# if found, it inserts just before those files, otherwise it will insert to
|
12
|
+
# the end of the list
|
13
|
+
insert_point = app.files.find_index { |file| file =~ /^(?:\.\/)?app\// } || 0
|
14
|
+
|
15
|
+
app.files.insert(insert_point, *Dir.glob(File.join(File.dirname(__FILE__), "motion-wiretap-polluting/#{app.template.to_s}/*.rb")))
|
16
|
+
app.files.insert(insert_point, *Dir.glob(File.join(File.dirname(__FILE__), "motion-wiretap-polluting/all/*.rb")))
|
17
|
+
end
|
data/motion-wiretap.gemspec
CHANGED
@@ -3,7 +3,8 @@ require File.expand_path('../lib/motion-wiretap/version.rb', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.name = 'motion-wiretap'
|
6
|
-
gem.version = MotionWiretap::
|
6
|
+
gem.version = MotionWiretap::VERSION
|
7
|
+
gem.licenses = ['BSD']
|
7
8
|
|
8
9
|
gem.authors = ['Colin T.A. Gray']
|
9
10
|
gem.email = ['colinta@gmail.com']
|
metadata
CHANGED
@@ -1,36 +1,26 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion-wiretap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Colin T.A. Gray
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
|
-
description:
|
15
|
-
the
|
16
|
-
|
13
|
+
description: |
|
14
|
+
ReactiveCocoa is an amazing system, and RubyMotion could benefit from the
|
17
15
|
lessons learned there!
|
18
16
|
|
19
|
-
|
20
17
|
Motion-Wiretap is, essentially, a wrapper for Key-Value coding and observation.
|
21
|
-
|
22
18
|
It exposes a +Wiretap+ class that you can use as a signal, or add listeners to
|
23
|
-
|
24
19
|
it.
|
25
20
|
|
26
|
-
|
27
21
|
Extensions are provided to listen to an +Array+ of +Wiretap+ objects, and the
|
28
|
-
|
29
22
|
`UIKit`/`AppKit` classes are augmented to provide actions as events (gestures,
|
30
|
-
|
31
23
|
mouse events, value changes).
|
32
|
-
|
33
|
-
'
|
34
24
|
email:
|
35
25
|
- colinta@gmail.com
|
36
26
|
executables: []
|
@@ -39,39 +29,42 @@ extra_rdoc_files: []
|
|
39
29
|
files:
|
40
30
|
- lib/motion-wiretap/all/signal.rb
|
41
31
|
- lib/motion-wiretap/all/wiretap.rb
|
42
|
-
- lib/motion-wiretap/all/wiretap_exts.rb
|
43
32
|
- lib/motion-wiretap/ios/wiretap_control_events.rb
|
44
|
-
- lib/motion-wiretap/ios/
|
33
|
+
- lib/motion-wiretap/ios/wiretap_factory.rb
|
45
34
|
- lib/motion-wiretap/ios/wiretap_gestures.rb
|
46
35
|
- lib/motion-wiretap/ios/wiretap_ios.rb
|
47
|
-
- lib/motion-wiretap/osx/
|
36
|
+
- lib/motion-wiretap/osx/wiretap_factory.rb
|
48
37
|
- lib/motion-wiretap/osx/wiretap_osx.rb
|
49
38
|
- lib/motion-wiretap/version.rb
|
39
|
+
- lib/motion-wiretap-polluting/all/polluting_all.rb
|
40
|
+
- lib/motion-wiretap-polluting/ios/polluting_ios.rb
|
41
|
+
- lib/motion-wiretap-polluting/osx/polluting_osx.rb
|
42
|
+
- lib/motion-wiretap-polluting.rb
|
50
43
|
- lib/motion-wiretap.rb
|
51
44
|
- README.md
|
52
45
|
- motion-wiretap.gemspec
|
53
46
|
homepage: https://github.com/colinta/motion-wiretap
|
54
|
-
licenses:
|
47
|
+
licenses:
|
48
|
+
- BSD
|
49
|
+
metadata: {}
|
55
50
|
post_install_message:
|
56
51
|
rdoc_options: []
|
57
52
|
require_paths:
|
58
53
|
- lib
|
59
54
|
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
55
|
requirements:
|
62
|
-
- -
|
56
|
+
- - '>='
|
63
57
|
- !ruby/object:Gem::Version
|
64
58
|
version: '0'
|
65
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
60
|
requirements:
|
68
|
-
- -
|
61
|
+
- - '>='
|
69
62
|
- !ruby/object:Gem::Version
|
70
63
|
version: '0'
|
71
64
|
requirements: []
|
72
65
|
rubyforge_project:
|
73
|
-
rubygems_version:
|
66
|
+
rubygems_version: 2.0.3
|
74
67
|
signing_key:
|
75
|
-
specification_version:
|
68
|
+
specification_version: 4
|
76
69
|
summary: It's like ReactiveCocoa, but in RubyMotion
|
77
70
|
test_files: []
|