bubble-wrap 1.8.0 → 1.9.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.
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