JSQMessagesViewControllerに画像を表示する

PictRuby 0.6 に向けてやりたいことを整理。

  • チャット画面で画像を表示できるように
  • 正規表現
  • irbでローカル変数を使えるようにしたい

どれも実現可能か分からないのでまずは調査から。今日は画像の表示。ググったらJSQPhotoMediaItemを使えば良さげ。

UIImage* image = [UIImage imageNamed:@"sample.jpg"];
JSQPhotoMediaItem* photo = [[JSQPhotoMediaItem alloc] initWithImage:image];

return [JSQMessage messageWithSenderId:@"ruby"
                           displayName:@"Ruby"
                                 media:photo];

あっさり表示できた、素晴らしい。

が、残念ながら画像のコピーができない。クリック時の拡大もない。Issueを見ると画像のコピーはできそうな雰囲気なのだが・・。ここまでやってみて画像を快適に扱えるようにするには幾つかの機能整備が必要なことに気づく。時間がかかりそうだ。

今回は見送りするのが良いかもな。次は正規表現を組み込めるか調査する。

iOSアプリのアイコンと起動画面の更新をRakefileを使って自動化する

昨日からの続きで、PictRubyのレポジトリの中に組み込んで1コマンドで更新できるようにした。

$ cd ~/Document/PictRuby
$ cd Tools/Icon
$ rake
ruby s9icongen.rb icon.png
    29x29: icons/Icon-29.png
    58x58: icons/Icon-29@2x.png
    87x87: icons/Icon-29@3x.png
    40x40: icons/Icon-40.png
    80x80: icons/Icon-40@2x.png
  120x120: icons/Icon-40@3x.png
  120x120: icons/Icon-60@2x.png
  180x180: icons/Icon-60@3x.png
    76x76: icons/Icon-76.png
  152x152: icons/Icon-76@2x.png
  120x120: icons/Icon-120.png
  512x512: icons/iTunesArtwork
1024x1024: icons/iTunesArtwork@2x
ruby s9splashgen.rb splash.png universal
  320x480(2x) ->   640x960: screens/Default@2x~iphone.png
  320x568(2x) ->  640x1136: screens/Default-568h@2x~iphone.png
  375x667(2x) ->  750x1334: screens/Default-667h@2x~iphone.png
  414x736(3x) -> 1242x2208: screens/Default-736h@3x~iphone.png
 768x1024(1x) ->  768x1024: screens/Default-Portrait~ipad.png
 1024x768(1x) ->  1024x768: screens/Default-Landscape~ipad.png
 768x1024(2x) -> 1536x2048: screens/Default-Portrait@2x~ipad.png
 1024x768(2x) -> 2048x1536: screens/Default-Landscape@2x~ipad.png

これで気軽にアイコンと起動画面を調整できるようになった。PictRuby/Tools/Icon以下をコピーすれば他の環境でも使えると思います。

iOSアプリのアイコンと起動画面の更新をRakefileを使って自動化する

昨日からの続きで、PictRubyのレポジトリの中に組み込んで1コマンドで更新できるようにした。

$ cd ~/Document/PictRuby
$ cd Tools/Icon
$ rake
ruby s9icongen.rb icon.png
    29x29: icons/Icon-29.png
    58x58: icons/Icon-29@2x.png
    87x87: icons/Icon-29@3x.png
    40x40: icons/Icon-40.png
    80x80: icons/Icon-40@2x.png
  120x120: icons/Icon-40@3x.png
  120x120: icons/Icon-60@2x.png
  180x180: icons/Icon-60@3x.png
    76x76: icons/Icon-76.png
  152x152: icons/Icon-76@2x.png
  120x120: icons/Icon-120.png
  512x512: icons/iTunesArtwork
1024x1024: icons/iTunesArtwork@2x
ruby s9splashgen.rb splash.png universal
  320x480(2x) ->   640x960: screens/Default@2x~iphone.png
  320x568(2x) ->  640x1136: screens/Default-568h@2x~iphone.png
  375x667(2x) ->  750x1334: screens/Default-667h@2x~iphone.png
  414x736(3x) -> 1242x2208: screens/Default-736h@3x~iphone.png
 768x1024(1x) ->  768x1024: screens/Default-Portrait~ipad.png
 1024x768(1x) ->  1024x768: screens/Default-Landscape~ipad.png
 768x1024(2x) -> 1536x2048: screens/Default-Portrait@2x~ipad.png
 1024x768(2x) -> 2048x1536: screens/Default-Landscape@2x~ipad.png

これで気軽にアイコンと起動画面を調整できるようになった。PictRuby/Tools/Icon以下をコピーすれば他の環境でも使えると思います。

iosでLINE風味にIRBできるようにする

Qiita から引っ越し。

次のバージョンのPictRubyでチャット風インターフェースを作れるようにしたのでirbを実装した。

pictruby-irb-01.png

コードは以下のような感じに。エラーハンドリングはさすがにObjective-C側に書かないとダメかなと思ったいたのだけど、例外キャッチするだけでうまくいった。Rubyの表現力はやはり高い。

# # 10_irb
#
# ## Description
# Interactive Ruby Shell is a REPL.

def chat(input)
  begin
    eval(input)
  rescue Exception => e
    e.message
  end
end

とりあえずうまくいったのだけど、なぜかローカル変数を定義できないので他のirbの実装を眺めてみた。

pictruby-irb-02.png

web-irb

http://joshnuss.github.io/mruby-web-irb/

>> a = [1, 2, 3]
=>[1, 2, 3]
=>NoMethodError: undefined method 'a' for main

同じ現象が起きている!

>> @a = [1, 2, 3]
>> @a
=>[1, 2, 3]

メンバ変数として定義するとちゃんと保持される。

mirbiosでLINE風味にIRBできるようにする

次のバージョンのPictRubyでチャット風インターフェースを作れるようにしたのでirbを実装した。

pictruby-irb-01.png

コードは以下のような感じに。エラーハンドリングはさすがにObjective-C側に書かないとダメかなと思ったいたのだけど、例外キャッチするだけでうまくいった。Rubyの表現力はやはり高い。

# # 10_irb
#
# ## Description
# Interactive Ruby Shell is a REPL.

def chat(input)
  begin
    eval(input)
  rescue Exception => e
    e.message
  end
end

とりあえずうまくいったのだけど、なぜかローカル変数を定義できないので他のirbの実装を眺めてみた。

pictruby-irb-02.png

web-irb

http://joshnuss.github.io/mruby-web-irb/

>> a = [1, 2, 3]
=>[1, 2, 3]
=>NoMethodError: undefined method 'a' for main

同じ現象が起きている!

>> @a = [1, 2, 3]
>> @a
=>[1, 2, 3]

メンバ変数として定義するとちゃんと保持される。

mirb

mruby標準添付のやつ

$ git clone https://github.com/mruby/mruby.git
$ rake
$ ./bin/mirb 
mirb - Embeddable Interactive Ruby Shell

> a = 1
a = 1
 => 1
> a
a
 => 1

mirbはちゃんとローカル変数が使えるようだ。何か実際に違いがあるのかも。(そのうちソース読もう)

まとめ

ともあれweb-irbは同じだったのでちょっと安心した(なにが)。メンバ変数を使うという逃げ道も見つかったのでとりあえず実装優先で進める。

mruby標準添付のやつ

$ git clone https://github.com/mruby/mruby.git
$ rake
$ ./bin/mirb 
mirb - Embeddable Interactive Ruby Shell

> a = 1
a = 1
 => 1
> a
a
 => 1

mirbはちゃんとローカル変数が使えるようだ。何か実際に違いがあるのかも。(そのうちソース読もう)

まとめ

ともあれweb-irbは同じだったのでちょっと安心した(なにが)。メンバ変数を使うという逃げ道も見つかったのでとりあえず実装優先で進める。

PictRuby 0.5 リリース - チャットボット、eval、irb

PictRuby 0.5 をリリースしました。 合わせて起動画面とアイコンをリニューアルしました。

f:id:tuto0621:20160331012430p:plain:w346

  • チャットボットが書けるように
  • evalが使えるように
  • irbが使えるように (サンプルの10_irb.rb)

チャットボットが書けるように

Chatという名前のクラスを定義すると対話型のインターフェースを構築することができるようになりました。以下はシンプルなチャットボットの例です。

f:id:tuto0621:20160331012449g:plain

class Chat
  def initialize
    @num = 0
  end

  def welcome
    "Hello, World!"
  end

  def call(input)
    case input
    when "name", "Name"
      "My name is Rubo"
    when "Rubo"
      "Yes, my load."
    when "help", "Help"
      <<EOF
Echo your message

name: teach my name
halt: stop this program
EOF
    when "halt", "Halt"
      aaaa
    else
      @num += 1
      "#{@num}: #{input}"
    end
  end
end

一番大切なのはcall(input)メソッドです。引数のinputにはユーザーが入力したテキストが渡されます。戻り値をレスポンスとしてbotがしゃべります。(inputをそのまま返すとエコーボットになります)

welcomeというメソッド(optional)が定義されているとbot起動時に発言してくれます。

内部状態はChatクラスのインスタンス変数として保持してください。

evalが使えるように

mruby-evalを組み込んだのでevalinstance_evalが使えるようになりました。

irbが使えるように

サンプルの10_irb.rbで待望のirbが使えるようになりました。これはチャットボットとevalの実践的なサンプルにもなっています。

f:id:tuto0621:20160331012542p:plain:w346

# # 10_irb
#
# ## Description
# Interactive Ruby Shell (REPL).

class Chat
  def welcome
    <<EOS
irb - Interactive Ruby Shell
EOS
  end

  def call(input)
    begin
      eval(input)
    rescue Exception => e
      e.message
    end
  end
end

過去の記事

mrubyであるクラスに特定の名前のメソッドが存在しているか調べる方法

次のPictRubyで以下のような対話型のプログラムが簡単に書けるようになる。

f:id:tuto0621:20160315001321j:plain

スクリプトはこんな感じ。(書き方は模索中、そのうち変わるかも)

def chat(input)
  @num ||= 0

  case input
  when "name", "Name"
    "My name is Rubo"
  when "Rubo"
    "Yes, my load."
  when "help", "Help"
    <<EOF
Echo your message

name: teach my name
halt: stop this program
EOF
  when "halt", "Halt"
    aaaa
  else
    @num += 1
    "#{@num}: #{input}"
  end
end

今までのプログラムと同居するために、スクリプトにchatとmainどちらの関数が定義されているかを調べて、生成するViewControllerを切り替える必要が出てきた。

いろいろと試行錯誤の結果以下のようになった。

+ (id) NewWithScriptName:(NSString*)scriptPath
{
    mrb_state* mrb = [self InitMrb:scriptPath];

    // ScriptController or ChatViewController
    mrb_sym mid = mrb_intern_cstr(mrb, "chat");
    struct RProc* m = mrb_method_search_vm(mrb, &mrb->object_class, mid);

    if (m) {
        return [[ChatViewController alloc] init:scriptPath mrb:mrb];
    } else {
        return [[ScriptController alloc] init:scriptPath mrb:mrb];
    }
}

mrb_intern_cstrmrb_method_search_vmの組み合わせで調べられる(&classなことに注意)。最初、

struct RProc* m = mrb_method_search_vm(mrb, &mrb->kernel_class, mid);

ってやっても全然見つからなくて、そうかグローバル関数(本当はそんなものRubyにはないのだろうけど)はKernelじゃなくてObjectに所属するのか、ってなった。mrb_funcallの時は中でうまくmessod_missingしてくれているのかな?

PictRubyでGUIプログラミングの参考に Ruby Shoes を動かす

PictRubyのGUIはこんな感じに書きたいと考えている。

app do
  button "Tap Me" do
    Popup.msg "Hello!"
  end
end

上のプログラムを実行すると"Tap Me"というボタンが表示され、タップすると"Hello!"というメッセージがポップアップする。 appのブロック内にGUIの初期化処理を書く。コードを読むとなんとなく何が起こるか分かるのではないだろうか?

上のようなコードをなんとなく見たことあるなら、それはRuby Shoesです。Ruby Shoesのスーパーシンプルな記述方法は(入力環境が貧弱なために)1文字でも冗長なコードを書きたくないモバイルプログラミングにぴったりだと感じている。

良い機会なので実際に Ruby Shoes をインストールして動かしたり、コードを読んだりしてPictRubyの参考にしたいのだが、悲しいことにOSXでは最新の El Capitan で動かない・・。仕方がないので Parallel Desktop 上の Windows 7 で動かすことにする。

Downloadsから"Shoes 3.3.0 for Windows 7+ 32 bit"をダウンロード。ダブルクリックすると普通にインストーラが起動する。fc-cache.exeの実行に結構に時間がかかるがインストールが終了。

Tutorialを上から順に実行していく。スクリプトを実行するにはアイコンに.rbをドラッグ&ドロップすれば良い。

f:id:tuto0621:20160308232524j:plain

以下は上から順にチュートリアルを実行した時の履歴。

# Shoes.app {
#   image "http://spiralofhope.com/i/ruby-shoes--nks-kidnap.png"
# }

# Shoes.app {
#   para strong("Q."), " Are you beginning to grasp hold of Shoes?"
# }

# Shoes.app {
#   stack(margin: 6) {
#     title "A Question"
#     para strong("Q."), " Are you beginning to grasp hold of Shoes?"
#     para em(strong("A."), " Quit pestering me, I'm hacking here.")
#   }
# }

# Shoes.app {
#   @push = button "Push me"
#   @note = para "Nothing pushed so far"
# }

# Shoes.app {
#   @push = button "Push me"
#   @note = para "Nothing pushed so far"
#   @push.click {
#     @note.replace "Aha! Click!"
#   }
# }

# Shoes.app do
#   background "#F3F".."#F90"
#   title("Shoooes",
#         top:    60,
#         align:  "center",
#         font:   "Trebuchet MS",
#         stroke: white)
# end

# Shoes.app do
#   background "#EFC"
#   border("#BE8",
#          strokewidth: 6)

#   stack(margin: 12) do
#     para "Enter your name"
#     flow do
#       edit_line
#       button "OK"
#     end
#   end
# end

# Shoes.app do
#   @shape = star(points: 5)
#   motion do |left, top|
#     @shape.move left, top
#   end
# end

# Shoes.app do
#   @shoes = image(
#     "http://spiralofhope.com/i/ruby-shoes--shoes.png",
#     top:  100,
#     left: 100
#   )
#   animate do |i|
#     @shoes.top += (-20..20).rand
#     @shoes.left += (-20..20).rand
#   end
# end

Shoes.app do
  @poem = stack do
    para "My eyes have blinked again
    And I have just realized
    This upright world
    I have been in.
    My eyelids wipe
    My eyes hundreds of times
    Reseting and renovating
    The scenery."
  end
  para(
    link("Clear").click do      # linkコマンドでリンク貼れるのいいなぁ
      @poem.clear
    end
  )
end

# Shoes.app(width: 300, height: 400) do
#   fill rgb(0, 0.6, 0.9, 0.1)
#   stroke rgb(0, 0.6, 0.9)
#   strokewidth 0.25

#   100.times do
#     oval(left:   (-5..self.width).rand,
#          top:    (-5..self.height).rand,
#          radius: (25..50).rand)
#   end

#   button("Regenerate") do
#     100.times do
#       oval(left:   (-5..self.width).rand,
#            top:    (-5..self.height).rand,
#            radius: (25..50).rand)
#     end
#   end
# end

一通り動かすことができた。次はManualを読んだり、他のアプリを動かしたりしてみよう。