Hatena::ブログ(Diary)

naoyaのはてなダイアリー

January 22, 2013

Pixate

数日前に Pixate という iOS 向けミドルウェアがリリースされました。なんとiOSアプリの見た目を css で書けるという、全ウェブ開発者感涙のライブラリ。こりゃすげえ。ただし無料というわけにはいかず、18,000円くらいでこざいます。

2月9日 追記

トライアル版と、個人利用のための無料版が出たようです。

f:id:naoya:20130122214713p:image

RubyMotion の teacupのように css チックな DSL で書ける、というものはありましたが Pixate はその辺とは次元が違ってて、普通に css ファイルに css を書くことができる。

button.blue {
    position: 60, 100;
    size: 200, 40;
    border-radius: 7px;
    font-family: 'Courier New'; 
    font-size: 18pt;
    font-weight: bold;
    border-width: 1px;
    border-color: #518cc6;
    background-color: linear-gradient(#3072b3, #599bdc);
    color: #fff;
}

こんな感じで css ファイルに css を書くと

f:id:naoya:20130122202722p:image:w300

こんな感じにボタンが仕上がる。Oh、なんとワンダフル。css のシンタックスは CSS3 に独自拡張を加えたものになってます。

気になる速度のほうですが

The Pixate Engine styles native controls using generated bitmaps, allowing your applications to take full advantage of hardware acceleration. In contrast to most asset workflows, the Pixate Engine renders and caches bitmap content dynamically as needed at runtime, giving your designs the utmost in flexibility and performance.

Pixate

というのが謳い文句のようで実際ちょっと触った感じでも、特にネガティブな影響は感じない。

これはずいぶんとまたワクテカなライブラリですなー。ついに iOS デフォルト丸出しのデザインから脱却できる・・・! ただし、css が書けるからといって美しいアプリが作れるかどうかはまた別問題です。

RubyMotion で Pixate

Pixate はもちろん Objective-C な開発で普通に使えますが、RubyMotion からでも使えます。

Using Pixate with RubyMotion is really easy. A Pixate gem for RubyMotion is available on the RubyMotion-Pixate project on GitHub.

Getting Started

てなわけで、@watson1978 さんによる RubyMotion バインディングが用意されています。watson さんマジカッキィよ〜。

基本的にはドキュメントにある通り、motion-pixate を require して Rakefile にちょちょいと追記してやるだけ。あとは resources ディレクトリの default.css に書けば ok です。

コンポーネントに ID や class を割り当てるには

view.styleId = 'main'
view.styleClass = 'blue'

とか、こんな感じでいけます。

css の変更を確認しやすく

さて「これはすごい」というのでいざ css を編集しはじめると、実際の見た目がどうなるかを確認するのに頻繁に rake でビルドする感じになってしまいます。さすがに編集の毎に何秒も待たされるのはつらい。

ビルドなしで変更を反映する方法を模索しましょう。

更新反映用のボタンを用意する

先のスクリーンショットのように、とりあえずナビゲーションバーにでも変更内容反映のトリガになるボタンを置いてそれを押したら反映、という形にしてみました。

この辺は色々やりようがあると思われますがひとまずシンプルに。

class MainViewController < UIViewController
  def viewDidLoad
    super
    navigationItem.title = 'Pixate'
    
    # 更新反映用のボタン
    navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
      UIBarButtonSystemItemRefresh,
      target:self,
      action:'changeStyleSheet'
    )
    
    view << UIButton.rounded_rect.tap do |button|
      button.setTitle('Hello', forState:UIControlStateNormal)
      button.frame = [[10, 10], [200, 44]]
      button.styleClass = 'blue'
    end
  end

  def changeStyleSheet
    # ここでスタイルへの更新を反映させる
  end
end

こんな感じです。sugarcube を使っています。

あとはボタンを押した際に呼ばれる changeStyleSheet メソッドの中身です。RubyMotion 拡張的には

self.style('cssの文字列')

で画面が更新されるようになってます。(中では PXStylesheet.applyStylesheets が呼ばれている。) こいつにどうにかして css の中身を取得して style() に渡してやる。

その1 : bundle ファイルから default.css を取得

一つ目は

def changeStyleSheet
  # バンドルファイルのパスを出力
  puts 'default.css'.resource

  url = 'default.css'.resource_url 
  style(NSString.stringWithContentsOfURL(url))
end

として、バンドルファイルの default.css の中身を展開してやる方法。resourceresource_url は sugarcube による NSBundle のラッパです。

バンドルファイルのパスをわざわざ出力しているのはなぜか、というところが若干わかりにくいと思います。

default.css はビルド時にソースコードのあった resources/default.css から iOS 側のローカルにコピーされます。ソースコード側の resources/default.css を編集しても、そもそもそのファイルを iOS は使っていないので、変更を反映させることはできません。

そこで、puts 'default.css'.resource でバンドルファイルのパスを出力してやりつつ、それを読み込むのが上のコードです。出力されるパスは OSX から iOS のローカルファイルにつながる特別なディレクトリです。そのパスの方の default.css を編集してからボタンを押すと変更が反映される・・・という仕組み。

お手軽ですが、バージョン管理しているソースコード側の css ではなくて、iOS のローカルにコピーされたほうをいじるというのがいまいちですね。シミュレータを閉じるとパスが変わってしまってファイルを再度開かなければならないところもいまいちです。ちょっとデバッグしたいとか、そういう時用の手段ですかね。

なお

PXStylesheet.currentApplicationStylesheet.monitorChanges = true

というのを viewDidLoad かどこかで呼んでおくと、実はボタンを押さなくてもcssファイル保存時にリアルタイムに変更が反映される・・・というのが ここ で紹介されてました。

その2 : リモートに css を置き、それを読み込む

css をウェブサーバでホストしてやってネットワーク越しにそれを取ってきてやる、とするのが2つ目の方法。webrick そのほかでサーバを用意して、resources/default.css をホストしてやると 1 つめの方法の問題が解決できます。

def changeStyleSheet
  url = 'http://localhost:5000/css/styles.css'.nsurl
  style(NSString.stringWithContentsOfURL(url))
end

とかこんな感じで HTTP の URL に差し替える。あとはサーバーを用意。@watson1978 さんが https://gist.github.com/4573005 でやってた方法で、webrick の実装はそちらに。

これはなかなかうまい方法でした。

css を LESS で書きたい! ⇒ それPlackで

こうして css を編集してビルド要らずで快適!ですが、今時 css は sass や LESS で書きたいよねーということで。自分は sass は使ったことがないので、LESS で書きたい。

先の webrick をいじって LESS パーサーで asset pipeline 的なことをするでもいいのですがここは慣れた perl で plackup といきましょう。

# app.psgi
use Project::Libs;
use Plack::Builder;
use Plack::App::File;

builder {
    mount '/css' => builder {
        enable 'File::Less';
        Plack::App::File->new(root => './resources');
    }
};

github にあった plack-middleware-file-less を使いました。

Makefile.PL に

use inc::Module::Install;
name 'PixateExample';
version '0.1';

requires 'Plack';
requires 'CSS::LESSp';
requires 'Project::Libs';

WriteAll;

と必要なモジュールを書いて

% carton install

でモジュールをインストール。github のモジュールは

git submodule add git@github.com:franckcuny/plack-middleware-file-less.git modules/plack-middleware-file-less

で submodule 化。パスは Project::Libs が良い感じに通してくれます。

% carton exec -- plackup

で /css/styles.css で less が処理された css が出力されますよ、と。すばらしい。

ボタンを押すのが面倒

ここまで来ると、わざわざボタンを押さなくてもファイル変更したら反映するようにしたいよねーと思い socket.io でサーバ側から push するようにしてみましたが、cocoapods の socket.IO パッケージがビルドできずじまいでうまくいってません。

ここは明日以降またトライしてみます。

まとめ

  • Pixate 熱いよ! でも高いよ!
  • RubyMotion でも使えるよ
  • Quick Hack で変更の確認が快適になるよ
  • Pixate のステマではありません!
Connection: close