Rakontanto de ludo

Peace cannot be kept by force. It can only be achieved by understanding.

iPhone で Ruby がコーディング & 実行できるアプリを作る!(3)

こんばんわ。毎日暑い日が続きます。今日はプログラミングの話を続けます。

前回のラブライブ!

前回の話は以下の記事を参照してください。前回の記事にはその前の記事の紹介がされていますので,辿っていけば最初から読むことができます。過去記事のリンクは便利ですね。

tomohiko37-i.hatenablog.jp

前回までで Objective-C のプログラムの中で文字列として定義した Ruby のコードを mruby のライブラリに渡して実行し,標準出力に出力された内容をプログラム内の NSString 型で扱うところまでを実装しました。今回は実際の iPhone アプリの画面部分を作成します。

UI を作る

Single View Application の形で Xcode のプロジェクトを作成しておりますので,そこに用意されている Main.storyboad に画面を実装します。

f:id:tomohiko37_i:20150803221912p:plain

Interface Builder を使用してこのような形の画面を作りました。上部のテキストフィールドに Ruby のコードを書きます。その上で Run ボタンを押下すると下部のラベルに結果を表示するという簡単な UI にしています。デザイン的にはセンスがないので触れないでください。

ViewController.h の実装

画面のコンポーネントとプログラムを紐づける作業は多くの書籍で紹介されていますので割愛します。ViewController.h はこのようになります。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextView *srcCodeTextField;
- (IBAction)runExec:(UIButton *)sender;
@property (weak, nonatomic) IBOutlet UILabel *consoleLabel;
- (IBAction)onSingleTap:(UITapGestureRecognizer *)sender;

@end

ざっと説明すると,srcCodeTextField は画面上部の入力フィールド。runExec: メソッドは Run ボタンを押下した時のアクション。consoleLabel は結果を表示するコンソールのラベル。onSingleTap: メソッドはキーボードが表示された時に,画面の任意の場所をタップしてキーボードを閉じるためのアクションとなります。

ViewController.m

本体です。ここはほぼ前回実装した viewDidLoad: メソッドの内容です。

#import "ViewController.h"
#include "mruby/mruby.h"
#include "mruby/mruby/proc.h"
#include "mruby/mruby/dump.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // コンソールラベルの表示位置の調整
    self.consoleLabel.baselineAdjustment = UIBaselineAdjustmentNone;    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

//
// run ボタンがタップされたときに動く処理.
//
- (IBAction)runExec:(UIButton *)sender {
    
    NSString *tmpPath = NSTemporaryDirectory();
    NSString *stdoutPath = [tmpPath stringByAppendingString:@"/result.txt"];
    FILE* fp_out = freopen([stdoutPath UTF8String], "w", stdout);
    
    // 実行する Ruby のコード
    const char* rubyCode = [self.srcCodeTextField.text UTF8String];
    
    // mrb_state の初期化
    mrb_state* mrb = mrb_open();
    
    // コードの実行
    mrb_load_string(mrb, rubyCode);
    
    // mrb_state のクローズ
    mrb_close(mrb);
    
    fclose(fp_out);
    
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:stdoutPath];
    if (!fileHandle) {
        NSLog(@"ファイルがありません.");
        return;
    }
    
    // ファイルの末尾まで読み込む
    NSData *data = [fileHandle readDataToEndOfFile];
    NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    self.consoleLabel.text = str;
}

//
// テキストフィールドやボタン以外の場所がタップされた時の処理
//
- (IBAction)onSingleTap:(UITapGestureRecognizer *)sender {
    
    // 表示されているキーボードを閉じる
    [self.view endEditing:YES];
}
@end

mruby の部分を前回説明済みなので,特に新しいことはないと思います。画面のテキストフィールドから文字列を取得する方法や,ラベルに文字列をセットする仕組みは多くの書籍で紹介されている通りのものです。

実機で動かす

シミュレータで動かすのも良いですが,ライセンスを持っているので実機で動かしたいと思います。

f:id:tomohiko37_i:20150803223435p:plain

こんな感じで動きます。iPhone 上で Ruby のコーディングと実行ができるようになりました。

今後

さて,目的は基本的に達成した状態ではありますが,せっかくなのでこのまま AppStore でリリースできるレベルのアプリに昇華させて多くの方に使っていただくというのが清く正しいデベロッパー精神じゃないかと思う次第でもありますので,入力したコードの保存や読み込み,出力結果の表示の改良,全体的なデザインなど改造を続けていきたいと思います。引き続き記事で紹介しながら進めます。