motion-wiretap 0.2.1 → 1.0.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 +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: []
|