mattak's blog

気の向くままに書く

RubyMotionことはじめ

RubyMotion

最近おいらの周りでrubymotion++なひとが多く、おいらも始めました。 とりあえず勢いで買っちまったのできちんと勉強して使えるようにする。

なんぞ?

いとうなおやさんのスライドわかりやすい。。

https://speakerdeck.com/naoya/shi-jian-rubymotion

チュートリアル

とりあえず、下を読む

http://rubymotion-tutorial.com/

1 hello motion

プロジェクト作成

motion create HelloMotion
  • $: はライブラリのロードパスの配列
  • Rakefileの.setupにプロジェクトの設定を書く
  • rake configでプロジェクト全体の設定を見る
  • .nameにプロジェクト名
  • require 'motion/project' でrubymotionのライブラリをロード
  • ./app/app_delegate.rb がAppDelegateクラス
  • [obj makeBoxWithOrigin:origin andSize: size] => obj.makeBox(origin, andSize: size) でAppleAPIに互換

アラートダイアログを出す

alert = UIAlertView.new
alert.message = "Hello Motion!"
alert.show

2 views

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.makeKeyAndVisible

    @blue_view = UIView.alloc.initWithFrame(CGRectMake(10, 10, 100, 100))
    @blue_view.backgroundColor = UIColor.blueColor
    @window.addSubview(@blue_view)

    @green_view = UIView.alloc.initWithFrame(CGRectMake(30, 30, 40, 40))
    @green_view.backgroundColor = UIColor.greenColor
    @window.addSubview(@green_view)

    @red_view = UIView.alloc.initWithFrame(CGRectMake(30, 30, 40, 40))
    @red_view.backgroundColor = UIColor.redColor
    @blue_view.addSubview(@red_view)

    true
  end
end

インタプリタで変更する

$ rake
(main)> delegate = UIApplication.sharedApplication.delegate
=> #<AppDelegate:0x950bb90 @window=#<UIWindow:0xb006be0> @blue_view=#<UIView:0x950d8f0> @green_view=#<UIView:0x950e2f0> @red_view=#<UIView:0x950e480>>
(main)> blue_view = delegate.instance_variable_get('@blue_view')
=> #<UIView:0x950d8f0>
(main)> blue_view.subviews.count
=> 1
(main)> red_view = blue_view.subviews[0]
=> #<UIView:0x950e480>
(main)> red_view.removeFromSuperview
=> #<UIView:0x950e480>
(main)> blue_view.subviews.count
=> 0

動的にblue_viewを削除できた。

3 controller

コントローラを作ってラベルを追加

class TapController < UIViewController
  def viewDidLoad
    super

    self.view.backgroundColor = UIColor.whiteColor

    @label = UILabel.alloc.initWithFrame(CGRectZero)
    @label.text = "Taps"
    @label.sizeToFit
    @label.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)
    self.view.addSubview @label
  end
end

delegate側も変更

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.makeKeyAndVisible

    @window.rootViewController = TapController.alloc.initWithNibName(nil, bundle: nil)

    true
  end
end

4 containers

UINavigationControllerを追加

@window.rootViewController = UINavigationController.alloc.initWithRootViewController(controller)

ナビゲーションアイテムを追加

right_button = UIBarButtonItem.alloc.initWithTitle("push", style: UIBarButtonItemStyleBordered, target: self, action: 'push')
self.navigationItem.rightBarButtonItem = right_button

ナビゲーションアイテムの追加処理

def push
  new_controller = TapController.alloc.initWithNibName(nil, bundle: nil)
  self.navigationController.pushViewController(new_controller, animated: true)
end

タイトルを変更

self.title = "Tap(#{ self.navigationController.viewControllers.count })"

...

タブメニュー(お気に入りアイコン)を追加

def initWithNibName(name, bundle: bundle)
  super
  self.tabBarItem = UITabBarItem.alloc.initWithTabBarSystemItem(UITabBarSystemItemFavorites, tag: 1)
  self
end

タブメニュー(背景: 紫)を追加

other_controller = UIViewController.alloc.initWithNibName(nil, bundle: nil)
other_controller.title = "Other"
other_controller.view.background = UIColor.purpleColor

tab_controller = UITabBarController.alloc.initWithNibName(nil, bundle: nil)
tab_controller.viewControllers = [nav_controller, other_controller]

5 tables

app_delegate.rbにテーブルコントローラを追加

alphabet_controller = AlphabetController.alloc.initWithNibName(nil, bundle: nil)
tab_controller.viewControllers = [alphabet_controller, nav_controller ]

app/controllers/AlphabetController.rbにテーブルコントローラを記述

class AlphabetController < UIViewController
  def viewDidLoad
    super

    self.title = "Alphabet"

    @table = UITableView.alloc.initWithFrame(self.view.bounds)

    self.view.addSubview @table

    @table.dataSource = self
    @table.delegate = self

    @data = ("A".."Z").to_a
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    @reuseIdentifier ||= "CELL_IDENTIFIER"

    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier) || begin
      UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier: @reuseIdentifier)
    end

    cell.textLabel.text = @data[indexPath.row]

    cell
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end

  def tableView(tableView, didSelectRowAtIndexPath: indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)

    alert = UIAlertView.alloc.init
    alert.message = "#{ @data[indexPath.row] } tapped!"
    alert.addButtonWithTitle "OK"
    alert.show
  end
end

6 animations

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.makeKeyAndVisible

    @points = [[0, 0], [50, 0], [0, 50], [50, 50]]
    @current_index = 0

    @view = UIView.alloc.initWithFrame [@points[@current_index], [100, 100]]
    @view.backgroundColor = UIColor.blueColor

    @window.addSubview(@view)

    animate_to_next_point

    true
  end

  def animate_to_next_point
    @current_index += 1

    @current_index = @current_index % @points.count

    UIView.animateWithDuration(
      delay: 2,
      options: UIViewAnimationOptionCurveLinear,
      animations: lambda {
        @view.frame = [@points[@current_index], [100, 100]]
      },
      completion: lambda {|finished| 
        self.animate_to_next_point
    })
  end
end

7 models

モデルをつくる

class User
  attr_accessor :id
  attr_accessor :name
  attr_accessor :email
end

変数を監視する

class AppDelegate
  include BW::KVO

  attr_accessor :user

  def application(application, didFinishLaunchingWithOptions:launchOptions)

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
    @window.makeKeyAndVisible

    @name_label = UILabel.alloc.initWithFrame([[0,0], [100,30]])
    @window.addSubview(@name_label)

    @email_label = UILabel.alloc.initWithFrame([[0, @name_label.frame.size.height + 10], @name_label.frame.size])
    @window.addSubview(@email_label)

    @id_label = UILabel.alloc.initWithFrame([[0, @email_label.frame.origin.y + @email_label.frame.size.height + 10], @name_label.frame.size])
    @window.addSubview(@id_label)

    # ------

    self.user = User.new

    ["name", "id", "email"].each do |prop|
      observe(self.user, prop) do |old_value, new_value|
          instance_variable_get("@#{prop}_label").text = new_value
      end
    end

    true
  end
end

ビルドして、リアルタイムに変数代入

$ rake
(main)> delegate = UIApplication.sharedApplication.delegate
=> #<AppDelegate:0xb14b8f0 @window=#<UIWindow:0x9660d70> @name_label=#<UILabel:0x9459320> @email_label=#<UILabel:0x9459e80> @id_label=#<UILabel:0x945a520> @user=#<NSKVONotifying_User:0x945a8a0> @targets={#<NSKVONotifying_User:0x945a8a0>=>{"name"=>[#<Proc:0x945ab10>], "id"=>[#<Proc:0x945d230>], "email"=>[#<Proc:0x945d750>]}}>
(main)> user = delegate.user
=> #<NSKVONotifying_User:0x945a8a0>
(main)> user.email = "test@xample.com"
=> "test@xample.com"
(main)> user.name = "john"
=> "john"
(main)> user.id = "1001"
=> "1001"

8 testing

ボタン一つのプロジェクトを作る.

class ButtonController < UIViewController

  def viewDidLoad
    super

    @button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
    @button.setTitle("Test me title!", forState: UIControlStateNormal)
    @button.accessibilityLabel = "Test me!"
    @button.sizeToFit
    self.view.addSubview(@button)

    @button.addTarget(self, action:'tapped', forControlEvents:UIControlEventTouchUpInside)
  end

  def tapped
    p "I'm tapped!"
    @was_tapped = true
  end
end

app/app_delegate.rb

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
    @window.makeKeyAndVisible

    @view_controller = ButtonController.alloc.initWithNibName(nil, bundle: nil)
    @window.rootViewController = @view_controller

    true
  end
end

spec/main_spec.rb ボタンをタップしてテストする。

describe "Application 'Tests'" do
  before do
    @app = UIApplication.sharedApplication
  end

  it "has one window" do
    @app.windows.size.should == 1
  end
end

describe "button controller" do
  tests ButtonController

  it "changes instance variable when button is tapped" do
    tap 'Test me!'

    controller.intance_variable_get("@was_tapped").should == true
  end
end

9 http

bubble-wrapでhttpリクエストが簡単に利用できる

BubbleWarp::Http.get("http://www.google.com") do |response|
    p response.body.to_str
end

10 API Driven Example

長いのでぱす。