├── resources └── test.png ├── railsuser3_avatar.png ├── motionuser12_avatar.png ├── railsuser3_avatar@2x.png ├── Gemfile ├── motionuser12_avatar@2x.png ├── swiftlytalking4_avatar.png ├── swiftlytalking4_avatar@2x.png ├── examples ├── example_api │ ├── Gemfile │ ├── public │ │ ├── railsuser3_avatar.png │ │ ├── motionuser12_avatar.png │ │ ├── motionuser12_avatar@2x.png │ │ ├── railsuser3_avatar@2x.png │ │ ├── swiftlytalking4_avatar.png │ │ └── swiftlytalking4_avatar@2x.png │ ├── Gemfile.lock │ └── example_web_app.rb ├── AppDotNet │ ├── resources │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── 20x20@2x.png │ │ │ │ ├── 20x20@3x.png │ │ │ │ ├── 29x29@2x.png │ │ │ │ ├── 29x29@3x.png │ │ │ │ ├── 40x40@2x.png │ │ │ │ ├── 40x40@3x.png │ │ │ │ ├── 60x60@2x.png │ │ │ │ ├── 60x60@3x.png │ │ │ │ ├── 1024x1024.png │ │ │ │ ├── 20x20~ipad.png │ │ │ │ ├── 29x29~ipad.png │ │ │ │ ├── 40x40~ipad.png │ │ │ │ ├── 76x76~ipad.png │ │ │ │ ├── 20x20~ipad@2x.png │ │ │ │ ├── 29x29~ipad@2x.png │ │ │ │ ├── 40x40~ipad@2x.png │ │ │ │ ├── 76x76~ipad@2x.png │ │ │ │ ├── 83.5x83.5~ipad@2x.png │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage │ │ │ │ ├── Default.png │ │ │ │ ├── Default-ipad.png │ │ │ │ ├── Default-2436h@3x.png │ │ │ │ ├── Default-568h@2x.png │ │ │ │ ├── Default-667h@2x.png │ │ │ │ ├── Default-736h@3x.png │ │ │ │ ├── Default-ipad@2x.png │ │ │ │ ├── Default-375w-667h@2x.png │ │ │ │ ├── Default-414w-736h@3x.png │ │ │ │ └── Contents.json │ │ ├── feed.png │ │ ├── upload.png │ │ ├── feed@2x.png │ │ ├── feed@3x.png │ │ ├── upload@2x.png │ │ ├── upload@3x.png │ │ ├── sample_upload.png │ │ ├── profile-image-placeholder.png │ │ └── profile-image-placeholder@2x.png │ ├── spec │ │ └── main_spec.rb │ ├── app │ │ ├── user.rb │ │ ├── post.rb │ │ ├── app_delegate.rb │ │ ├── post_table_view_cell.rb │ │ ├── global_timeline_view_controller.rb │ │ └── upload_view_controller.rb │ ├── .gitignore │ ├── vendor │ │ └── Podfile.lock │ ├── readme.md │ └── Rakefile └── EXAMPLES.md ├── lib ├── afmotion │ ├── version.rb │ ├── ext │ │ ├── NSString_NSUrl.rb │ │ ├── UIImageView_url.rb │ │ └── AFHTTPSessionManager.rb │ ├── operation.rb │ ├── http_result.rb │ ├── serializer.rb │ ├── session_client.rb │ ├── http.rb │ ├── client_shared.rb │ └── session_client_dsl.rb └── afmotion.rb ├── .gitignore ├── app └── app_delegate.rb ├── Rakefile ├── .travis.yml ├── vendor └── Podfile.lock ├── AFMotion.gemspec ├── LICENSE ├── spec ├── http_spec.rb ├── integration_spec.rb └── session_client_spec.rb └── README.md /resources/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/resources/test.png -------------------------------------------------------------------------------- /railsuser3_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/railsuser3_avatar.png -------------------------------------------------------------------------------- /motionuser12_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/motionuser12_avatar.png -------------------------------------------------------------------------------- /railsuser3_avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/railsuser3_avatar@2x.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in motion-settings.gemspec 4 | gemspec -------------------------------------------------------------------------------- /motionuser12_avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/motionuser12_avatar@2x.png -------------------------------------------------------------------------------- /swiftlytalking4_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/swiftlytalking4_avatar.png -------------------------------------------------------------------------------- /swiftlytalking4_avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/swiftlytalking4_avatar@2x.png -------------------------------------------------------------------------------- /examples/example_api/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "sinatra" 6 | -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /examples/AppDotNet/resources/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/feed.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/upload.png -------------------------------------------------------------------------------- /lib/afmotion/version.rb: -------------------------------------------------------------------------------- 1 | module AFMotion 2 | VERSION = "3.0" 3 | 4 | HTTP_METHODS = [:get, :post, :put, :delete, :patch, :head] 5 | end 6 | -------------------------------------------------------------------------------- /examples/AppDotNet/resources/feed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/feed@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/feed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/feed@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/upload@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/upload@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/upload@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/upload@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/sample_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/sample_upload.png -------------------------------------------------------------------------------- /examples/example_api/public/railsuser3_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/example_api/public/railsuser3_avatar.png -------------------------------------------------------------------------------- /examples/example_api/public/motionuser12_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/example_api/public/motionuser12_avatar.png -------------------------------------------------------------------------------- /examples/example_api/public/motionuser12_avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/example_api/public/motionuser12_avatar@2x.png -------------------------------------------------------------------------------- /examples/example_api/public/railsuser3_avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/example_api/public/railsuser3_avatar@2x.png -------------------------------------------------------------------------------- /examples/example_api/public/swiftlytalking4_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/example_api/public/swiftlytalking4_avatar.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/profile-image-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/profile-image-placeholder.png -------------------------------------------------------------------------------- /examples/example_api/public/swiftlytalking4_avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/example_api/public/swiftlytalking4_avatar@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/profile-image-placeholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/profile-image-placeholder@2x.png -------------------------------------------------------------------------------- /examples/EXAMPLES.md: -------------------------------------------------------------------------------- 1 | ### To run the example, first run the example rails app 2 | 3 | ``` 4 | cd examples/example_api 5 | bundle install 6 | bundle exec ruby example_web_app.rb 7 | ``` 8 | -------------------------------------------------------------------------------- /lib/afmotion/ext/NSString_NSUrl.rb: -------------------------------------------------------------------------------- 1 | class NSString 2 | def to_url 3 | NSURL.URLWithString(self) 4 | end 5 | end 6 | 7 | class NSURL 8 | def to_url 9 | self 10 | end 11 | end -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/60x60@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/60x60@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20~ipad.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29~ipad.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40~ipad.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/76x76~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/76x76~ipad.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/20x20~ipad@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/29x29~ipad@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/40x40~ipad@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/76x76~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/76x76~ipad@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/83.5x83.5~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/83.5x83.5~ipad@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-ipad.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-2436h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-2436h@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-ipad@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-375w-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-375w-667h@2x.png -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-414w-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubymotion-community/afmotion/HEAD/examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Default-414w-736h@3x.png -------------------------------------------------------------------------------- /examples/AppDotNet/spec/main_spec.rb: -------------------------------------------------------------------------------- 1 | describe "Application 'AppDotNet'" do 2 | before do 3 | @app = UIApplication.sharedApplication 4 | end 5 | 6 | it "has one window" do 7 | @app.windows.size.should == 1 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .repl_history 2 | build 3 | .build 4 | libPods.a 5 | vendor/Pods/ 6 | tags 7 | resources/*.nib 8 | resources/*.momd 9 | resources/*.storyboardc 10 | .DS_Store 11 | nbproject 12 | .redcar 13 | #*# 14 | *~ 15 | *.sw[po] 16 | .eprj 17 | pkg/* 18 | Gemfile.lock 19 | -------------------------------------------------------------------------------- /app/app_delegate.rb: -------------------------------------------------------------------------------- 1 | class AppDelegate 2 | attr_accessor :image, :data 3 | def application(application, didFinishLaunchingWithOptions:launchOptions) 4 | self.image = UIImage.imageNamed("test") 5 | self.data = UIImagePNGRepresentation(self.image) 6 | true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /examples/AppDotNet/app/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | attr_accessor :id, :username, :avatar_url 3 | 4 | def initialize(attributes = {}) 5 | self.id = attributes["id"].to_i 6 | self.username = attributes["username"] 7 | self.avatar_url = attributes["avatar_image.url"] 8 | end 9 | end -------------------------------------------------------------------------------- /examples/AppDotNet/.gitignore: -------------------------------------------------------------------------------- 1 | .repl_history 2 | build 3 | tags 4 | app/pixate_code.rb 5 | resources/*.nib 6 | resources/*.momd 7 | resources/*.storyboardc 8 | .DS_Store 9 | nbproject 10 | .redcar 11 | #*# 12 | *~ 13 | *.sw[po] 14 | .eprj 15 | .sass-cache 16 | .idea 17 | .dat*.* 18 | vendor/Pods 19 | -------------------------------------------------------------------------------- /lib/afmotion/ext/UIImageView_url.rb: -------------------------------------------------------------------------------- 1 | class UIImageView 2 | def url=(url) 3 | case url 4 | when Hash 5 | self.setImageWithURL((url[:url] && url[:url].to_url), placeholderImage: url[:placeholder]) 6 | else 7 | self.setImageWithURL(url && url.to_url) 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /examples/example_api/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | mustermann (1.1.1) 5 | ruby2_keywords (~> 0.0.1) 6 | rack (2.2.3) 7 | rack-protection (2.1.0) 8 | rack 9 | ruby2_keywords (0.0.2) 10 | sinatra (2.1.0) 11 | mustermann (~> 1.0) 12 | rack (~> 2.2) 13 | rack-protection (= 2.1.0) 14 | tilt (~> 2.0) 15 | tilt (2.0.10) 16 | 17 | PLATFORMS 18 | ruby 19 | 20 | DEPENDENCIES 21 | sinatra 22 | 23 | BUNDLED WITH 24 | 2.1.4 25 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | $:.unshift("/Library/RubyMotion/lib") 3 | $:.unshift("~/.rubymotion/rubymotion-templates") # Add this line 4 | require 'motion/project/template/gem/gem_tasks' 5 | require 'motion/project/template/ios' 6 | require "bundler/setup" 7 | Bundler.require :default 8 | 9 | $:.unshift("./lib/") 10 | require './lib/afmotion' 11 | 12 | require 'webstub' 13 | 14 | Motion::Project::App.setup do |app| 15 | # Use `rake config' to see complete project settings. 16 | app.name = 'AFMotion' 17 | app.deployment_target = "10.0" 18 | end 19 | -------------------------------------------------------------------------------- /lib/afmotion.rb: -------------------------------------------------------------------------------- 1 | require "afmotion/version" 2 | require 'motion-cocoapods' 3 | require 'motion-require' 4 | 5 | unless defined?(Motion::Project::Config) 6 | raise "This file must be required within a RubyMotion project Rakefile." 7 | end 8 | 9 | Motion::Require.all(Dir.glob(File.join(File.dirname(__FILE__), 'afmotion/**/*.rb'))) 10 | 11 | Motion::Project::App.setup do |app| 12 | Dir.glob(File.join(File.dirname(__FILE__), 'afmotion/**/*.rb')).each do |file| 13 | if app.respond_to?("exclude_from_detect_dependencies") 14 | app.exclude_from_detect_dependencies << file 15 | end 16 | end 17 | 18 | app.pods do 19 | pod 'AFNetworking', '~> 4.0.0' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /examples/AppDotNet/app/post.rb: -------------------------------------------------------------------------------- 1 | class Post 2 | attr_accessor :id, :text, :user 3 | 4 | def initialize(attributes = {}) 5 | self.id = attributes["id"].to_i 6 | self.text = attributes["text"].to_s 7 | self.user = User.new(attributes["user"]) 8 | end 9 | 10 | def self.fetchGlobalTimelinePosts(&callback) 11 | AFMotion::SessionClient.shared.get("feed") do |result| 12 | if result.success? 13 | posts = [] 14 | result.object["data"].each do |attributes| 15 | posts << Post.new(attributes) 16 | end 17 | callback.call(posts, nil) 18 | else 19 | callback.call([], result.error) 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: 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 | install: 9 | - bundle install 10 | - pod setup > /dev/null 11 | - bundle exec rake pod:install > /dev/null 12 | gemfile: 13 | - Gemfile 14 | script: bundle exec rake spec 15 | env: 16 | global: 17 | - COCOAPODS_NO_REPO_UPDATE_OUTPUT=true 18 | deploy: 19 | provider: rubygems 20 | api_key: 21 | secure: cCwC+P6XvkgW+iE3LY9YpzL8rSwCVVlUQ9dThepSGFN9mmMOYYMhohVm9NS3kkFzCFKCzu80ATi65ndKukOCwLEgsQO4HM9fH2y0XfmYp/aooJUUxzakDA7XI9kWc0SpqURSRujZBobcJnt4HctMLIogihYYQ/DlILgyy+0fU7U= 22 | gem: afmotion 23 | on: 24 | tags: true 25 | repo: clayallsopp/afmotion 26 | -------------------------------------------------------------------------------- /lib/afmotion/operation.rb: -------------------------------------------------------------------------------- 1 | module AFMotion 2 | module Operation 3 | module_function 4 | 5 | def success_block_for_http_method(http_method, callback) 6 | if http_method.downcase.to_sym == :head 7 | return lambda { |operation_or_task| 8 | result = AFMotion::HTTPResult.new(operation_or_task, nil, nil) 9 | callback.call(result) 10 | } 11 | end 12 | 13 | lambda { |operation_or_task, responseObject| 14 | result = AFMotion::HTTPResult.new(operation_or_task, responseObject, nil) 15 | callback.call(result) 16 | } 17 | end 18 | 19 | def failure_block(callback) 20 | lambda { |operation_or_task, error| 21 | result = AFMotion::HTTPResult.new(operation_or_task, nil, error) 22 | callback.call(result) 23 | } 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/afmotion/http_result.rb: -------------------------------------------------------------------------------- 1 | module AFMotion 2 | class HTTPResult 3 | attr_accessor :object, :error, :task 4 | 5 | def initialize(task, responseObject, error) 6 | self.task = task 7 | self.object = responseObject 8 | self.error = error 9 | end 10 | 11 | def success? 12 | !failure? 13 | end 14 | 15 | def failure? 16 | !!error 17 | end 18 | 19 | # include this for backwards compatibility (?) 20 | def operation 21 | puts "HTTPResult#operation is deprecated and returns a task, switch to using #task" 22 | task 23 | end 24 | 25 | def status_code 26 | if self.task && self.task.response 27 | self.task.response.statusCode 28 | else 29 | nil 30 | end 31 | end 32 | 33 | def body 34 | self.object 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /vendor/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (4.0.1): 3 | - AFNetworking/NSURLSession (= 4.0.1) 4 | - AFNetworking/Reachability (= 4.0.1) 5 | - AFNetworking/Security (= 4.0.1) 6 | - AFNetworking/Serialization (= 4.0.1) 7 | - AFNetworking/UIKit (= 4.0.1) 8 | - AFNetworking/NSURLSession (4.0.1): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (4.0.1) 13 | - AFNetworking/Security (4.0.1) 14 | - AFNetworking/Serialization (4.0.1) 15 | - AFNetworking/UIKit (4.0.1): 16 | - AFNetworking/NSURLSession 17 | 18 | DEPENDENCIES: 19 | - AFNetworking (~> 4.0.0) 20 | 21 | SPEC REPOS: 22 | trunk: 23 | - AFNetworking 24 | 25 | SPEC CHECKSUMS: 26 | AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce 27 | 28 | PODFILE CHECKSUM: 22ee56e144b87d6a6d6c7d940a77dd21f0574b3c 29 | 30 | COCOAPODS: 1.8.0 31 | -------------------------------------------------------------------------------- /AFMotion.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/afmotion/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "afmotion" 6 | s.version = AFMotion::VERSION 7 | s.authors = ["Clay Allsopp"] 8 | s.email = ["clay.allsopp@gmail.com"] 9 | s.homepage = "https://github.com/clayallsopp/AFMotion" 10 | s.summary = "A RubyMotion Wrapper for AFNetworking" 11 | s.description = "A RubyMotion Wrapper for AFNetworking" 12 | s.license = 'MIT' 13 | 14 | s.files = `git ls-files`.split($\).delete_if {|x| x.include? "example"} 15 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 16 | s.require_paths = ["lib"] 17 | 18 | s.add_dependency "motion-cocoapods", ">= 1.9.1" 19 | s.add_dependency "motion-require", ">= 0.1" 20 | s.add_development_dependency 'rake' 21 | s.add_development_dependency 'webstub', "~> 1.1.6" 22 | end 23 | -------------------------------------------------------------------------------- /examples/AppDotNet/vendor/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (4.0.1): 3 | - AFNetworking/NSURLSession (= 4.0.1) 4 | - AFNetworking/Reachability (= 4.0.1) 5 | - AFNetworking/Security (= 4.0.1) 6 | - AFNetworking/Serialization (= 4.0.1) 7 | - AFNetworking/UIKit (= 4.0.1) 8 | - AFNetworking/NSURLSession (4.0.1): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (4.0.1) 13 | - AFNetworking/Security (4.0.1) 14 | - AFNetworking/Serialization (4.0.1) 15 | - AFNetworking/UIKit (4.0.1): 16 | - AFNetworking/NSURLSession 17 | 18 | DEPENDENCIES: 19 | - AFNetworking (~> 4.0.0) 20 | 21 | SPEC REPOS: 22 | trunk: 23 | - AFNetworking 24 | 25 | SPEC CHECKSUMS: 26 | AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce 27 | 28 | PODFILE CHECKSUM: e75369dffa3170dcbd1c6d3089c07b556c4b6d07 29 | 30 | COCOAPODS: 1.8.0 31 | -------------------------------------------------------------------------------- /lib/afmotion/serializer.rb: -------------------------------------------------------------------------------- 1 | class AFHTTPRequestSerializer 2 | class HeaderWrapper 3 | def initialize(serializer) 4 | @serializer = WeakRef.new(serializer) 5 | end 6 | 7 | def [](header) 8 | @serializer.HTTPRequestHeaders[header] 9 | end 10 | 11 | def []=(header, value) 12 | @serializer.setValue(value, forHTTPHeaderField: header) 13 | end 14 | 15 | def delete(header) 16 | value = self[header] 17 | self[header] = nil 18 | value 19 | end 20 | end 21 | 22 | def headers 23 | @header_wrapper ||= HeaderWrapper.new(self) 24 | end 25 | 26 | # options can be 27 | # - {username: ___, password: ____} 28 | # or 29 | # - {token: ___ } 30 | def authorization=(options = {}) 31 | if options.nil? 32 | clearAuthorizationHeader 33 | elsif options[:username] && options[:password] 34 | setAuthorizationHeaderFieldWithUsername(options[:username], password: options[:password]) 35 | else 36 | raise "Not a valid authorization hash: #{options.inspect}" 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /examples/example_api/example_web_app.rb: -------------------------------------------------------------------------------- 1 | require "sinatra" 2 | 3 | set :root, File.dirname(__FILE__) 4 | set :port, "4567" 5 | set :bind, "0.0.0.0" 6 | set :static, true 7 | 8 | 9 | get "/feed" do 10 | content_type "text/json" 11 | { 12 | data: [ 13 | { 14 | id: 1, 15 | text: "Trains in Motion travel on Rails", 16 | user: { 17 | id: 1, 18 | username: "motionuser12", 19 | "avatar_image.url" => "#{ request.base_url }/motionuser12_avatar.png" 20 | } 21 | }, 22 | { 23 | id: 2, 24 | text: "Funny joke, I think", 25 | user: { 26 | id: 2, 27 | username: "railsuser3", 28 | "avatar_image.url" => "#{ request.base_url }/railsuser3_avatar.png" 29 | } 30 | }, 31 | { 32 | id: 3, 33 | text: "The Objective, See, is to program", 34 | user: { 35 | id: 3, 36 | username: "swiftlytalking4", 37 | "avatar_image.url" => "#{ request.base_url }/swiftlytalking4_avatar.png" 38 | } 39 | } 40 | ] 41 | }.to_json 42 | end 43 | 44 | post "/upload" do 45 | pp params 46 | 47 | content_type "text/json" 48 | 49 | {result: :success}.to_json 50 | end 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENCE 2 | 3 | MIT: http://clayallsopp.mit-license.org 4 | 5 | ------------------------------------------------ 6 | 7 | The MIT License (MIT) 8 | Copyright © 2012 Clay Allsopp 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the “Software”), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /lib/afmotion/session_client.rb: -------------------------------------------------------------------------------- 1 | motion_require 'version' 2 | motion_require 'client_shared' 3 | motion_require 'session_client_dsl' 4 | motion_require 'ext/AFHTTPSessionManager' 5 | 6 | =begin 7 | AFMotion::SessionClient.build("http://google.com") do |client| 8 | client.session_configuration :default # :ephemeral 9 | client.session_configuration :background, "com.usepropeller.afmotion" 10 | client.session_configuration my_session_configuration 11 | 12 | response_serializer 13 | 14 | request_serializer 15 | end 16 | =end 17 | module AFMotion 18 | class SessionClient 19 | class << self 20 | 21 | attr_accessor :shared 22 | 23 | # Returns an instance of AFHTTPRequestOperationManager 24 | def build(base_url, &block) 25 | dsl = AFMotion::SessionClientDSL.new(base_url) 26 | 27 | if block_given? 28 | case block.arity 29 | when 0 30 | dsl.instance_eval(&block) 31 | when 1 32 | block.call(dsl) 33 | end 34 | end 35 | 36 | dsl.to_session_manager 37 | end 38 | 39 | # Sets AFMotion::Client.shared as the built client 40 | def build_shared(base_url, &block) 41 | self.shared = self.build(base_url, &block) 42 | end 43 | end 44 | end 45 | 46 | # These are now the same thing 47 | class Client < SessionClient 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /examples/AppDotNet/app/app_delegate.rb: -------------------------------------------------------------------------------- 1 | class AppDelegate 2 | attr_accessor :navigationController, :window 3 | def application(application, didFinishLaunchingWithOptions:launchOptions) 4 | 5 | AFMotion::SessionClient.build_shared("http://localhost:4567") do 6 | session_configuration :default 7 | header "Accept", "application/json" 8 | 9 | request_serializer :json 10 | end 11 | 12 | url_cache = NSURLCache.alloc.initWithMemoryCapacity(4 * 1024 * 1024, diskCapacity:20 * 1024 * 1024,diskPath:nil) 13 | NSURLCache.setSharedURLCache(url_cache) 14 | 15 | AFNetworkActivityIndicatorManager.sharedManager.enabled = true 16 | 17 | feedController = GlobalTimelineViewController.alloc.initWithStyle(UITableViewStylePlain) 18 | uploadController = UploadViewController.alloc.init 19 | uploadController.title = "Upload Example" 20 | 21 | feedController.tabBarItem.image = UIImage.imageNamed("feed") 22 | uploadController.tabBarItem.image = UIImage.imageNamed("upload") 23 | 24 | self.navigationController = UITabBarController.alloc.init 25 | self.navigationController.tabBar.tintColor = UIColor.darkGrayColor 26 | self.navigationController.setViewControllers([feedController, uploadController], animated: false) 27 | 28 | self.window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) 29 | self.window.backgroundColor = UIColor.whiteColor 30 | self.window.rootViewController = self.navigationController 31 | self.window.makeKeyAndVisible 32 | 33 | true 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /examples/AppDotNet/app/post_table_view_cell.rb: -------------------------------------------------------------------------------- 1 | class PostTableViewCell < UITableViewCell 2 | attr_accessor :post 3 | 4 | def initWithStyle(style, reuseIdentifier:reuseIdentifier) 5 | super 6 | 7 | self.textLabel.adjustsFontSizeToFitWidth = true 8 | self.textLabel.textColor = UIColor.darkGrayColor 9 | self.detailTextLabel.font = UIFont.systemFontOfSize 12 10 | self.detailTextLabel.numberOfLines = 0 11 | self.selectionStyle = UITableViewCellSelectionStyleGray 12 | 13 | self 14 | end 15 | 16 | def post=(post) 17 | @post = post 18 | 19 | self.textLabel.text = self.post.user.username 20 | self.detailTextLabel.text = self.post.text 21 | self.imageView.url = {url: self.post.user.avatar_url.to_url, placeholder: UIImage.imageNamed("profile-image-placeholder")} 22 | 23 | self.setNeedsLayout 24 | 25 | @post 26 | end 27 | 28 | def self.heightForCellWithPost(post) 29 | sizeToFit = post.text.sizeWithFont(UIFont.systemFontOfSize(12), constrainedToSize: CGSizeMake(220, Float::MAX), lineBreakMode:UILineBreakModeWordWrap) 30 | 31 | return [70, sizeToFit.height + 45].max 32 | end 33 | 34 | def layoutSubviews 35 | super 36 | 37 | self.imageView.frame = CGRectMake(10, 10, 50, 50); 38 | self.textLabel.frame = CGRectMake(70, 10, 240, 20); 39 | 40 | detailTextLabelFrame = CGRectOffset(self.textLabel.frame, 0, 25); 41 | detailTextLabelFrame.size.height = self.class.heightForCellWithPost(self.post) - 45 42 | self.detailTextLabel.frame = detailTextLabelFrame 43 | end 44 | end -------------------------------------------------------------------------------- /lib/afmotion/http.rb: -------------------------------------------------------------------------------- 1 | motion_require 'version' 2 | 3 | module AFMotion 4 | class HTTP 5 | def self.manager 6 | @manager ||= begin 7 | manager = AFHTTPSessionManager.manager 8 | configure_manager(manager) 9 | manager 10 | end 11 | end 12 | 13 | def self.configure_manager(manager) 14 | manager.http! 15 | end 16 | end 17 | 18 | class JSON < HTTP 19 | def self.configure_manager(manager) 20 | manager.json! 21 | manager.responseSerializer.readingOptions = NSJSONReadingMutableContainers 22 | end 23 | end 24 | 25 | class XML < HTTP 26 | def self.configure_manager(manager) 27 | manager.xml! 28 | end 29 | end 30 | 31 | class PLIST < HTTP 32 | def self.configure_manager(manager) 33 | manager.plist! 34 | end 35 | end 36 | 37 | class Image < HTTP 38 | def self.configure_manager(manager) 39 | manager.image! 40 | end 41 | end 42 | 43 | [HTTP, JSON, XML, PLIST, Image].each do |base| 44 | AFMotion::HTTP_METHODS.each do |method_name| 45 | http_method = method_name.to_s.upcase 46 | 47 | method_signature = "#{http_method}:parameters:headers:progress:success:failure:" 48 | method_signature.gsub!("progress:", "") if http_method == "HEAD" 49 | 50 | base.define_singleton_method(method_name, -> (url, options = {}, &callback) do 51 | parameters = options.fetch(:params, {}) 52 | headers = options[:headers] 53 | progress = options[:progress_block] 54 | 55 | args = [ method_signature, 56 | url, 57 | parameters, 58 | headers, 59 | progress, 60 | manager.success_block_for_http_method(method_name, callback), 61 | manager.failure_block(callback) 62 | ] 63 | 64 | args.delete_at(4) if http_method == "HEAD" # HEAD doesn't take a progress arg 65 | 66 | base.manager.send(*args) 67 | end) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/http_spec.rb: -------------------------------------------------------------------------------- 1 | describe "AFMotion" do 2 | 3 | modules = [AFMotion::HTTP, AFMotion::JSON, AFMotion::XML, AFMotion::PLIST] 4 | 5 | modules.each do |_module| 6 | describe _module.to_s do 7 | extend WebStub::SpecHelpers 8 | 9 | before do 10 | disable_network_access! 11 | @result = nil 12 | end 13 | 14 | after do 15 | enable_network_access! 16 | reset_stubs 17 | end 18 | 19 | 20 | it "should have all the HTTP methods" do 21 | AFMotion::HTTP_METHODS.each do |method| 22 | _module.respond_to?(method).should == true 23 | end 24 | end 25 | 26 | describe ".get" do 27 | before do 28 | @result = nil 29 | end 30 | 31 | it "should work with string" do 32 | _module.get("https://google.com") do |result| 33 | @result = result 34 | resume 35 | end 36 | wait_max(10) do 37 | @result.nil?.should == false 38 | end 39 | end 40 | end 41 | 42 | describe ".head" do 43 | before do 44 | @result = nil 45 | end 46 | 47 | it "should work with string" do 48 | _module.head("http://google.com") do |result| 49 | @result = result 50 | resume 51 | end 52 | wait_max(10) do 53 | @result.nil?.should == false 54 | end 55 | end 56 | end 57 | 58 | end 59 | end 60 | 61 | describe "AFMotion::Image" do 62 | before do 63 | @result = nil 64 | end 65 | 66 | it "should work with an image" do 67 | url = "https://www.google.com/images/srpr/logo3w.png" 68 | AFMotion::Image.get(url) do |result| 69 | @result = result 70 | resume 71 | end 72 | wait_max(10) do 73 | @result.nil?.should == false 74 | @result.object.is_a?(UIImage).should == true 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "11.0", 8 | "subtype" : "2436h", 9 | "filename" : "Default-2436h@3x.png", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "orientation" : "portrait", 14 | "idiom" : "iphone", 15 | "extent" : "full-screen", 16 | "minimum-system-version" : "8.0", 17 | "subtype" : "736h", 18 | "filename" : "Default-736h@3x.png", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "orientation" : "portrait", 23 | "idiom" : "iphone", 24 | "extent" : "full-screen", 25 | "minimum-system-version" : "8.0", 26 | "subtype" : "667h", 27 | "filename" : "Default-667h@2x.png", 28 | "scale" : "2x" 29 | }, 30 | { 31 | "orientation" : "portrait", 32 | "idiom" : "iphone", 33 | "extent" : "full-screen", 34 | "minimum-system-version" : "7.0", 35 | "filename" : "Default.png", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "orientation" : "portrait", 40 | "idiom" : "iphone", 41 | "extent" : "full-screen", 42 | "minimum-system-version" : "7.0", 43 | "filename" : "Default-568h@2x.png", 44 | "subtype" : "retina4", 45 | "scale" : "2x" 46 | }, 47 | { 48 | "orientation" : "portrait", 49 | "idiom" : "ipad", 50 | "extent" : "full-screen", 51 | "minimum-system-version" : "7.0", 52 | "filename" : "Default-ipad.png", 53 | "scale" : "1x" 54 | }, 55 | { 56 | "orientation" : "portrait", 57 | "idiom" : "ipad", 58 | "extent" : "full-screen", 59 | "minimum-system-version" : "7.0", 60 | "filename" : "Default-ipad@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /spec/integration_spec.rb: -------------------------------------------------------------------------------- 1 | describe "AFMotion" do 2 | extend WebStub::SpecHelpers 3 | 4 | before do 5 | disable_network_access! 6 | @object = nil 7 | @result = nil 8 | end 9 | 10 | after do 11 | enable_network_access! 12 | reset_stubs 13 | end 14 | 15 | describe "JSON" do 16 | it "should use mutable containers" do 17 | url = "http://example.com/" 18 | stub_request(:get, url). 19 | to_return(json: {"data" => ["thing"]}, delay: 0.3) 20 | 21 | AFMotion::JSON.get(url) do |result| 22 | @result = result 23 | @object = result.object 24 | resume 25 | end 26 | 27 | wait_max 1.0 do 28 | array = @object['data'] 29 | array << 'derp' 30 | array.count.should == 2 31 | 32 | @object['hello'] = 'world' 33 | @object.count.should == 2 34 | 35 | @object.delete('data') 36 | @object.count.should == 1 37 | 38 | @result.status_code.should == 200 39 | end 40 | end 41 | end 42 | 43 | describe "Client" do 44 | describe "JSON" do 45 | it "should use mutable containers" do 46 | base_url = "http://example.com/" 47 | path = "path" 48 | stub_request(:get, base_url + path). 49 | to_return(json: {"data" => ["thing"]}, delay: 0.3) 50 | 51 | client = AFMotion::Client.build(base_url) do 52 | response_serializer :json 53 | end 54 | 55 | client.get(path, params: nil) do |result| 56 | @result = result 57 | @object = result.object 58 | resume 59 | end 60 | 61 | wait_max 1.0 do 62 | array = @object['data'] 63 | array << 'derp' 64 | array.count.should == 2 65 | 66 | @object['hello'] = 'world' 67 | @object.count.should == 2 68 | 69 | @object.delete('data') 70 | @object.count.should == 1 71 | 72 | @result.status_code.should == 200 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/afmotion/ext/AFHTTPSessionManager.rb: -------------------------------------------------------------------------------- 1 | motion_require '../client_shared' 2 | 3 | module AFMotion 4 | module Serialization 5 | def with_request_serializer(serializer_klass) 6 | self.requestSerializer = serializer_klass.serializer 7 | self 8 | end 9 | 10 | def with_response_serializer(serializer_klass) 11 | self.responseSerializer = serializer_klass.serializer 12 | self 13 | end 14 | 15 | def http! 16 | with_request_serializer(AFHTTPRequestSerializer). 17 | with_response_serializer(AFHTTPResponseSerializer) 18 | end 19 | 20 | def json! 21 | with_request_serializer(AFJSONRequestSerializer). 22 | with_response_serializer(AFJSONResponseSerializer) 23 | end 24 | 25 | def xml! 26 | with_response_serializer(AFXMLParserResponseSerializer) 27 | end 28 | 29 | def plist! 30 | with_request_serializer(AFPropertyListRequestSerializer). 31 | with_response_serializer(AFPropertyListResponseSerializer) 32 | end 33 | 34 | def image! 35 | with_response_serializer(AFImageResponseSerializer) 36 | end 37 | end 38 | end 39 | 40 | class AFHTTPSessionManager 41 | include AFMotion::Serialization 42 | include AFMotion::ClientShared 43 | 44 | AFMotion::HTTP_METHODS.each do |method| 45 | # EX client.get('my/resource.json') 46 | define_method "#{method}", -> (path, options = {}, &callback) do 47 | create_task(method, path, options, &callback) 48 | end 49 | end 50 | 51 | # options = {parameters: , constructingBodyWithBlock: , success:, failure:} 52 | def PUT(url_string, options = {}) 53 | parameters = options[:parameters] 54 | block = options[:constructingBodyWithBlock] 55 | progress = options[:progress_block] 56 | success = options[:success] 57 | failure = options[:failure] 58 | 59 | request = self.requestSerializer.multipartFormRequestWithMethod("PUT", URLString: NSURL.URLWithString(url_string, relativeToURL: self.baseURL).absoluteString, parameters:parameters, constructingBodyWithBlock:block, error:nil) 60 | 61 | task = self.dataTaskWithRequest(request, uploadProgress: progress, downloadProgress: nil, completionHandler: ->(response, responseObject, error) { 62 | if error && failure 63 | failure.call(task, error) 64 | elsif success 65 | success.call(task, responseObject) 66 | end 67 | }) 68 | 69 | task.resume 70 | 71 | task 72 | end 73 | end -------------------------------------------------------------------------------- /examples/AppDotNet/app/global_timeline_view_controller.rb: -------------------------------------------------------------------------------- 1 | class GlobalTimelineViewController < UITableViewController 2 | attr_accessor :posts 3 | attr_accessor :activityIndicatorView 4 | 5 | def reload 6 | self.activityIndicatorView.startAnimating 7 | self.navigationItem.rightBarButtonItem.enabled = true 8 | 9 | Post.fetchGlobalTimelinePosts do |posts, error| 10 | if (error) 11 | UIAlertView.alloc.initWithTitle("Error", 12 | message:error.localizedDescription, 13 | delegate:nil, 14 | cancelButtonTitle:nil, 15 | otherButtonTitles:"OK", nil).show 16 | else 17 | self.posts = posts 18 | end 19 | 20 | self.activityIndicatorView.stopAnimating 21 | self.navigationItem.rightBarButtonItem.enabled = true 22 | end 23 | end 24 | 25 | def posts 26 | @posts ||= [] 27 | end 28 | 29 | def posts=(posts) 30 | @posts = posts 31 | self.tableView.reloadData 32 | @posts 33 | end 34 | 35 | def loadView 36 | super 37 | 38 | self.activityIndicatorView = UIActivityIndicatorView.alloc.initWithActivityIndicatorStyle(UIActivityIndicatorViewStyleWhite) 39 | self.activityIndicatorView.hidesWhenStopped = true 40 | end 41 | 42 | def viewDidLoad 43 | super 44 | 45 | self.title = "Feed Example" 46 | 47 | self.navigationItem.leftBarButtonItem = UIBarButtonItem.alloc.initWithCustomView(self.activityIndicatorView) 48 | self.navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemRefresh, target:self, action: 'reload') 49 | 50 | self.tableView.rowHeight = 70 51 | 52 | self.reload 53 | end 54 | 55 | def viewDidUnload 56 | self.activityIndicatorView = nil 57 | 58 | super 59 | end 60 | 61 | def tableView(tableView, numberOfRowsInSection:section) 62 | self.posts.count 63 | end 64 | 65 | def tableView(tableView, cellForRowAtIndexPath:indexPath) 66 | @@identifier ||= "Cell" 67 | 68 | cell = tableView.dequeueReusableCellWithIdentifier(@@identifier) || begin 69 | PostTableViewCell.alloc.initWithStyle(UITableViewCellStyleSubtitle, reuseIdentifier:@@identifier) 70 | end 71 | 72 | cell.post = self.posts[indexPath.row] 73 | 74 | cell 75 | end 76 | 77 | def tableView(tableView, heightForRowAtIndexPath:indexPath) 78 | PostTableViewCell.heightForCellWithPost(self.posts[indexPath.row]) 79 | end 80 | 81 | def tableView(tableView, didSelectRowAtIndexPath:indexPath) 82 | tableView.deselectRowAtIndexPath(indexPath, animated:true) 83 | end 84 | end -------------------------------------------------------------------------------- /examples/AppDotNet/app/upload_view_controller.rb: -------------------------------------------------------------------------------- 1 | class UploadViewController < UIViewController 2 | 3 | def viewDidLoad 4 | @upload_button = UIButton.buttonWithType(UIButtonTypeCustom) 5 | @upload_button.setTitle("Upload Avatar", forState: UIControlStateNormal) 6 | @upload_button.setTitleColor(UIColor.blueColor, forState: UIControlStateNormal) 7 | @upload_button.backgroundColor = UIColor.lightGrayColor 8 | @upload_button.titleEdgeInsets = UIEdgeInsetsMake(8,8,8,8) 9 | @upload_button.layer.cornerRadius = 6 10 | @upload_button.sizeToFit 11 | @upload_button.frame.size.width = 200 12 | 13 | 14 | @upload_button.addTarget(self, action: "upload_avatar", forControlEvents: UIControlEventTouchUpInside) 15 | 16 | avatar_image = UIImageView.alloc.initWithImage(UIImage.imageNamed("sample_upload.png")) 17 | avatar_image.contentMode = UIViewContentModeScaleAspectFit 18 | 19 | @progress_bar = UIProgressView.alloc.init 20 | 21 | @stack = UIStackView.alloc.init 22 | @stack.axis = UILayoutConstraintAxisVertical 23 | @stack.alignment = UIStackViewAlignmentFill 24 | @stack.distribution = UIStackViewDistributionEqualSpacing 25 | @stack.spacing = 16.0 26 | @stack.layoutMarginsRelativeArrangement = true 27 | @stack.layoutMargins = UIEdgeInsetsMake(16,16,16,16) 28 | 29 | 30 | 31 | @stack.addArrangedSubview(avatar_image) 32 | @stack.addArrangedSubview(@progress_bar) 33 | @stack.addArrangedSubview(@upload_button) 34 | 35 | view.addSubview(@stack) 36 | end 37 | 38 | def viewWillAppear(a) 39 | @progress_bar.frame.size.width = 200 40 | @stack.frame = CGRectMake(0,40,view.bounds.size.width, view.bounds.size.width - 100) 41 | end 42 | 43 | 44 | def upload_avatar 45 | 46 | @upload_button.enabled = false 47 | 48 | client = AFMotion::SessionClient.build("http://localhost:4567") 49 | data = UIImagePNGRepresentation(UIImage.imageNamed("sample_upload.png")) 50 | 51 | progress_block = proc do |progress| 52 | Dispatch::Queue.main.async do 53 | @progress_bar.setProgress(progress.fractionCompleted, animated: true) 54 | end 55 | end 56 | 57 | client.multipart_post("upload", params: { avatar_upload: "other stuff" }, progress_block: progress_block) do |result, form_data| 58 | if form_data 59 | form_data.appendPartWithFileData(data, name: "avatar", fileName:"sample_upload.png", mimeType: "image/png") 60 | elsif result 61 | @upload_button.enabled = true 62 | 63 | if result.error 64 | alert("Error", result.error.localizedDescription) 65 | else 66 | alert("Complete!", result.body.to_s) 67 | end 68 | end 69 | end 70 | end 71 | 72 | def alert(title, message) 73 | UIAlertView.alloc.initWithTitle(title, 74 | message:message, 75 | delegate:nil, 76 | cancelButtonTitle:nil, 77 | otherButtonTitles:"OK", nil).show 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /examples/AppDotNet/resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "filename" : "20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "20x20", 12 | "filename" : "20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "29x29", 18 | "filename" : "29x29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "idiom" : "iphone", 23 | "size" : "29x29", 24 | "filename" : "29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "40x40", 30 | "filename" : "40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "40x40", 36 | "filename" : "40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "idiom" : "iphone", 47 | "size" : "60x60", 48 | "filename" : "60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "20x20", 54 | "filename" : "20x20~ipad.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "20x20", 60 | "filename" : "20x20~ipad@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "29x29", 66 | "filename" : "29x29~ipad.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "29x29", 72 | "filename" : "29x29~ipad@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "idiom" : "ipad", 77 | "size" : "40x40", 78 | "filename" : "40x40~ipad.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "idiom" : "ipad", 83 | "size" : "40x40", 84 | "filename" : "40x40~ipad@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "76x76~ipad.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "76x76~ipad@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "idiom" : "ipad", 101 | "size" : "83.5x83.5", 102 | "filename" : "83.5x83.5~ipad@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "1024x1024.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/afmotion/client_shared.rb: -------------------------------------------------------------------------------- 1 | module AFMotion 2 | module ClientShared 3 | def headers 4 | requestSerializer.headers 5 | end 6 | 7 | def all_headers 8 | requestSerializer.HTTPRequestHeaders 9 | end 10 | 11 | def authorization=(authorization) 12 | requestSerializer.authorization = authorization 13 | end 14 | 15 | def multipart_post(path, options = {}, &callback) 16 | create_multipart_task(:post, path, options, &callback) 17 | end 18 | 19 | def multipart_put(path, options = {}, &callback) 20 | create_multipart_task(:put, path, options, &callback) 21 | end 22 | 23 | def create_multipart_task(http_method, path, options = {}, &callback) 24 | parameters = options[:params] 25 | headers = options.fetch(:headers, {}) 26 | progress = options[:progress_block] 27 | 28 | inner_callback = Proc.new do |result, form_data| 29 | case callback.arity 30 | when 1 31 | callback.call(result) 32 | when 2 33 | callback.call(result, form_data) 34 | end 35 | end 36 | 37 | multipart_callback = nil 38 | if callback.arity > 1 39 | multipart_callback = lambda { |formData| 40 | inner_callback.call(nil, formData) 41 | } 42 | end 43 | 44 | http_method = http_method.to_s.upcase 45 | if http_method == "POST" 46 | task = self.POST(path, 47 | parameters: parameters, 48 | headers: headers, 49 | constructingBodyWithBlock: multipart_callback, 50 | progress: progress, 51 | success: success_block_for_http_method(:post, inner_callback), 52 | failure: failure_block(inner_callback)) 53 | else 54 | task = self.PUT(path, 55 | parameters: parameters, 56 | headers: headers, 57 | constructingBodyWithBlock: multipart_callback, 58 | progress: progress, 59 | success: success_block_for_http_method(:post, inner_callback), 60 | failure: failure_block(inner_callback)) 61 | end 62 | task 63 | end 64 | 65 | def create_task(http_method, path, options = {}, &callback) 66 | parameters = options.fetch(:params, {}) 67 | headers = options.fetch(:headers, {}) 68 | progress = options[:progress_block] 69 | 70 | method_signature = "#{http_method.to_s.upcase}:parameters:headers:progress:success:failure" 71 | success = success_block_for_http_method(http_method, callback) 72 | failure = failure_block(callback) 73 | method_and_args = [method_signature, path, parameters, headers, progress, success, failure] 74 | 75 | # HEAD doesn't take a progress arg 76 | if http_method.to_s.upcase == "HEAD" 77 | method_signature.gsub!("progress:", "") 78 | method_and_args.delete_at(4) 79 | end 80 | 81 | self.public_send(*method_and_args) 82 | end 83 | 84 | def success_block_for_http_method(http_method, callback) 85 | if http_method.downcase.to_sym == :head 86 | return ->(task) { 87 | result = AFMotion::HTTPResult.new(task, nil, nil) 88 | callback.call(result) 89 | } 90 | end 91 | 92 | ->(task, responseObject) { 93 | result = AFMotion::HTTPResult.new(task, responseObject, nil) 94 | callback.call(result) 95 | } 96 | end 97 | 98 | def failure_block(callback) 99 | ->(task, error) { 100 | result = AFMotion::HTTPResult.new(task, nil, error) 101 | callback.call(result) 102 | } 103 | end 104 | 105 | private 106 | # To force RubyMotion pre-compilation of these methods 107 | def dummy 108 | self.GET("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil) 109 | self.HEAD("", parameters: nil, headers: nil, success: nil, failure: nil) 110 | self.POST("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil) 111 | self.POST("", parameters: nil, headers: nil, constructingBodyWithBlock: nil, progress: nil, success: nil, failure: nil) 112 | self.PUT("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil) 113 | self.DELETE("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil) 114 | self.PATCH("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil) 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /examples/AppDotNet/readme.md: -------------------------------------------------------------------------------- 1 | # Join the RubyMotion Slack Channel # 2 | 3 | [Here is the link.](http://motioneers.herokuapp.com/) Say hello! 4 | 5 | # Minimum Requirements # 6 | 7 | The minimum requirements to use this template are XCode 9.2 and 8 | RubyMotion 5.0. 9 | 10 | Keep in mind that if you've recently upgraded from a previous versions 11 | of XCode or RubyMotion, you'll want to run `rake clean:all` as opposed 12 | to just `rake clean`. 13 | 14 | # Build # 15 | 16 | To build using the default simulator, run: `rake` (alias `rake 17 | simulator`). 18 | 19 | To run on a specific type of simulator. You can run `rake simulator 20 | device_name="SIMULATOR"`. Here is a list of simulators available: 21 | 22 | - `rake simulator device_name='iPhone 5s'` 23 | - `rake simulator device_name='iPhone 8 Plus'` 24 | - `rake simulator device_name='iPhone 8 Plus'` 25 | - `rake simulator device_name='iPhone X'` 26 | - `rake simulator device_name='iPad Pro (9.7-inch)'` 27 | - `rake simulator device_name='iPad Pro (10.5-inch)'` 28 | - `rake simulator device_name='iPad Pro (12.9-inch)'` 29 | 30 | Consider using https://github.com/KrauseFx/xcode-install (and other 31 | parts of FastLane) to streamline management of simulators, 32 | certificates, and pretty much everything else. 33 | 34 | So, for example, you can run `rake simulator device_name='iPhone X'` 35 | to see what your app would look like on iPhone X. 36 | 37 | # Deploying to the App Store # 38 | 39 | To deploy to the App Store, you'll want to use `rake clean 40 | archive:distribution`. With a valid distribution certificate. 41 | 42 | In your `Rakefile`, set the following values: 43 | 44 | ```ruby 45 | #This is only an example, the location where you store your provisioning profiles is at your discretion. 46 | app.codesign_certificate = "iPhone Distribution: xxxxx" #This is only an example, you certificate name may be different. 47 | 48 | #This is only an example, the location where you store your provisioning profiles is at your discretion. 49 | app.provisioning_profile = './profiles/distribution.mobileprovision' 50 | ``` 51 | 52 | For TestFlight builds, you'll need to include the following line 53 | (still using the distribution certificates): 54 | 55 | ```ruby 56 | app.entitlements['beta-reports-active'] = true 57 | ``` 58 | 59 | # Icons # 60 | 61 | As of iOS 11, Apple requires the use of Asset Catalogs for defining 62 | icons and launch screens. You'll find icon and launch screen templates 63 | under `./resources/Assets.xcassets`. 64 | 65 | The current build has a built-in icon generate that can be triggered 66 | from your Rakefile. The first step is to create a PNG file (keep in 67 | mind that your `.png` file _cannot_ contain alpha channels) and place 68 | it in your `resources` directory. This file should be a minimum of 1024 69 | x 1024 pixels (it can be higher) and should be square (to prevent 70 | aspect distortion during the generation process). 71 | 72 | To generate your icon in the Asset catalogue, add a dependency to your `Rakefile` as follows: 73 | 74 | ```ruby 75 | task :icons => 'resources/app-icon.icon_asset' 76 | ``` 77 | 78 | For the above example, the application icon is in a file called `resources/app-icon.png`. The new icons are then generated with the following command: 79 | 80 | ```sh 81 | bundle exec rake icons 82 | ``` 83 | Once complete the new set of icon assets (and the `Contents.json` file) 84 | are generated. Additionally the file `resources/app-icon.icon_asset` will be 85 | created (and should be added to git) to track when the generated icons require 86 | a rebuild (the above command can be run multiple time and will only regenerated 87 | when the base `.png` is newer than the icon assets). 88 | 89 | To make the icon generate part of your regular build process, add then following 90 | dependency (replacing the previous dependency): 91 | 92 | ```ruby 93 | task 'build:icons' => 'resources/app-icon.icon_asset' 94 | ``` 95 | 96 | Now, the icon assets will be regenerated (if the `resources/app-icon.png` file is newer than the assets) whenever you run any `bundle exec rake` command. 97 | 98 | For more information about Asset Catalogs, refer to this link: https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/ 99 | 100 | *Note:* For existing projects that do not have the Assets.xcassets directories from the new 101 | RubyMotion templates can simply add the `task 'build:icons' ...` dependency from above and 102 | all of the necessary files will be generated. -------------------------------------------------------------------------------- /lib/afmotion/session_client_dsl.rb: -------------------------------------------------------------------------------- 1 | module AFMotion 2 | class SessionClientDSL 3 | class Config 4 | attr_accessor :responseSerializer, :requestSerializer, :sessionConfiguration 5 | 6 | class MockRequestSerializer 7 | attr_accessor :authorization 8 | end 9 | 10 | def requestSerializer 11 | @requestSerializer ||= MockRequestSerializer.new 12 | end 13 | 14 | def requestSerializer=(requestSerializer) 15 | if @requestSerializer && @requestSerializer.is_a?(MockRequestSerializer) 16 | requestSerializer.authorization = @requestSerializer.authorization 17 | end 18 | @requestSerializer = requestSerializer 19 | end 20 | 21 | def headers 22 | @headers ||= {} 23 | end 24 | end 25 | 26 | attr_accessor :config 27 | 28 | def initialize(base_url) 29 | @base_url = base_url 30 | @config = Config.new 31 | end 32 | 33 | def to_session_manager 34 | session_manager = AFHTTPSessionManager.alloc.initWithBaseURL(@base_url.to_url, 35 | sessionConfiguration: config.sessionConfiguration) 36 | 37 | session_manager.responseSerializer = config.responseSerializer if config.responseSerializer 38 | if !config.requestSerializer.is_a?(Config::MockRequestSerializer) 39 | session_manager.requestSerializer = config.requestSerializer 40 | elsif config.requestSerializer.authorization 41 | session_manager.requestSerializer.authorization = config.requestSerializer.authorization 42 | end 43 | 44 | config.headers.each do |key, value| 45 | session_manager.requestSerializer.headers[key] = value 46 | end 47 | session_manager 48 | end 49 | 50 | SESSION_CONFIGURATION_SHORTHAND = { 51 | default: :defaultSessionConfiguration, 52 | ephemeral: :ephemeralSessionConfiguration, 53 | background: (Object.const_defined?("UIDevice") && UIDevice.currentDevice.systemVersion.to_f >= 8.0 ? "backgroundSessionConfigurationWithIdentifier:" : "backgroundSessionConfiguration:").to_sym 54 | } 55 | 56 | def session_configuration(session_configuration, identifier = nil) 57 | if session_configuration.is_a?(Symbol) || session_configuration.is_a?(String) 58 | method_signature = SESSION_CONFIGURATION_SHORTHAND[session_configuration.to_sym] 59 | ns_url_session_configuration = begin 60 | if identifier 61 | NSURLSessionConfiguration.send(method_signature, identifier) 62 | else 63 | NSURLSessionConfiguration.send(method_signature) 64 | end 65 | end 66 | self.config.sessionConfiguration = ns_url_session_configuration 67 | elsif session_configuration.is_a?(NSURLSessionConfiguration) || 68 | # cluster class smh 69 | session_configuration.class.to_s.include?("URLSessionConfiguration") 70 | self.config.sessionConfiguration = session_configuration 71 | else 72 | raise "Invalid type for session_configuration; need Symbol, String, or NSURLSessionConfiguration, but got #{session_configuration.class}" 73 | end 74 | end 75 | 76 | def header(header, value) 77 | @headers ||= {} 78 | @headers[header] = value 79 | apply_header(header, value) 80 | end 81 | 82 | def authorization(options = {}) 83 | @authorization = options 84 | apply_authorization(options) 85 | end 86 | 87 | OPERATION_TO_REQUEST_SERIALIZER = { 88 | json: AFJSONRequestSerializer, 89 | plist: AFPropertyListRequestSerializer 90 | } 91 | def request_serializer(serializer) 92 | if serializer.is_a?(Symbol) || serializer.is_a?(String) 93 | config.requestSerializer = OPERATION_TO_REQUEST_SERIALIZER[serializer.to_sym].serializer 94 | elsif serializer.is_a?(Class) 95 | config.requestSerializer = serializer.serializer 96 | else 97 | config.requestSerializer = serializer 98 | end 99 | reapply_options 100 | end 101 | 102 | OPERATION_TO_RESPONSE_SERIALIZER = { 103 | json: AFJSONResponseSerializer, 104 | xml: AFXMLParserResponseSerializer, 105 | plist: AFPropertyListResponseSerializer, 106 | image: AFImageResponseSerializer, 107 | http: AFHTTPResponseSerializer, 108 | form: AFHTTPResponseSerializer 109 | } 110 | def response_serializer(serializer) 111 | write_json_options = true 112 | if serializer.is_a?(Symbol) || serializer.is_a?(String) 113 | config.responseSerializer = OPERATION_TO_RESPONSE_SERIALIZER[serializer.to_sym].serializer 114 | elsif serializer.is_a?(Class) 115 | config.responseSerializer = serializer.serializer 116 | else 117 | config.responseSerializer = serializer 118 | write_json_options = false 119 | end 120 | af_serializer = config.responseSerializer 121 | if af_serializer.is_a?(AFJSONResponseSerializer) && write_json_options 122 | af_serializer.readingOptions = NSJSONReadingMutableContainers 123 | end 124 | af_serializer 125 | end 126 | 127 | private 128 | 129 | def reapply_options 130 | @headers.each{ |h,v| apply_header(h, v) } unless @headers.nil? 131 | apply_authorization(@authorization) unless @authorization.nil? 132 | end 133 | 134 | def apply_header(header, value) 135 | config.headers[header] = value 136 | end 137 | 138 | def apply_authorization(options) 139 | config.requestSerializer.authorization = options 140 | end 141 | 142 | end 143 | end -------------------------------------------------------------------------------- /examples/AppDotNet/Rakefile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | $:.unshift("/Library/RubyMotion/lib") 3 | $:.unshift("~/.rubymotion/rubymotion-templates") 4 | 5 | # =========================================================================================== 6 | # 1. Be sure to read `readme.md`. 7 | # =========================================================================================== 8 | 9 | require 'motion/project/template/ios' 10 | 11 | begin 12 | require 'bundler' 13 | Bundler.require 14 | rescue LoadError 15 | end 16 | 17 | # Uncomment the following line to add an icon generate capacity to your build 18 | #task 'build:icons' => 'resources/app-icon.icon_asset' 19 | 20 | Motion::Project::App.setup do |app| 21 | # Use `rake config' to see complete project settings. 22 | define_icon_defaults!(app) 23 | 24 | # =========================================================================================== 25 | # 2. Set your app name (this is what will show up under the icon when your app is installed). 26 | # =========================================================================================== 27 | app.name = 'AppDotNet' 28 | 29 | # version for your app 30 | app.version = '1.0' 31 | 32 | 33 | app.info_plist['NSAppTransportSecurity'] = { 34 | # See https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW68 35 | 'NSAllowsLocalNetworking' => true # The presence of this key overrides the previous key, whitelising only local connections. Supports iOS 10+. 36 | } 37 | 38 | app.pods do 39 | pod 'AFNetworking', "~> 4.0.0" 40 | end 41 | 42 | # =========================================================================================== 43 | # 3. Set your deployment target (it's recommended that you at least target 10.0 and above). 44 | # If you're using RubyMotion Starter Edition. You cannot set this value (the latest 45 | # version of iOS will be used). 46 | # =========================================================================================== 47 | # app.deployment_target = '10.0' 48 | 49 | # =========================================================================================== 50 | # 4. Set the architectures for which to build. 51 | # =========================================================================================== 52 | app.archs['iPhoneOS'] = ['arm64'] 53 | 54 | # =========================================================================================== 55 | # 5. Your app identifier is needed to deploy to an actual device. You do not need to set this 56 | # if you are using the simulator. You can create an app identifier at: 57 | # https://developer.apple.com/account/ios/identifier/bundle. You must enroll into Apple's 58 | # Developer program to get access to this screen (there is an annual fee of $99). 59 | # =========================================================================================== 60 | # app.identifier = '' 61 | 62 | # =========================================================================================== 63 | # 6. If you need to reference any additional iOS libraries, use the config array below. 64 | # Default libraries: UIKit, Foundation, CoreGraphics, CoreFoundation, CFNetwork, CoreAudio 65 | # =========================================================================================== 66 | # app.frameworks << "StoreKit" 67 | 68 | # reasonable defaults 69 | app.device_family = [:iphone, :ipad] 70 | app.interface_orientations = [:portrait] 71 | app.info_plist['UIRequiresFullScreen'] = true 72 | app.info_plist['ITSAppUsesNonExemptEncryption'] = false 73 | 74 | # =========================================================================================== 75 | # 7. To deploy to an actual device, you will need to create a developer certificate at: 76 | # https://developer.apple.com/account/ios/certificate/development 77 | # The name of the certificate will be accessible via Keychain Access. Set the value you 78 | # see there below. 79 | # =========================================================================================== 80 | # app.codesign_certificate = '' 81 | 82 | # =========================================================================================== 83 | # 8. To deploy to an actual device, you will need to create a provisioning profile. First: 84 | # register your device at: 85 | # https://developer.apple.com/account/ios/device/ 86 | # 87 | # Then create a development provisioning profile at: 88 | # https://developer.apple.com/account/ios/profile/limited 89 | # 90 | # Download the profile and set the path to the download location below. 91 | # =========================================================================================== 92 | # app.provisioning_profile = '' 93 | 94 | # =========================================================================================== 95 | # 9. Similar to Step 8. Production, create a production certificate at: 96 | # https://developer.apple.com/account/ios/certificate/distribution. 97 | # These values will need to be set to before you can deploy to the App Store. Compile 98 | # using `rake clean archive:distribution` and upload the .ipa under ./build using 99 | # Application Loader. 100 | # =========================================================================================== 101 | # app.codesign_certificate = '' 102 | # app.provisioning_profile = '' 103 | 104 | # =========================================================================================== 105 | # 10. If you want to create a beta build. Uncomment the line below and set your profile to 106 | # point to your production provisions (Step 9). 107 | # =========================================================================================== 108 | # app.entitlements['beta-reports-active'] = true 109 | end 110 | 111 | def define_icon_defaults!(app) 112 | # This is required as of iOS 11.0 (you must use asset catalogs to 113 | # define icons or your app will be rejected. More information in 114 | # located in the readme. 115 | 116 | app.info_plist['CFBundleIcons'] = { 117 | 'CFBundlePrimaryIcon' => { 118 | 'CFBundleIconName' => 'AppIcon', 119 | 'CFBundleIconFiles' => ['AppIcon60x60'] 120 | } 121 | } 122 | 123 | app.info_plist['CFBundleIcons~ipad'] = { 124 | 'CFBundlePrimaryIcon' => { 125 | 'CFBundleIconName' => 'AppIcon', 126 | 'CFBundleIconFiles' => ['AppIcon60x60', 'AppIcon76x76'] 127 | } 128 | } 129 | end 130 | -------------------------------------------------------------------------------- /spec/session_client_spec.rb: -------------------------------------------------------------------------------- 1 | describe "AFMotion::SessionClientDSL" do 2 | before do 3 | @dsl = AFMotion::SessionClientDSL.new("https://url") 4 | end 5 | 6 | describe "#header" do 7 | it "should set header" do 8 | @dsl.header "Accept", "application/json" 9 | @dsl.to_session_manager.requestSerializer.HTTPRequestHeaders["Accept"].should == "application/json" 10 | end 11 | end 12 | 13 | describe "#authorization" do 14 | it "should set authorization" do 15 | @dsl.authorization username: "clay", password: "test" 16 | @dsl.to_session_manager.requestSerializer.HTTPRequestHeaders["Authorization"].nil?.should == false 17 | end 18 | end 19 | 20 | describe "#request_serializer" do 21 | it "should set request_serializer if provided type" do 22 | @dsl.request_serializer AFJSONRequestSerializer 23 | @dsl.to_session_manager.requestSerializer.is_a?(AFJSONRequestSerializer).should == true 24 | end 25 | 26 | it "should set request_serializer if provided string" do 27 | [["json", AFJSONRequestSerializer], ["plist", AFPropertyListRequestSerializer]].each do |op, op_class| 28 | @dsl.request_serializer op 29 | @dsl.to_session_manager.requestSerializer.is_a?(op_class).should == true 30 | end 31 | end 32 | end 33 | 34 | describe "#response_serializer" do 35 | it "should set response_serializer if provided type" do 36 | @dsl.response_serializer AFJSONResponseSerializer 37 | @dsl.to_session_manager.responseSerializer.is_a?(AFJSONResponseSerializer).should == true 38 | end 39 | 40 | it "should set response_serializer if provided string" do 41 | [["json", AFJSONResponseSerializer], 42 | ["form", AFHTTPResponseSerializer], 43 | ["http", AFHTTPResponseSerializer], 44 | ["xml", AFXMLParserResponseSerializer], 45 | ["plist", AFPropertyListResponseSerializer], 46 | ["image", AFImageResponseSerializer]].each do |enc, enc_class| 47 | @dsl.response_serializer enc 48 | @dsl.to_session_manager.responseSerializer.is_a?(enc_class).should == true 49 | end 50 | end 51 | 52 | it "should set mutable reading options for JSON serializer" do 53 | @dsl.response_serializer :json 54 | @dsl.to_session_manager.responseSerializer.readingOptions.should == NSJSONReadingMutableContainers 55 | end 56 | 57 | it "should not set reading options for JSON serializer if raw one supplied" do 58 | @dsl.response_serializer AFJSONResponseSerializer.serializer 59 | @dsl.to_session_manager.responseSerializer.readingOptions.should.not == NSJSONReadingMutableContainers 60 | end 61 | end 62 | 63 | describe "#session_configuration" do 64 | describe "for default" do 65 | it "should work" do 66 | @dsl.session_configuration :default 67 | @dsl.to_session_manager.sessionConfiguration.URLCache.diskCapacity.should > 0 68 | end 69 | end 70 | 71 | describe "for ephemeral" do 72 | it "should work" do 73 | @dsl.session_configuration :ephemeral 74 | @dsl.to_session_manager.sessionConfiguration.URLCache.diskCapacity.should == 0 75 | end 76 | end 77 | 78 | describe "for background" do 79 | it "should work" do 80 | @dsl.session_configuration :background, "com.usepropeller.afmotion.test" 81 | manager = @dsl.to_session_manager 82 | manager.sessionConfiguration.sessionSendsLaunchEvents.should == true 83 | manager.sessionConfiguration.identifier.should == "com.usepropeller.afmotion.test" 84 | end 85 | end 86 | 87 | describe "for instances" do 88 | it "should work" do 89 | session_config = NSURLSessionConfiguration.defaultSessionConfiguration 90 | session_config.identifier = "test" 91 | @dsl.session_configuration session_config 92 | @dsl.to_session_manager.sessionConfiguration.should == session_config 93 | end 94 | end 95 | end 96 | end 97 | 98 | describe "AFMotion::SessionClient" do 99 | describe ".build" do 100 | it "should return an AFHTTPSessionManager" do 101 | client = AFMotion::SessionClient.build("https://url") do 102 | end 103 | client.is_a?(AFHTTPSessionManager).should == true 104 | end 105 | end 106 | 107 | describe ".build_shared" do 108 | it "should set AFMotion::SessionClient.shared" do 109 | client = AFMotion::SessionClient.build_shared("https://url") do 110 | end 111 | AFMotion::SessionClient.shared.should == client 112 | end 113 | end 114 | end 115 | 116 | describe "AFHTTPSessionManager" do 117 | extend WebStub::SpecHelpers 118 | 119 | before do 120 | @url = "https://url.com" 121 | @client = AFMotion::SessionClient.build(@url) 122 | 123 | disable_network_access! 124 | @result = nil 125 | end 126 | 127 | after do 128 | enable_network_access! 129 | reset_stubs 130 | end 131 | 132 | describe "URL Helpers" do 133 | it "should exist" do 134 | AFMotion::HTTP_METHODS.each do |method| 135 | @client.respond_to?(method).should == true 136 | end 137 | end 138 | end 139 | 140 | # Pretty basic test 141 | it "should work" do 142 | stub_request(:get, @url).to_return(body: "") 143 | 144 | @client.get("") do |result| 145 | @result = result 146 | resume 147 | end 148 | wait_max(10) do 149 | @result.nil?.should == false 150 | @result.error.should == nil 151 | end 152 | end 153 | 154 | describe "#authorization=" do 155 | it "should set basic auth" do 156 | @client.authorization = {username: "clay", password: "pass"} 157 | @client.requestSerializer.HTTPRequestHeaders["Authorization"].split[0].should == "Basic" 158 | end 159 | end 160 | 161 | describe "#headers" do 162 | describe "#[]" do 163 | it "should return a header" do 164 | @client.requestSerializer.setValue("test_value", forHTTPHeaderField: "test") 165 | @client.headers["test"].should == "test_value" 166 | end 167 | end 168 | 169 | describe "#[]=" do 170 | it "should set a header" do 171 | @client.headers["test"] = "test_set_value" 172 | @client.requestSerializer.HTTPRequestHeaders["test"].should == "test_set_value" 173 | end 174 | end 175 | 176 | describe "#delete" do 177 | it "should remove a header" do 178 | @client.requestSerializer.setValue("test_value", forHTTPHeaderField: "test") 179 | @client.headers.delete("test").should == "test_value" 180 | @client.requestSerializer.HTTPRequestHeaders["test"].should == nil 181 | end 182 | end 183 | end 184 | 185 | ["multipart_post", "multipart_put"].each do |multipart_method| 186 | describe "##{multipart_method}" do 187 | it "should trigger multipart request" do 188 | stub_request(multipart_method.gsub(/^multipart_/, "").to_sym, @url).to_return(body: "", delay: 0.5) 189 | 190 | @client.send(multipart_method, "", params: { test: "Herp" }) do |result, form_data| 191 | @result = result 192 | resume if result 193 | end 194 | 195 | wait_max(10) do 196 | @result.should.not == nil 197 | 198 | if @result.error 199 | puts "HTTP ERROR: #{@result.error.localizedDescription}" 200 | end 201 | 202 | @result.error.should == nil 203 | @result.task.currentRequest.valueForHTTPHeaderField("Content-Type").include?("multipart/form-data").should == true 204 | end 205 | end 206 | 207 | it "should work with form data" do 208 | stub_request(multipart_method.gsub(/^multipart_/, "").to_sym, @url).to_return(body: "", delay: 0.5) 209 | 210 | @client.send(multipart_method, "", params: { test: "Herp" }) do |result, form_data| 211 | if result 212 | resume 213 | else 214 | @form_data = form_data 215 | end 216 | end 217 | 218 | wait_max(10) do 219 | @form_data.should.not == nil 220 | end 221 | end 222 | 223 | it "should have upload callback with progress" do 224 | stub_request(multipart_method.gsub(/^multipart_/, "").to_sym, @url).to_return(json: "", delay: 0.5) 225 | 226 | image = UIImage.imageNamed("test") 227 | @data = UIImagePNGRepresentation(image) 228 | @file_added = nil 229 | progress_block = proc do |progress| 230 | @progress = progress 231 | end 232 | 233 | @client.send(multipart_method, "", params: { test: "Herp" }, progress_block: progress_block) do |result, form_data| 234 | if form_data 235 | @file_added = true 236 | form_data.appendPartWithFileData(@data, name: "test", fileName:"test.png", mimeType: "image/png") 237 | elsif result 238 | @result = result 239 | resume 240 | end 241 | end 242 | 243 | wait_max(20) do 244 | @result.should.not == nil 245 | 246 | if @result.error 247 | puts "HTTP ERROR: #{@result.error.localizedDescription}" 248 | end 249 | 250 | @result.error.should == nil 251 | @file_added.should == true 252 | 253 | # with updated webstub, I wasn't able to get progress to report at all (but it works in the sample app) 254 | # if (Object.const_defined?("UIDevice") && UIDevice.currentDevice.model =~ /simulator/i).nil? 255 | # @progress.should.not == nil 256 | # @progress.fractionCompleted.should <= 1.0 257 | # end 258 | @result.should.not == nil 259 | end 260 | end 261 | end 262 | end 263 | end 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AFMotion 2 | 3 | [![Build Status](https://travis-ci.org/clayallsopp/afmotion.png?branch=master)](https://travis-ci.org/clayallsopp/afmotion) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion.svg?size=small)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion?ref=badge_small) 4 | 5 | AFMotion is a thin RubyMotion wrapper for [AFNetworking](https://github.com/AFNetworking/AFNetworking), the absolute best networking library on iOS and OS X. 6 | 7 | ## Usage 8 | 9 | AFMotion can be used with standalone URL requests: 10 | 11 | ```ruby 12 | AFMotion::HTTP.get("http://google.com") do |result| 13 | p result.body 14 | end 15 | 16 | AFMotion::JSON.get("http://jsonip.com") do |result| 17 | p result.object["ip"] 18 | end 19 | ``` 20 | 21 | ### Web Services 22 | 23 | ```ruby 24 | 25 | @client = AFMotion::... # create your client 26 | 27 | @client.get("stream/0/posts/stream/global") do |result| 28 | if result.success? 29 | p (result.operation || result.task) # depending on your client 30 | elsif result.failure? 31 | p result.error.localizedDescription 32 | end 33 | end 34 | ``` 35 | 36 | #### Migration from AFMotion 2.x 37 | 38 | _Breaking Change_ 39 | Parameters must now be specified with the `params:` keyword arg. 40 | 41 | AFMotion 2.x 42 | 43 | ```ruby 44 | AFMotion::HTTP.get("http://google.com", q: "rubymotion") do |result| 45 | # sends request to http://google.com?q=rubymotion 46 | end 47 | ``` 48 | 49 | AFMotion 3.x 50 | 51 | ```ruby 52 | AFMotion::HTTP.get("http://google.com", params: { q: "rubymotion" }) do |result| 53 | # sends request to http://google.com?q=rubymotion 54 | end 55 | ``` 56 | 57 | This allows you to also pass in a progress_block or additional headers on the fly: 58 | 59 | ```ruby 60 | AFMotion::HTTP.get("http://url.com/large_file.mov", params: { quality: "high" }, progress_block: proc { |progress| update_progress(progress) }, headers: {}) do |result| 61 | # sends request to http://google.com?q=rubymotion 62 | end 63 | ``` 64 | 65 | For grouping similar requests (AFHTTPSession), use `AFMotion::Client` (now exactly the same as `AFMotion::SessionClient`) 66 | 67 | #### AFMotion::Client 68 | 69 | If you're interacting with a web service, you can use [`AFHTTPRequestOperationManager`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPRequestOperationManager.html) with this nice wrapper: 70 | 71 | ```ruby 72 | # DSL Mapping to properties of AFHTTPRequestOperationManager 73 | 74 | @client = AFMotion::Client.build("https://alpha-api.app.net/") do 75 | header "Accept", "application/json" 76 | 77 | response_serializer :json 78 | end 79 | ``` 80 | 81 | ### Images 82 | 83 | Loading images from the internet is pretty common. AFNetworking's existing methods aren't bad at all, but just incase you want extra Ruby: 84 | 85 | ```ruby 86 | image_view = UIImageView.alloc.initWithFrame CGRectMake(0, 0, 100, 100) 87 | image_view.url = "http://i.imgur.com/r4uwx.jpg" 88 | 89 | # or 90 | 91 | placeholder = UIImage.imageNamed "placeholder-avatar" 92 | image_view.url = {url: "http://i.imgur.com/r4uwx.jpg", placeholder: placeholder} 93 | ``` 94 | 95 | You can also request arbitrary images: 96 | 97 | ```ruby 98 | AFMotion::Image.get("https://www.google.com/images/srpr/logo3w.png") do |result| 99 | image_view = UIImageView.alloc.initWithImage(result.object) 100 | end 101 | ``` 102 | 103 | ## Install 104 | 105 | 1. `gem install afmotion` 106 | 107 | 2. `require 'afmotion'` or add to your `Gemfile` (`gem 'afmotion'`) 108 | 109 | 3. `rake pod:install` 110 | 111 | ## Overview 112 | 113 | ### Results 114 | 115 | Each AFMotion wrapper callback yields an `AFMotion::HTTPResult` object. This object has properties like so: 116 | 117 | ```ruby 118 | AFMotion::some_function do |result| 119 | p result.task.inspect 120 | p result.status_code 121 | 122 | if result.success? 123 | # result.object depends on the type of operation. 124 | # For JSON and PLIST, this is usually a Hash. 125 | # For XML, this is an NSXMLParser 126 | # For HTTP, this is an NSURLResponse 127 | # For Image, this is a UIImage 128 | p result.object 129 | 130 | elsif result.failure? 131 | # result.error is an NSError 132 | p result.error.localizedDescription 133 | end 134 | end 135 | ``` 136 | 137 | ### One-off Requests 138 | 139 | There are wrappers which automatically run a URL request for a given URL and HTTP method, of the form: 140 | 141 | ```ruby 142 | AFMotion::[Operation Type].[HTTP method](url, [Parameters = {}]) do |result| 143 | ... 144 | end 145 | ``` 146 | 147 | Example: 148 | 149 | ```ruby 150 | AFMotion::HTTP.get("http://google.com", params: { q: "rubymotion" }) do |result| 151 | # sends request to http://google.com?q=rubymotion 152 | end 153 | ``` 154 | 155 | - `AFMotion::HTTP.get/post/put/patch/delete(url)...` 156 | - `AFMotion::JSON.get/post/put/patch/delete(url)...` 157 | - `AFMotion::XML.get/post/put/patch/delete(url)...` 158 | - `AFMotion::PLIST.get/post/put/patch/delete(url)...` 159 | - `AFMotion::Image.get/post/put/patch/delete(url)...` 160 | 161 | ### HTTP Client 162 | 163 | If you're constantly accesing a web service, it's a good idea to use an `AFHTTPSessionManager`. Things lets you add a common base URL and request headers to all the requests issued through it, like so: 164 | 165 | ```ruby 166 | client = AFMotion::Client.build("https://alpha-api.app.net/") do 167 | header "Accept", "application/json" 168 | 169 | response_serializer :json 170 | end 171 | 172 | client.get("stream/0/posts/stream/global") do |result| 173 | # result.operation exists 174 | ... 175 | end 176 | ``` 177 | 178 | If you're constantly used one web service, you can use the `AFMotion::Client.shared` variable have a common reference. It can be set like a normal variable or created with `AFMotion::Client.build_shared`. 179 | 180 | `AFHTTPRequestOperationManager` & `AFHTTPSessionManager` support methods of the form `Client#get/post/put/patch/delete(url, request_parameters)`. The `request_parameters` is a hash containing your parameters to attach as the request body or URL parameters, depending on request type. For example: 181 | 182 | ```ruby 183 | client.get("users", params: { id: 1 }) do |result| 184 | ... 185 | end 186 | 187 | client.post("users", params: { name: "@clayallsopp", library: "AFMotion" }) do |result| 188 | ... 189 | end 190 | ``` 191 | 192 | #### Multipart Requests 193 | 194 | `AFHTTPSessionManager` support multipart form requests (i.e. for image uploading) - simply use `multipart_post` and it'll convert your parameters into properly encoded multipart data. For all other types of request data, use the `form_data` object passed to your callback: 195 | 196 | ```ruby 197 | # an instance of UIImage 198 | image = my_function.get_image 199 | data = UIImagePNGRepresentation(image) 200 | 201 | client.multipart_post("avatars") do |result, form_data| 202 | if form_data 203 | # Called before request runs 204 | # see: http://cocoadocs.org/docsets/AFNetworking/2.5.0/Protocols/AFMultipartFormData.html 205 | form_data.appendPartWithFileData(data, name: "avatar", fileName:"avatar.png", mimeType: "image/png") 206 | elsif result.success? 207 | ... 208 | else 209 | ... 210 | end 211 | end 212 | ``` 213 | 214 | This is an instance of [`AFMultipartFormData`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Protocols/AFMultipartFormData.html). 215 | 216 | If you want to track upload progress, simply add a progress_block (Taking a single arg: `NSProgress`) 217 | 218 | ```ruby 219 | client.multipart_post("avatars", progress_block: proc { |progress| update_progress(progress) }) do |result, form_data| 220 | if form_data 221 | # Called before request runs 222 | # see: https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ 223 | form_data.appendPartWithFileData(data, name: "avatar", fileName:"avatar.png", mimeType: "image/png") 224 | else 225 | ... 226 | end 227 | ``` 228 | 229 | #### Headers 230 | 231 | You can set default HTTP headers using `client.headers`, which is sort of like a `Hash`: 232 | 233 | ```ruby 234 | client.headers["Accept"] 235 | #=> "application/json" 236 | 237 | client.headers["Accept"] = "something_else" 238 | #=> "application/something_else" 239 | 240 | client.headers.delete "Accept" 241 | #=> "application/something_else" 242 | ``` 243 | 244 | #### Client Building DSL 245 | 246 | The `AFMotion::Client` & `AFMotion::SessionClient` DSLs allows the following properties: 247 | 248 | - `header(header, value)` 249 | - `authorization(username: ___, password: ____)` for HTTP Basic auth, or `authorization(token: ____)` for Token based auth. 250 | - `request_serializer(serializer)`. Allows you to set an [`AFURLRequestSerialization`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Protocols/AFURLRequestSerialization.html) for all your client's requests, which determines how data is encoded on the way to the server. So if your API is always going to be JSON, you should set `operation(:json)`. Accepts `:json` and `:plist`, or any instance of `AFURLRequestSerialization` and must be called before calling `header` or `authorization` or else the [headers will not be applied](https://github.com/clayallsopp/afmotion/issues/78). 251 | - `response_serializer(serializer)`. Allows you to set an [`AFURLResponseSerialization`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Protocols/AFURLResponseSerialization.html), which determines how data is decoded once the server respnds. Accepts `:json`, `:xml`, `:plist`, `:image`, `:http`, or any instance of `AFURLResponseSerialization`. 252 | 253 | For `AFMotion::SessionClient` only: 254 | 255 | - `session_configuration(session_configuration, identifier = nil)`. Allows you to set the [`NSURLSessionConfiguration`](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/cl/NSURLSessionConfiguration). Accepts `:default`, `:ephemeral`, `:background` (with the `identifier` as a String), or an instance of `NSURLSessionConfiguration`. 256 | 257 | You can also configure your client by passing it as a block argument: 258 | 259 | ```ruby 260 | client = AFMotion::SessionClient.build("https://alpha-api.app.net/") do |client| 261 | client.session_configuration :default 262 | 263 | client.header "Accept", @custom_header 264 | end 265 | ``` 266 | 267 | ## License 268 | 269 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion.svg?size=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion?ref=badge_large) 270 | --------------------------------------------------------------------------------