24/7 twenty-four seven

iOS/OS X application programing topics.

JavaScriptでiOSアプリが書けるライブラリJavaScriptBridgeを公開しました

kishikawakatsumi/JavaScriptBridge · GitHub


前にiOS 7から導入されたJavaScriptCore.frameworkを使ってUIKit標準のクラスを操作する話を書きました。

JavaScriptだけでiOSのUIを書いてみる - 24/7 twenty-four seven


JavaScriptCoreを使ってJavaScriptからObjective-Cのオブジェクトを操作するのは、あらかじめJSExportプロトコルで公開しておく必要があるなど、あまり実用的ではありませんでしたが、このライブラリを導入すうることで、そういった事前準備がすでに済んでいる状態で使いはじめることができます。


また、JavaScriptからObjective-Cのクラスを定義できるなどいくつかの拡張を加えてあり、UIViewControllerのサブクラスで画面を定義したり、デリゲートに応答するオブジェクトを作ったり、UIKitの作法にしたがってアプリケーションを記述することができるようになっています。


使い方を見てもらえればわかりますが、全部JavaScriptで書くことを強制されることはなく、部分的に使用することもできます。
むしろ、ピンポイントで一部の画面だけJavaScriptを使用して書きたい、という場合に力を発揮するのではないかと思います。

応用の方法としては、キャンペーンや期間限定の画面などを増減できるようにしたり、変更可能にしておくといった方法や、プロトタイピングで、再ビルドやアプリケーションを終了することなく、動的に修正を確認したりなどに向いているかと考えています。


まだバージョン0.0.1のベータ版ですが、バグ報告、プルリクエストをお待ちしています。

使い方

もっとも簡単な例は下記になります。

#import <JavaScriptBridge/JavaScriptBridge.h>
...

// Retrieve the prepared context
JSContext *context = [JSBScriptingSupport globalContext];

// Add framework support if needed.
// ('Foundation', 'UIKit', 'QuartzCore' enabled by default.)
[context addScriptingSupport:@"MapKit"];
[context addScriptingSupport:@"MessageUI"];

// Evaluate script
[context evaluateScript:
 @"var window = UIWindow.new();"
 @"window.frame = UIScreen.mainScreen().bounds;"
 @"window.backgroundColor = UIColor.whiteColor();"
 @"window.makeKeyAndVisible();"
];


JSBScriptingSupportから取得したJSContextオブジェクトは標準のUIKitやFoundationのほとんどのクラス群がすでにJSExportプロトコルに適合している状態になっています。

JSContext *context = [JSBScriptingSupport globalContext];


必要に応じてJSExportに適合させるクラスを追加します。
Frameworkごとに名前を指定することでそのFrameworkに属するクラス群がJSExportに適合されます。
例えば下記のようにMapKitを指定すると、MKMapViewなどがJavaScriptから扱えるようになります。

[context addScriptingSupport:@"MapKit"];


必要なことはこれだけです。あとはおもむろにJavaScriptでアプリケーションを書き始めることができます。

// Evaluate script
[context evaluateScript:
 @"var window = UIWindow.new();"
 @"window.frame = UIScreen.mainScreen().bounds;"
 @"window.backgroundColor = UIColor.whiteColor();"
 @"window.makeKeyAndVisible();"
];

文法など

変数宣言

型名を取り除きます。

UILabel *label;
var label;
プロパティ

プロパティはほぼObjective-Cと同様でドットを使います。
Objective-Cではメソッド呼び出しの形式でプロパティにアクセスすることができますが、メソッドによるアクセスは今のところサポートしていません。

UISlider *slider = [[UISlider alloc] initWithFrame:frame];
slider.backgroundColor = [UIColor clearColor];
slider.minimumValue = 0.0;
slider.maximumValue = 100.0;
slider.continuous = YES;
slider.value = 50.0;
var slider = UISlider.alloc().initWithFrame(frame);
slider.backgroundColor = UIColor.clearColor();
slider.minimumValue = 0.0;
slider.maximumValue = 100.0;
slider.continuous = true;
slider.value = 50.0;
メソッド呼び出し

Objective-Cのカッコを使ったメソッド呼び出しは、ドット記法に置き換えます。
パラメータを取るメソッドについては、コロンを取り除き、パラメータのラベルをキャメルケースで連結してメソッド名にします。
そのあと、順番にパラメータを並べます。

UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
var window = UIWindow.alloc().initWithFrame(UIScreen.mainScreen().bounds);
クラス定義

次のように、JSB.defineメソッドを使うとObjective-Cのクラスが作成されます。
第1引数でクラスの宣言、第2引数にハッシュでメソッド名と処理のfunctionオブジェクトを渡します。
これを使うことでUITableViewのデリゲートメソッドに応答したり、UIViewControllerのサブクラスとして画面を作成したりできます。

var MainViewController = JSB.define('MainViewController : UITableViewController <UITableviewDataSource, UITableviewDelegate>', // Declaration
// Instance Method Definitions
{
  viewDidLoad: function() {
    self.navigationItem.title = 'UICatalog';
  },
  tableViewNumberOfRowsInSection: function(tableView, section) {
    return self.menuList.length;
  },
  tableViewCellForRowAtIndexPath: function(tableView, indexPath) {
    var cell = UITableViewCell.alloc().initWithStyleReuseIdentifier(3, 'Cell');
    cell.accessoryType = 1;
    cell.textLabel.text = self.menuList[indexPath.row]['title'];
    cell.detailTextLabel.text = self.menuList[indexPath.row]['explanation'];

    return cell;
  },
  tableViewDidSelectRowAtIndexPath: function(tableView, indexPath) {
    var targetViewController = self.menuList[indexPath.row]['viewController'];
    self.navigationController.pushViewControllerAnimated(targetViewController, true);
  }
});
モジュール

Node.js風の簡単なモジュールシステムを提供しています。
JSB.require('/path/to/module.js') と書くと別のファイルのモジュールが利用可能になります。
モジュールとして公開するにはJSB.exportsにオブジェクトを代入します。

ファイルを分けて書けるのでちょっと大きめのアプリケーションでもJavaScriptだけでけっこう書けます。
↓ このサンプルを見るとよくわかります。
JavaScriptBridge/Examples/UICatalog/UICatalog/js at master · kishikawakatsumi/JavaScriptBridge · GitHub

var ButtonsViewController = JSB.require('buttonsViewController');
var ControlsViewController = JSB.require('controlsViewController');
var WebViewController = JSB.require('webViewController');
var MapViewController = JSB.require('mapViewController');

var MainViewController = JSB.define('MainViewController : UITableViewController', {
  viewDidLoad: function() {
    self.navigationItem.title = 'UICatalog';
    ...
});

JSB.exports = MainViewController;