README.md in bubble-wrap-1.7.1 vs README.md in bubble-wrap-1.8.0

- old
+ new

@@ -3,11 +3,11 @@ A collection of (tested) helpers and wrappers used to wrap Cocoa Touch and AppKit code and provide more Ruby like APIs. [BubbleWrap website](http://rubymotion.github.io/BubbleWrap/) [BubbleWrap mailing list](https://groups.google.com/forum/#!forum/bubblewrap) -[![Code Climate](https://codeclimate.com/github/rubymotion/BubbleWrap.png)](https://codeclimate.com/github/rubymotion/BubbleWrap) +[![Code Climate](https://codeclimate.com/github/rubymotion/BubbleWrap.svg)](https://codeclimate.com/github/rubymotion/BubbleWrap) [![Build Status](https://travis-ci.org/rubymotion/BubbleWrap.svg?branch=master)](https://travis-ci.org/rubymotion/BubbleWrap) [![Dependency Status](https://gemnasium.com/rubymotion/BubbleWrap.png)](https://gemnasium.com/rubymotion/BubbleWrap) ## Installation @@ -24,29 +24,15 @@ ``` If you using Bundler: ```ruby -gem "bubble-wrap", "~> 1.7.0" +gem "bubble-wrap", "~> 1.8.0" ``` -BubbleWrap is split into multiple modules so that you can easily choose which parts -are included at compile-time. +BubbleWrap is split into multiple modules so that you can easily choose which parts are included at compile-time. -The above example requires the `core` and `http` modules. If you wish to only -include the core modules use the following line of code instead: - -```ruby -require 'bubble-wrap/core' -``` - -If you wish to only include the `HTTP` wrapper: - -```ruby -require 'bubble-wrap/http' -``` - If you wish to only include the `RSS Parser` wrapper: ```ruby require 'bubble-wrap/rss_parser' ``` @@ -103,11 +89,16 @@ ```ruby require 'bubble-wrap/all' ``` +You can also do this directly in your `Gemfile` like so: +```ruby +gem 'bubble-wrap', require: %w[bubble-wrap/core bubble-wrap/location, bubble-wrap/reactor] +``` + Note: **DON'T** use `app.files =` in your Rakefile to set up your files once you've required BubbleWrap. Make sure to append onto the array or use `+=`. 2. Now, you can use BubbleWrap extension in your app: @@ -244,10 +235,12 @@ # 480 > Device.screen.width_for_orientation(:landscape_left) # 480 > Device.screen.height_for_orientation(:landscape_left) # 320 +> Device.vendor_identifier +# <NSUUID> ``` ### Camera Added interface for better camera access: @@ -271,26 +264,32 @@ # Lets the user edit the photo (with access to the edited and original photos) BW::Device.camera.any.picture(allows_editing: true, media_types: [:image]) do |result| edited_image_view = UIImageView.alloc.initWithImage(result[:edited_image]) original_image_view = UIImageView.alloc.initWithImage(result[:original_image]) end + +# Capture a low quality movie with a limit of 10 seconds +BW::Device.camera.front.picture(media_types: [:movie], video_quality: :low, video_maximum_duration: 10) do |result| + video_file_path = result[:media_url] +end ``` - Options include: - `:allows_editing` - Boolean; whether a user can edit the photo/video before picking - `:animated` - Boolean; whether to display the camera with an animation (default true) - `:on_dismiss` - Lambda; called instead of the default dismissal logic - `:media_types` - Array; containing any of `[:movie, :image]` +- `:video_quality` - Symbol; one of `:high`, `:medium`, `low`, `"640x480".to_sym`, `iframe1280x720`, or `iframe960x540`. Defaults to `:medium` +- `:video_maximum_duration` - Integer; limits movie recording length. Defaults to 600. ### JSON `BW::JSON` wraps `NSJSONSerialization` available in iOS5 and offers the same API as Ruby's JSON std lib. For apps building for iOS4, we suggest a different JSON alternative, like [AnyJSON](https://github.com/mattt/AnyJSON). ```ruby -BW::JSON.generate({'foo => 1, 'bar' => [1,2,3], 'baz => 'awesome'}) +BW::JSON.generate({'foo' => 1, 'bar' => [1,2,3], 'baz' => 'awesome'}) => "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}" BW::JSON.parse "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}" => {"foo"=>1, "bar"=>[1, 2, 3], "baz"=>"awesome"} ``` @@ -357,13 +356,13 @@ ``` ruby class ExampleViewController < UIViewController include BW::KVO def viewDidLoad - @label = UILabel.alloc.initWithFrame [[20,20],[280,44]] - @label.text = "" - view.addSubview @label + @label = UILabel.alloc.initWithFrame [[20,20],[280,44]] + @label.text = "" + view.addSubview @label observe(@label, :text) do |old_value, new_value| puts "Hello from viewDidLoad!" end end @@ -400,11 +399,11 @@ => 2012-05-31 21:41:33 +0200 ``` ## Location -Added interface for Ruby-like GPS and compass access: +Interface for Ruby-like GPS and compass access (the CoreLocation framework): ```ruby > BW::Location.enabled? # Whether location services are enabled on the device => true > BW::Location.authorized? # If your app is authorized to use location services @@ -447,10 +446,181 @@ p result[:accuracy] p result[:timestamp] end ``` +### iOS 8 Location Requirements + +iOS 8 introduced stricter location services requirements. Although BubbleWrap will handle most of this for you automatically, you are required to add a few key/value pairs to the `Info.plist`. Add these two lines to your `Rakefile` (with your descriptions, obviously): + +```ruby +app.info_plist['NSLocationAlwaysUsageDescription'] = 'Description' +app.info_plist['NSLocationWhenInUseUsageDescription'] = 'Description' +``` + +*Note: you need both keys to use `get_once`, so it's probably best to just include both no matter what.* See [Apple's documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW18) on iOS 8 location services requirements for more information. + +## Motion + +Interface for the accelerometer, gyroscope, and magnetometer sensors (the +CoreMotion framework). You can access each sensor individually, or you can get +data from all of them at once using the `BW::Motion.device` interface, which +delegates to `CMMotionManager#deviceMotion`. + +Each sensor has an `every` and `once` method. `every` expects a time interval, +and you will need to retain the object it returns and call `#stop` on it when +you are done with the data. + +The `every` and `once` methods can accept a `:queue` option. The default value +is a queue that runs on the main loop, so that UI updates can be processed in +the block. This is useful, but not recommended by Apple, since the events can +come in at a high rate. If you want to use a background queue, you can either +specify an NSOperationQueue object, or you can use one of these symbols: + +- `:main` - `NSOperationQueue.mainQueue`, this is the default value. +- `:background` - BubbleWrap will create a new `NSOperationQueue`. +- `:current` - BubbleWrap will use `NSOperationQueue.currentQueue`. + +If you pass a string instead, a new queue will be created and its `name` +property will be set to that string. + +The `CMDeviceMotion` interface (`BW::Motion.device`) accepts a `:reference` +option, which specifies the `CMAttitudeReferenceFrame`. The default value is +the same as the one that `CMMotionManager` uses, which is returned by the +`CMMotionManager#attitudeReferenceFrame` method. This option should be passed +to the `repeat`, `every` or `once` methods. + +###### Accelerometer +```ruby +BW::Motion.accelerometer.available? +BW::Motion.accelerometer.data # returns CMAccelerometerData object or nil + +# ask the CMMotionManager to update every 5 seconds +BW::Motion.accelerometer.every(5) do |result| + # result contains the following data (from CMAccelerometerData#acceleration): + p result[:data] # the CMAccelerometerData object + p result[:acceleration] # the CMAcceleration struct + p result[:x] # acceleration in the x direction + p result[:y] # " y direction + p result[:z] # " z direction +end + +# every, start, and repeat all need to be stopped later. +BW::Motion.accelerometer.stop + +# repeat, but don't set the interval +BW::Motion.accelerometer.repeat do |result| +end + +# you can specify a :queue where the operations will be executed. See above for details +BW::Motion.accelerometer.every(5, queue: :background) { |result| ... } +BW::Motion.accelerometer.every(5, queue: :main) { |result| ... } +BW::Motion.accelerometer.every(5, queue: :current) { |result| ... } +BW::Motion.accelerometer.every(5, queue: 'my queue') { |result| ... } + +BW::Motion.accelerometer.once do |result| + # ... +end +``` + +###### Gyroscope +```ruby +BW::Motion.gyroscope.available? +BW::Motion.gyroscope.data # returns CMGyroData object or nil + +# ask the CMMotionManager to update every second. +BW::Motion.gyroscope.every(1) do |result| + # result contains the following data (from CMGyroData#rotationRate): + p result[:data] # the CMGyroData object + p result[:rotation] # the CMRotationRate struct + p result[:x] # rotation in the x direction + p result[:y] # " y direction + p result[:z] # " z direction +end +BW::Motion.gyroscope.stop + +BW::Motion.gyroscope.once do |result| + # ... +end +``` + +###### Magnetometer +```ruby +BW::Motion.magnetometer.available? +BW::Motion.magnetometer.data # returns CMMagnetometerData object or nil + +# ask the CMMotionManager to update every second +BW::Motion.magnetometer.every(1) do |result| + # result contains the following data (from CMMagnetometerData#magneticField): + p result[:data] # the CMMagnetometerData object + p result[:field] # the CMMagneticField struct + p result[:x] # magnetic field in the x direction + p result[:y] # " y direction + p result[:z] # " z direction +end +BW::Motion.magnetometer.stop + +BW::Motion.magnetometer.once do |result| + # ... +end +``` + +###### Device Motion + +This is an amalgam of all the motion sensor data. + +```ruby +BW::Motion.device.available? +BW::Motion.device.data # returns CMDeviceMotion object or nil + +BW::Motion.device.every(1) do |result| + # result contains the following data: + p result[:data] # the CMDeviceMotion object + # orientation data, from CMDeviceMotion#attitude + p result[:attitude] # the CMAttitude struct + p result[:roll] + p result[:pitch] + p result[:yaw] + # rotation data, from CMDeviceMotion#rotationRate + p result[:rotation] # the CMRotationRate struct + p result[:rotation_x] + p result[:rotation_y] + p result[:rotation_z] + # gravity+acceleration vector, from CMDeviceMotion#gravity + p result[:gravity] # the CMAcceleration struct + p result[:gravity_x] + p result[:gravity_y] + p result[:gravity_z] + # just the acceleration vector, from CMDeviceMotion#userAcceleration + p result[:acceleration] # the CMAcceleration struct + p result[:acceleration_x] + p result[:acceleration_y] + p result[:acceleration_z] + # the magnetic data, from CMDeviceMotion#magneticField + p result[:magnetic] # the CMCalibratedMagneticField struct + p result[:magnetic_field] # the CMMagneticField struct from the CMCalibratedMagneticField + p result[:magnetic_x] + p result[:magnetic_y] + p result[:magnetic_z] + p result[:magnetic_accuracy] # this will be a symbol, :low, :medium, :high, or nil if the magnetic data is uncalibrated + + # less useful data from CMAttitude, unless you're into the whole linear algebra thing: + p result[:matrix] # CMAttitude#rotationMatrix + p result[:quarternion] # CMAttitude#quarternion +end + +# the reference frame should be one of the CMAttitudeReferenceFrame constants... +ref = CMAttitudeReferenceFrameXArbitraryZVertical +# ... or one of these symbols: :arbitrary_z, :corrected_z, :magnetic_north, :true_north +ref = :corrected_z +BW::Motion.device.every(1, queue: :background, reference: ref) { |result| ... } + +BW::Motion.device.once do |result| + # ... +end +``` + ## Media Added wrapper for playing remote and local media. Available are `modal` and custom presentation styles: ```ruby @@ -467,10 +637,12 @@ ## Mail Wrapper for showing an in-app mail composer view. +You should always determine if the device your app is running on is configured to send mail before displaying a mail composer window. `BW::Mail.can_send_mail?` will return `true` or `false`. + ```ruby # Opens as a modal in the current UIViewController BW::Mail.compose( delegate: self, # optional, defaults to rootViewController to: [ "tom@example.com" ], @@ -491,10 +663,12 @@ ## SMS Wrapper for showing an in-app message (SMS) composer view. +You should always determine if the device your app is running on can send SMS messages before displaying a SMS composer window. `BW::SMS.can_send_sms?` will return `true` or `false`. + ```ruby # Opens as a modal in the current UIViewController BW::SMS.compose ( { delegate: self, # optional, will use root view controller by default @@ -554,10 +728,16 @@ end ``` There are similar methods for `pinched`, `rotated`, `swiped`, `panned`, and `pressed` (for long presses). All of the methods return the actual recognizer object, so it is possible to set the delegate if more fine-grained control is needed. +In order to prevent retain cycles due to strong references within the passed block, use the use_weak_callbacks flag so the blocks do not retain a strong reference to self: + +```ruby +BubbleWrap.use_weak_callbacks = true +``` + ### UIViewController A custom method was added to `UIViewController` to return the content frame of a view controller. @@ -569,10 +749,30 @@ button.when(UIControlEventTouchUpInside) do self.view.backgroundColor = UIColor.redColor end ``` +The `#when` method also accepts bitwise combinations of events: + +```ruby +button.when(UIControlEventTouchUpInside | UIControlEventTouchUpOutside) do + self.view.backgroundColor = UIColor.redColor +end +``` + +You can use symbols for events (but won't work with the bitwise operator): + +```ruby +button.when(:touch_up_inside) do + self.view.backgroundColor = UIColor.redColor +end + +button.when(:value_changed) do + self.view.backgroundColor = UIColor.blueColor +end +``` + Set the use_weak_callbacks flag so the blocks do not retain a strong reference to self: ```ruby BubbleWrap.use_weak_callbacks = true ``` @@ -727,109 +927,10 @@ :post_to_vimeo :post_to_tencent_weibo :air_drop ``` - -## HTTP - -`BW::HTTP` wraps `NSURLRequest`, `NSURLConnection` and friends to provide Ruby developers with a more familiar and easier to use API. -The API uses async calls and blocks to stay as simple as possible. - -To enable it add the following require line to your `Rakefile`: -```ruby -require 'bubble-wrap/http' -``` - -Usage example: - -```ruby -BW::HTTP.get("https://api.github.com/users/mattetti") do |response| - p response.body.to_str -end -``` - -```ruby -BW::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response| - p response.body.to_str # prints the response's body -end -``` - -```ruby -data = {first_name: 'Matt', last_name: 'Aimonetti'} -BW::HTTP.post("http://foo.bar.com/", {payload: data}) do |response| - if response.ok? - json = BW::JSON.parse(response.body.to_str) - p json['id'] - elsif response.status_code.to_s =~ /40\d/ - App.alert("Login failed") - else - App.alert(response.error_message) - end -end -``` - -To upload files to a server, provide a `files:` hash: - -```ruby -data = {token: "some-api-token"} -avatar_data = UIImagePNGRepresentation(UIImage.imageNamed("some-image")) -avatar = { data: avatar_data, filename: "some-image.png", content_type: "image/png" } - -BW::HTTP.post("http://foo.bar.com/", {payload: data}, files: { avatar: avatar }) do |response| - if response.ok? - # files are uploaded - end -end -``` - -A `:download_progress` option can also be passed. The expected object -would be a Proc that takes two arguments: a float representing the -amount of data currently received and another float representing the -total amount of data expected. - -Connections can also be cancelled. Just keep a refrence, - -```ruby -@conn = BW::HTTP.get("https://api.github.com/users/mattetti") do |response| - p response.body.to_str -end -``` - -and send the `cancel` method to it asynchronously as desired. The block will not be executed. - -```ruby -@conn.cancel -``` - -### Gotchas - -Because of how RubyMotion currently works, you sometimes need to assign objects as `@instance_variables` in order to retain their callbacks. - -For example: - -```ruby -class HttpClient - def get_user(user_id, &callback) - BW::HTTP.get(user_url(user_id)) do |response| - # .. - end - end -end -``` - -This class should be invoked in your code as: - -```ruby -@http_client = HttpClient.new -@http_client.get_user(user_id) do |user| - # .. -end -``` - -(instead of doing an instance-variable-less `HttpClient.new.get_user`) - ## RSS Parser **Since: > version 1.0.0** The RSS Parser provides an easy interface to consume RSS feeds in an asynchronous (non blocking) way. @@ -926,10 +1027,18 @@ A deferrable is an object with four states: unknown, successful, failure and timeout. When you initially create a deferrable it is in an unknown state, however you can assign callbacks to be run when the object changes to either successful or failure state. +Using `delegate`, `errback_delegate` and `callback_delegate` you can link +deferrables together. + +By default, callbacks will be made on the thread that the deferrable +succeeds/fails on. For multithreaded environments, it can be useful to use +EM::ThreadAwareDeferrable so that callbacks will be made on the threads they +are declared on. + #### Success ```ruby > d = EM::DefaultDeferrable.new => #<BW::Reactor::DefaultDeferrable:0x6d859a0> @@ -949,11 +1058,42 @@ => [#<Proc:0x8bf3ef0>] > d.fail "sadness" Great sadness! => nil ``` +#### Delegate +```ruby +> d = EM::DefaultDeferrable.new +=> #<BW::Reactor::DefaultDeferrable:0x8bf3ee0> +> delegate = EM::DefaultDeferrable.new +=> #<BW::Reactor::DefaultDeferrable:0x8bf5910> +> d.delegate delegate +=> #<BW::Reactor::DefaultDeferrable:0x8bf3ee0> +> delegate.callback { |*args| puts args } +=> [#<Proc:0x8bf3ef0>] +> d.succeed :passed +=> nil +=> [:passed] +``` + +#### ThreadAwareDeferrable + +```ruby +> d = EM::ThreadAwareDeferrable.new +=> #<BW::Reactor::ThreadAwareDeferrable:0x8bf3ee0> + +> queue = Dispatch::Queue.new(:deferrable.to_s) +> queue.async do +> d.callback do |*args| +> Dispatch::Queue.current == queue +> => true # this is normally false +> end +> end +> d.succeed true +``` + #### Timeout ```ruby > d = EM::DefaultDeferrable.new => #<BW::Reactor::DefaultDeferrable:0x8bf5910> @@ -1062,9 +1202,13 @@ > o.off(:november_5_1955, &flux) => #<Proc:0xab630f0> > o.trigger(:november_5_1955) Ow! => [nil] +> o.on(:november_5_1955) { puts "Ow!" } +> o.on(:november_5_1955) { puts "Another Ow!" } +> o.off(:november_5_1955) +=> nil ``` # Suggestions? Do you have a suggestion for a specific wrapper? Feel free to open an