bubble-wrap 1.8.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +14 -9
  3. data/CHANGELOG.md +21 -2
  4. data/Gemfile.lock +8 -5
  5. data/README.md +54 -3
  6. data/Rakefile +7 -1
  7. data/lib/bubble-wrap/camera.rb +1 -1
  8. data/lib/bubble-wrap/core.rb +1 -1
  9. data/lib/bubble-wrap/font.rb +1 -1
  10. data/lib/bubble-wrap/loader.rb +4 -4
  11. data/lib/bubble-wrap/media.rb +1 -1
  12. data/lib/bubble-wrap/motion.rb +7 -1
  13. data/lib/bubble-wrap/version.rb +2 -2
  14. data/motion/core/app.rb +4 -0
  15. data/motion/core/device/ios/camera_wrapper.rb +1 -1
  16. data/motion/core/device/ios/screen.rb +2 -2
  17. data/motion/core/device/osx/screen.rb +1 -1
  18. data/motion/core/device/screen.rb +1 -1
  19. data/motion/core/ios/device.rb +1 -1
  20. data/motion/core/ios/ns_index_path.rb +11 -0
  21. data/motion/core/json.rb +2 -2
  22. data/motion/core/kvo.rb +118 -55
  23. data/motion/core/ns_notification_center.rb +2 -2
  24. data/motion/core/ns_url_request.rb +2 -2
  25. data/motion/core/osx/device.rb +1 -1
  26. data/motion/core/string.rb +7 -7
  27. data/motion/font/font.rb +1 -1
  28. data/motion/ios/8/location_constants.rb +21 -0
  29. data/motion/location/location.rb +38 -18
  30. data/motion/mail/result.rb +1 -1
  31. data/motion/media/media.rb +1 -1
  32. data/motion/motion/accelerometer.rb +55 -0
  33. data/motion/motion/device_motion.rb +139 -0
  34. data/motion/motion/gyroscope.rb +55 -0
  35. data/motion/motion/magnetometer.rb +55 -0
  36. data/motion/motion/motion.rb +0 -288
  37. data/motion/reactor.rb +3 -3
  38. data/motion/reactor/deferrable.rb +32 -32
  39. data/motion/reactor/periodic_timer.rb +1 -1
  40. data/motion/reactor/queue.rb +6 -6
  41. data/motion/reactor/thread_aware_deferrable.rb +1 -1
  42. data/motion/reactor/timer.rb +1 -1
  43. data/motion/rss_parser.rb +6 -3
  44. data/motion/shortcut.rb +1 -1
  45. data/motion/sms/result.rb +1 -1
  46. data/motion/test_suite_delegate.rb +1 -1
  47. data/motion/ui/ui_activity_view_controller_wrapper.rb +6 -1
  48. data/motion/ui/ui_alert_view.rb +21 -1
  49. data/motion/util/deprecated.rb +1 -1
  50. data/samples/alert/Gemfile.lock +1 -1
  51. data/spec/lib/bubble-wrap/requirement_spec.rb +2 -2
  52. data/spec/lib/bubble-wrap_spec.rb +1 -1
  53. data/spec/lib/motion_stub.rb +1 -1
  54. data/spec/motion/core/app_spec.rb +6 -0
  55. data/spec/motion/core/device/osx/screen_spec.rb +1 -1
  56. data/spec/motion/core/ios/ns_index_path_spec.rb +20 -0
  57. data/spec/motion/core/kvo_spec.rb +171 -58
  58. data/spec/motion/core/ns_notification_center_spec.rb +3 -3
  59. data/spec/motion/core/string_spec.rb +16 -16
  60. data/spec/motion/core_spec.rb +3 -3
  61. data/spec/motion/font/font_spec.rb +1 -1
  62. data/spec/motion/location/location_spec.rb +61 -9
  63. data/spec/motion/mail/result_spec.rb +7 -7
  64. data/spec/motion/media/player_spec.rb +1 -1
  65. data/spec/motion/reactor/thread_aware_deferrable_spec.rb +3 -3
  66. data/spec/motion/sms/result_spec.rb +6 -6
  67. data/spec/motion/ui/ui_alert_view_spec.rb +59 -1
  68. data/spec/motion/util/deprecated_spec.rb +1 -1
  69. metadata +17 -4
  70. data/motion/ios/7/uiactivity_view_controller_constants.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf1fb0a71b37c7e7473219036a4d8304dd7c9fd1
4
- data.tar.gz: 1f57a00000623dac2a425cceb6ce5f74ae40eb3e
3
+ metadata.gz: 77584cd0711e17af5282a0e9f40306916177b6d5
4
+ data.tar.gz: a12694fd9f1a6e370a9d91f3ed15f3ce74af6469
5
5
  SHA512:
6
- metadata.gz: e259382ebbc9970d3b264f9a95961807944bb17da2aec89f6af873d4b267e1319d2ac1dfc86cd0a2ef08d205cf9719160a10d7db5bf32c51c8926fdc2f332ec2
7
- data.tar.gz: edced4a22d3af5a7f067a08a84176206aa039632adf97b46411582a33f0cd8a6b256f730aecbf73c3a639a51d404d563d163d76973680fecb38b73d661d28c01
6
+ metadata.gz: e0fa5d162a946c67f83a77b56b84426f42c62770a10a027523cb2ab0e5c0958e9f657ebfb5e5fda060174aaacdc2faaf22e9f6f5b984b8db73a6bf96866495e2
7
+ data.tar.gz: 18fbc4e01a2091678107341a57d1067d2ec3341dadec26c180baf863cdfb1d034ff69046e04ecd6c5242937ae5ee410b9cf80270d55a30bd8af996be921881ea
data/.travis.yml CHANGED
@@ -1,12 +1,17 @@
1
1
  language: objective-c
2
2
  before_install:
3
- - (ruby --version)
4
- - sudo chown -R travis ~/Library/RubyMotion
5
- - mkdir -p ~/Library/RubyMotion/build
6
- - sudo motion update
3
+ - (ruby --version)
4
+ - sudo chown -R travis ~/Library/RubyMotion
5
+ - sudo mkdir -p ~/Library/RubyMotion/build
6
+ - sudo chown -R travis ~/Library/RubyMotion/build
7
+ - sudo motion update
8
+ gemfile:
9
+ - Gemfile
7
10
  script:
8
- - bundle install
9
- - bundle exec rake clean
10
- - bundle exec rake spec
11
- - bundle exec rake clean
12
- - bundle exec rake spec osx=true
11
+ - bundle install --jobs=3 --retry=3
12
+ - bundle exec rake clean
13
+ - bundle exec rake spec
14
+ - bundle exec rake clean
15
+ - bundle exec rake spec target=7.1
16
+ - bundle exec rake clean
17
+ - bundle exec rake spec osx=true
data/CHANGELOG.md CHANGED
@@ -1,10 +1,29 @@
1
1
  ## Unreleased
2
2
 
3
- [Commit history](https://github.com/rubymotion/BubbleWrap/compare/v1.4.0...master)
3
+ [Commit history](https://github.com/rubymotion/BubbleWrap/compare/v1.9.0...master)
4
+
5
+ ## 1.9.0
6
+
7
+ [Commit history](https://github.com/rubymotion/BubbleWrap/compare/v1.8.0...v1.9.0)
8
+
9
+ * Add support for `keyboardType` on first input field of `BW::UIAlertView` ([#406](https://github.com/rubymotion/BubbleWrap/pull/406))
10
+ * Implement #+ and #- for NSIndexPath to incr/decr row ([#420](https://github.com/rubymotion/BubbleWrap/pull/420))
11
+ * Extract CoreMotion classes to make it easier to use and more maintainable ([#454](https://github.com/rubymotion/BubbleWrap/pull/454))
12
+ * Motion extract classes ([#454](https://github.com/rubymotion/BubbleWrap/pull/454))
13
+ * Added RSS fields: creator, category, encoded ([#461](https://github.com/rubymotion/BubbleWrap/pull/461))
14
+ * KVO `observe!` and ability to pass multiple key paths to `observe` ([#460](https://github.com/rubymotion/BubbleWrap/pull/460))
15
+ * `App.short_version` ([#466](https://github.com/rubymotion/BubbleWrap/pull/466))
16
+ * Bump the required version of iOS to >= 7 ([#424](https://github.com/rubymotion/BubbleWrap/pull/424))
17
+ * Bump the required version of iOS to >= 7 ([#424](https://github.com/rubymotion/BubbleWrap/pull/424))
18
+ * Fixes to CoreLocation ([#422](https://github.com/rubymotion/BubbleWrap/pull/422)) & ([#432](https://github.com/rubymotion/BubbleWrap/pull/432))
19
+ * Adds default text option to BW::UIAlertView ([#467](https://github.com/rubymotion/BubbleWrap/pull/467))
20
+ * Bump the minimum required RubyMotion version to `3.12`.
21
+
22
+ ## ... nothing to see here... move along.
4
23
 
5
24
  ## 1.4.0
6
25
 
7
- [Commit history](https://github.com/rubymotion/BubbleWrap/compare/v1.3.0...master)
26
+ [Commit history](https://github.com/rubymotion/BubbleWrap/compare/v1.3.0...v1.4.0)
8
27
 
9
28
  * Added `BW::Mail` for sending emails ([#247](https://github.com/rubymotion/BubbleWrap/pull/247))
10
29
  * Added `BW::SMS` for sending SMS/iMessages ([#287](https://github.com/rubymotion/BubbleWrap/pull/287))
data/Gemfile.lock CHANGED
@@ -1,18 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bubble-wrap (1.8.0)
4
+ bubble-wrap (1.9.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  bacon (1.2.0)
10
10
  metaclass (0.0.4)
11
- mocha (0.11.4)
11
+ mocha (0.14.0)
12
12
  metaclass (~> 0.0.1)
13
- mocha-on-bacon (0.2.1)
14
- mocha (>= 0.9.8)
15
- rake (10.3.2)
13
+ mocha-on-bacon (0.2.2)
14
+ mocha (>= 0.13.0)
15
+ rake (10.4.2)
16
16
  webstub (1.1.2)
17
17
 
18
18
  PLATFORMS
@@ -25,3 +25,6 @@ DEPENDENCIES
25
25
  mocha-on-bacon (~> 0.2)
26
26
  rake (~> 10.0)
27
27
  webstub (~> 1.1)
28
+
29
+ BUNDLED WITH
30
+ 1.10.4
data/README.md CHANGED
@@ -7,6 +7,7 @@ A collection of (tested) helpers and wrappers used to wrap Cocoa Touch and AppKi
7
7
 
8
8
  [![Code Climate](https://codeclimate.com/github/rubymotion/BubbleWrap.svg)](https://codeclimate.com/github/rubymotion/BubbleWrap)
9
9
  [![Build Status](https://travis-ci.org/rubymotion/BubbleWrap.svg?branch=master)](https://travis-ci.org/rubymotion/BubbleWrap)
10
+ [![Gem Version](https://badge.fury.io/rb/bubble-wrap.png)](http://badge.fury.io/rb/bubble-wrap)
10
11
  [![Dependency Status](https://gemnasium.com/rubymotion/BubbleWrap.png)](https://gemnasium.com/rubymotion/BubbleWrap)
11
12
 
12
13
  ## Installation
@@ -26,7 +27,7 @@ require 'bubble-wrap'
26
27
  If you using Bundler:
27
28
 
28
29
  ```ruby
29
- gem "bubble-wrap", "~> 1.8.0"
30
+ gem "bubble-wrap", "~> 1.9.0"
30
31
  ```
31
32
 
32
33
  BubbleWrap is split into multiple modules so that you can easily choose which parts are included at compile-time.
@@ -79,6 +80,12 @@ If you wish to only include the `SMS` wrapper:
79
80
  require 'bubble-wrap/sms'
80
81
  ```
81
82
 
83
+ If you wish to only include the `Motion` (CoreMotion) wrapper:
84
+
85
+ ```ruby
86
+ require 'bubble-wrap/motion'
87
+ ```
88
+
82
89
  If you wish to only include the `NetworkIndicator` wrapper:
83
90
 
84
91
  ```ruby
@@ -298,6 +305,11 @@ BW::JSON.parse "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}"
298
305
  Helper methods added to give `NSIndexPath` a bit more of a Ruby
299
306
  interface.
300
307
 
308
+ ```ruby
309
+ index_path = table_view.indexPathForCell(cell)
310
+ index_path + 1 # NSIndexPath for next cell in the same section
311
+ => #<NSIndexPath:0x120db8e0>
312
+ ```
301
313
 
302
314
  ### NSNotificationCenter
303
315
 
@@ -376,6 +388,32 @@ class ExampleViewController < UIViewController
376
388
  end
377
389
  ```
378
390
 
391
+ You can remove observers using `unobserve` method.
392
+
393
+ **Since: > version 1.9.0**
394
+
395
+ Optionally, multiple key paths can be passed to the `observer` method:
396
+
397
+ ``` ruby
398
+ class ExampleViewController < UIViewController
399
+ include BW::KVO
400
+
401
+ def viewDidLoad
402
+ @label = UILabel.alloc.initWithFrame [[20,20],[280,44]]
403
+ @label.text = ""
404
+ view.addSubview @label
405
+
406
+ observe(@label, [:text, :textColor]) do |old_value, new_value, key_path|
407
+ puts "Hello from viewDidLoad for #{key_path}!"
408
+ end
409
+ end
410
+ end
411
+ ```
412
+
413
+ Also you can use `observe!` method to register observer that will immediately
414
+ return initial value. Note that in this case only new value will be passed to
415
+ the block.
416
+
379
417
 
380
418
  ### String
381
419
 
@@ -416,6 +454,10 @@ BW::Location.get(purpose: 'We need to use your GPS because...') do |result|
416
454
  p "To Lat #{result[:to].latitude}, Long #{result[:to].longitude}"
417
455
  end
418
456
  ```
457
+ *Note: `result[:from]` will return `nil` the first time location services are started.*
458
+
459
+ The `:previous` key in the `BW::Location.get()` result hash will always return an array of zero or more additional `CLLocation` objects aside from the locations returned from the `:to` and `:from` hash keys. While in most scenarios this array will be empty, per [Apple's Documentation](https://developer.apple.com/library/IOs/documentation/CoreLocation/Reference/CLLocationManagerDelegate_Protocol/index.html#//apple_ref/occ/intfm/CLLocationManagerDelegate/locationManager:didUpdateLocations:) if there are deferred updates or multiple locations that arrived before they could be delivered, multiple locations will be returned in an order of oldest to newest.
460
+
419
461
 
420
462
  ```ruby
421
463
  BW::Location.get_compass do |result|
@@ -1210,9 +1252,18 @@ Ow!
1210
1252
  => nil
1211
1253
  ```
1212
1254
 
1213
- # Suggestions?
1255
+ # Contributing
1214
1256
 
1215
1257
  Do you have a suggestion for a specific wrapper? Feel free to open an
1216
1258
  issue/ticket and tell us about what you are after. If you have a
1217
1259
  wrapper/helper you are using and are thinking that others might enjoy,
1218
- please send a pull request (with tests if possible).
1260
+ please send a pull request with tests. If you need help writing the tests,
1261
+ send the pull request anyways and we'll try to help you out with that.
1262
+
1263
+ 1. Create an issue in GitHub to make sure your PR will be accepted
1264
+ 2. Fork the BubbleWrap repository
1265
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
1266
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
1267
+ 5. Write tests for your changes and ensure they pass locally (`bundle exec rake spec && bundle exec rake spec osx=true`)
1268
+ 6. Push to the branch (`git push origin my-new-feature`)
1269
+ 7. Create new Pull Request
data/Rakefile CHANGED
@@ -32,9 +32,15 @@ Motion::Project::App.setup do |app|
32
32
  app.spec_files -= Dir.glob("./spec/motion/#{package}/**/*.rb")
33
33
  end
34
34
  else
35
+ app.deployment_target = '7.1'
36
+ app.info_plist['NSLocationAlwaysUsageDescription'] = 'Description'
37
+ app.info_plist['NSLocationWhenInUseUsageDescription'] = 'Description'
38
+
35
39
  app.spec_files -= Dir.glob("./spec/motion/**/osx/**.rb")
36
40
  end
37
- app.version = '1.2.3'
41
+
42
+ app.version = '1.2.3'
43
+ app.short_version = '3.2.1'
38
44
  end
39
45
 
40
46
  namespace :spec do
@@ -9,4 +9,4 @@ BubbleWrap.require_ios("camera") do
9
9
  file('motion/core/device/ios/camera.rb').depends_on ['motion/core/ios/device.rb', 'motion/util/constants.rb']
10
10
  file('motion/core/device/ios/screen.rb').depends_on 'motion/core/ios/device.rb'
11
11
  end
12
- end
12
+ end
@@ -10,7 +10,7 @@ BubbleWrap.require('motion/core/device/*.rb')
10
10
  BubbleWrap.require_ios do
11
11
  BubbleWrap.require('motion/core/ios/**/*.rb')
12
12
  BubbleWrap.require('motion/core/device/ios/**/*.rb')
13
-
13
+
14
14
  require 'bubble-wrap/camera'
15
15
  require 'bubble-wrap/ui'
16
16
  end
@@ -1,4 +1,4 @@
1
1
  require 'bubble-wrap/loader'
2
2
  BubbleWrap.require_ios("font") do
3
3
  BubbleWrap.require('motion/font/font.rb')
4
- end
4
+ end
@@ -48,13 +48,13 @@ unless defined?(BubbleWrap::LOADER_PRESENT)
48
48
 
49
49
  def after_config(config)
50
50
  BubbleWrap.require_ios do
51
- ios7_files = 'motion/ios/7/uiactivity_view_controller_constants.rb'
52
- if config.send(:deployment_target).to_f >= 7.0
53
- ::BubbleWrap.require(ios7_files)
51
+ ios8_files = 'motion/ios/8/location_constants.rb'
52
+ if config.send(:deployment_target).to_f >= 8.0
53
+ ::BubbleWrap.require(ios8_files)
54
54
  before_config(config)
55
55
  else
56
56
  config.files = config.files.reject {|s|
57
- s.include?(ios7_files)
57
+ s.include?(ios8_files)
58
58
  }
59
59
  end
60
60
  end
@@ -11,4 +11,4 @@ BubbleWrap.require_ios("media") do
11
11
  file('motion/media/player.rb').depends_on 'motion/core/string.rb'
12
12
  file('motion/media/player.rb').uses_framework('MediaPlayer')
13
13
  end
14
- end
14
+ end
@@ -5,6 +5,12 @@ BubbleWrap.require_ios('motion') do
5
5
  BubbleWrap.require('motion/core/string.rb')
6
6
  BubbleWrap.require('motion/motion/**/*.rb') do
7
7
  file('motion/motion/motion.rb').depends_on 'motion/util/constants.rb'
8
+
9
+ file('motion/motion/accelerometer.rb').depends_on('motion/motion/motion.rb')
10
+ file('motion/motion/device_motion.rb').depends_on('motion/motion/motion.rb')
11
+ file('motion/motion/gyroscope.rb').depends_on('motion/motion/motion.rb')
12
+ file('motion/motion/magnetometer.rb').depends_on('motion/motion/motion.rb')
13
+
8
14
  file('motion/motion/motion.rb').uses_framework('CoreMotion')
9
15
  end
10
- end
16
+ end
@@ -1,4 +1,4 @@
1
1
  module BubbleWrap
2
- VERSION = '1.8.0' unless defined?(BubbleWrap::VERSION)
3
- MIN_MOTION_VERSION = '2.17'
2
+ VERSION = '1.9.0' unless defined?(BubbleWrap::VERSION)
3
+ MIN_MOTION_VERSION = '3.12'
4
4
  end
data/motion/core/app.rb CHANGED
@@ -62,6 +62,10 @@ module BubbleWrap
62
62
  info_plist['CFBundleVersion']
63
63
  end
64
64
 
65
+ def short_version
66
+ info_plist['CFBundleShortVersionString']
67
+ end
68
+
65
69
  # @return [NSLocale] locale of user settings
66
70
  def current_locale
67
71
  languages = NSLocale.preferredLanguages
@@ -52,4 +52,4 @@ module BubbleWrap
52
52
  end
53
53
  end
54
54
  end
55
- end
55
+ end
@@ -78,7 +78,7 @@ module BubbleWrap
78
78
  # compensating for screen rotation (which
79
79
  # can do your head in).
80
80
  def width_for_orientation(o=orientation)
81
- return height if (o == :landscape_left) || (o == :landscape_right)
81
+ return height if (o == :landscape_left) || (o == :landscape_right)
82
82
  width
83
83
  end
84
84
 
@@ -86,7 +86,7 @@ module BubbleWrap
86
86
  # compensating for screen rotation (which
87
87
  # can do your head in).
88
88
  def height_for_orientation(o=orientation)
89
- return width if (o == :landscape_left) || (o == :landscape_right)
89
+ return width if (o == :landscape_left) || (o == :landscape_right)
90
90
  height
91
91
  end
92
92
  end
@@ -15,4 +15,4 @@ module BubbleWrap
15
15
  end
16
16
  end
17
17
  end
18
- end
18
+ end
@@ -4,4 +4,4 @@ module BubbleWrap
4
4
 
5
5
  end
6
6
  end
7
- end
7
+ end
@@ -13,7 +13,7 @@ module BubbleWrap
13
13
  def ipad?(idiom=UIDevice.currentDevice.userInterfaceIdiom)
14
14
  idiom == UIUserInterfaceIdiomPad
15
15
  end
16
-
16
+
17
17
  # Verifies that the device having a long screen (4 inch iPhone/iPod)
18
18
  # @return [TrueClass, FalseClass] true will be returned if the device is an iPhone/iPod with 4 inche screen, false otherwise.
19
19
  def long_screen?(idiom=UIDevice.currentDevice.userInterfaceIdiom, screen_height=UIScreen.mainScreen.bounds.size.height)
@@ -0,0 +1,11 @@
1
+ module NSIndexPathWrap
2
+
3
+ def +(aNumber)
4
+ self.class.indexPathForRow(row+aNumber, inSection:section)
5
+ end
6
+
7
+ def -(aNumber)
8
+ self.class.indexPathForRow(row-aNumber, inSection:section)
9
+ end
10
+
11
+ end
data/motion/core/json.rb CHANGED
@@ -21,10 +21,10 @@ module BubbleWrap
21
21
  raise ParserError, error[0].description if error[0]
22
22
  if block_given?
23
23
  yield obj
24
- else
24
+ else
25
25
  obj
26
26
  end
27
-
27
+
28
28
  end
29
29
 
30
30
  def self.generate(obj)
data/motion/core/kvo.rb CHANGED
@@ -18,93 +18,156 @@
18
18
  # @see https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i
19
19
  module BubbleWrap
20
20
  module KVO
21
- COLLECTION_OPERATIONS = [ NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement ]
22
- DEFAULT_OPTIONS = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
21
+ class Registry
22
+ COLLECTION_OPERATIONS = [ NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement ]
23
+ OPTION_MAP = {
24
+ new: NSKeyValueChangeNewKey,
25
+ old: NSKeyValueChangeOldKey
26
+ }
27
+
28
+ attr_reader :callbacks, :keys
29
+
30
+ def initialize(value_keys = [:old, :new])
31
+ @keys = value_keys.inject([]) do |acc, key|
32
+ value_change_key = OPTION_MAP[key]
33
+
34
+ if value_change_key.nil?
35
+ raise RuntimeError, "Unknown value change key #{key}. Possible keys: #{OPTION_MAP.keys}"
36
+ end
37
+
38
+ acc << value_change_key
39
+ end
23
40
 
24
- def observe(*arguments, &block)
25
- unless [1,2].include?(arguments.length)
26
- raise ArgumentError, "wrong number of arguments (#{arguments.length} for 1 or 2)"
41
+ @callbacks = Hash.new do |hash, key|
42
+ hash[key] = Hash.new do |subhash, subkey|
43
+ subhash[subkey] = Array.new
44
+ end
45
+ end
27
46
  end
28
47
 
29
- key_path = arguments.pop
30
- target = arguments.pop || self
48
+ def add(target, key_path, &block)
49
+ return if target.nil? || key_path.nil? || block.nil?
31
50
 
32
- target.addObserver(self, forKeyPath:key_path, options:DEFAULT_OPTIONS, context:nil) unless registered?(target, key_path)
33
- add_observer_block(target, key_path, &block)
34
- end
51
+ block.weak! if BubbleWrap.use_weak_callbacks?
35
52
 
36
- def unobserve(*arguments)
37
- unless [1,2].include?(arguments.length)
38
- raise ArgumentError, "wrong number of arguments (#{arguments.length} for 1 or 2)"
53
+ callbacks[target][key_path.to_s] << block
39
54
  end
40
55
 
41
- key_path = arguments.pop
42
- target = arguments.pop || self
56
+ def remove(target, key_path)
57
+ return if target.nil? || key_path.nil?
43
58
 
44
- return unless registered?(target, key_path)
59
+ key_path = key_path.to_s
45
60
 
46
- target.removeObserver(self, forKeyPath:key_path)
47
- remove_observer_block(target, key_path)
48
- end
61
+ callbacks[target].delete(key_path)
49
62
 
50
- def unobserve_all
51
- return if @targets.nil?
63
+ # If there no key_paths left for target, remove the target key
64
+ if callbacks[target].empty?
65
+ callbacks.delete(target)
66
+ end
67
+ end
68
+
69
+ def registered?(target, key_path)
70
+ callbacks[target].has_key? key_path.to_s
71
+ end
72
+
73
+ def remove_all
74
+ callbacks.clear
75
+ end
52
76
 
53
- @targets.each do |target, key_paths|
54
- key_paths.each_key do |key_path|
55
- target.removeObserver(self, forKeyPath:key_path)
77
+ def each_key_path
78
+ callbacks.each do |target, key_paths|
79
+ key_paths.each_key do |key_path|
80
+ yield target, key_path
81
+ end
56
82
  end
57
83
  end
58
- remove_all_observer_blocks
59
- end
60
84
 
61
- # Observer blocks
85
+ private
62
86
 
63
- private
64
- def registered?(target, key_path)
65
- !@targets.nil? && !@targets[target].nil? && @targets[target].has_key?(key_path.to_s)
87
+ def observeValueForKeyPath(key_path, ofObject: target, change: change, context: context)
88
+ key_paths = callbacks[target] || {}
89
+ blocks = key_paths[key_path] || []
90
+
91
+ args = change.values_at(*keys)
92
+ args << key_path
93
+
94
+ blocks.each do |block|
95
+ block.call(*args)
96
+ end
97
+ end
66
98
  end
67
99
 
68
- def add_observer_block(target, key_path, &block)
69
- return if target.nil? || key_path.nil? || block.nil?
100
+ DEFAULT_OPTIONS = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
101
+ IMMIDEATE_OPTIONS = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
70
102
 
71
- block.weak! if BubbleWrap.use_weak_callbacks?
103
+ def observe(target = self, key_paths, &block)
104
+ key_paths = [key_paths].flatten
72
105
 
73
- @targets ||= {}
74
- @targets[target] ||= {}
75
- @targets[target][key_path.to_s] ||= []
76
- @targets[target][key_path.to_s] << block
106
+ key_paths.each do |key_path|
107
+ if not observers_registry.registered?(target, key_path)
108
+ target.addObserver(observers_registry, forKeyPath:key_path, options:DEFAULT_OPTIONS, context:nil)
109
+ end
110
+
111
+ # Add block even if observer is registed, so multiplie blocks can be invoked.
112
+ observers_registry.add(target, key_path, &block)
113
+ end
77
114
  end
78
115
 
79
- def remove_observer_block(target, key_path)
80
- return if @targets.nil? || target.nil? || key_path.nil?
116
+ def observe!(target = self, key_paths, &block)
117
+ key_paths = [key_paths].flatten
81
118
 
82
- key_paths = @targets[target]
83
- key_paths.delete(key_path.to_s) if !key_paths.nil?
84
- if key_paths.nil? || key_paths.length == 0
85
- @targets.delete(target)
119
+ key_paths.each do |key_path|
120
+ registered = immediate_observers_registry.registered?(target, key_path)
121
+
122
+ immediate_observers_registry.add(target, key_path, &block)
123
+
124
+ # We need to first register the block, and then call addObserver, because
125
+ # observeValueForKeyPath will fire immedeately.
126
+ if not registered
127
+ target.addObserver(immediate_observers_registry, forKeyPath:key_path, options: IMMIDEATE_OPTIONS, context:nil)
128
+ end
86
129
  end
87
130
  end
88
131
 
89
- def remove_all_observer_blocks
90
- @targets.clear unless @targets.nil?
132
+ def unobserve(target = self, key_paths)
133
+ remove_from_registry_if_exists(target, key_paths, observers_registry)
134
+ end
135
+
136
+ def unobserve!(target = self, key_paths)
137
+ remove_from_registry_if_exists(target, key_paths, immediate_observers_registry)
91
138
  end
92
139
 
93
- # NSKeyValueObserving Protocol
140
+ def unobserve_all
141
+ observers_registry.each_key_path do |target, key_path|
142
+ target.removeObserver(observers_registry, forKeyPath:key_path)
143
+ end
94
144
 
95
- def observeValueForKeyPath(key_path, ofObject: target, change: change, context: context)
96
- key_paths = @targets[target] || {}
97
- blocks = key_paths[key_path] || []
98
- blocks.each do |block|
99
- args = [change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]]
100
- args << change[NSKeyValueChangeIndexesKey] if collection?(change)
101
- block.call(*args)
145
+ immediate_observers_registry.each_key_path do |target, key_path|
146
+ target.removeObserver(immediate_observers_registry, forKeyPath:key_path)
102
147
  end
148
+
149
+ observers_registry.remove_all
150
+ immediate_observers_registry.remove_all
151
+ end
152
+
153
+ private
154
+ def observers_registry
155
+ @observers_registry ||= Registry.new
103
156
  end
104
157
 
105
- def collection?(change)
106
- COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
158
+ def immediate_observers_registry
159
+ @immediate_observers_registry ||= Registry.new([:new])
107
160
  end
108
161
 
162
+ def remove_from_registry_if_exists(target, key_paths, registry)
163
+ key_paths = [key_paths].flatten
164
+
165
+ key_paths.each do |key_path|
166
+ if registry.registered?(target, key_path)
167
+ target.removeObserver(registry, forKeyPath:key_path)
168
+ registry.remove(target, key_path)
169
+ end
170
+ end
171
+ end
109
172
  end
110
173
  end