SKIPのiPhone用ビューを作ってみたよ。

はじめに

冬休みの宿題でSKIPのiPhone対応してみると手をあげてはみたもののサボってしまったので、この三連休に慌てて作ってみたというお話。

まずは学習

ちょっと古い記事だけど以下を見て基本を学習
http://www.atmarkit.co.jp/fwcr/special/iphone/02.html
http://www.atmarkit.co.jp/fwcr/special/iphone/03.html
http://www.atmarkit.co.jp/fwcr/special/iphone/04.html
重要っぽい所を抜き出すと以下のような感じだった。

02.html
    Web標準に対応している
    未対応技術がある
    Webブラウザ判定には2種類ある
        UAでの判定
        Apple提供のWeb Kit 判定スクリプト(Detecting WebKit with JavaScript
    ダブルタップによるズーム
        img -> 画像の横幅一杯にズーム
        画像以外 -> その要素を包含するブロック要素の横幅一杯にズーム
    リソース制限
        HTML CSS JavaScriptなどは10Mbyteまで
        JavaScriptの実行時間は[5sec]まで!!!!!!
        JavaScriptのアロケーションは10Mbyteまで -> アロケーションって何だっけ
03.html
    ビューポートの設定
        ポートレート表示(縦表示)だと320x480(320x356)
        ランドスケープ表示(横表示)だと480x320
        つまり320x356がiPhone版Safariに最適化されたデザイン!!
        ただし、デフォルトで縮小表示(横幅を980px)として表示される…のか
            -> そこでビューポートメタタグですよ
                -> device-widthという便利な値が指定出来るよ(勝手に横幅320pxにしてくれる
04.html
    スタイルシート
        CSS3プロパティの先行サポート
            webkit-border-radius(角丸)
        文字の拡大縮小(自動アジャスト)
        トランジションとアニメーション
            js使わなくても色々な効果が使えるみたいだ
    iPhoneボタンとiPhoneリストパターン
        フォーム要素はiPhone独自のUI部品にアクセス可
        Web Apps Dev Center
            iPhoneらしい各種ボタン
            iPhoneらしいリスと表示
            などを実現するCSSを提供している。
                -> SDKでなくともネイティブっぽい見た目にできる。
    入力イベント
        ドラッグ
            ドラッグ中にonscrollが発生しない
            指を止めてドラッグを終了した時点でonscrollが発生
        タッチ&ホールド
            マウスイベントなし
        ダブルタップ
            マウスイベントなし
        タップ
            クリック不可要素では発生しない
            クリック可能要素ではmousemoveイベントが発生
                コンテンツが変化しない場合はmousedown, mouseup, clickが発生
            mousemoveはmouseover, mouseoutも発生させる
        2本指ドラッグ
            スクロール可能な要素の場合mousewheel
            それ以外の場所では1本指ドラッグと同様
    デバッグ
        iPhone版Safariには簡単なデバッグモードがある。
            -> 本体上でスクリプトエラーを確認可能
        デスクトップ版Safariの開発メニューで詳細な動作確認が可能
        MacがあればiPhoneシュミレータが使える…

jsの実行時間5秒までってのは要注意だなーと思った。

要件を考える。

ブレスト
    iPhoneSKIPで使いたい機能って何だろう?(自分が
        未読記事のチェック
        未読コメントのチェック
        コメントを加える
        上記ぐらいが出来れば十分だよなぁ。最初は読めるだけでいいや。
要件
    ログイン画面
        ログインするとマイページに飛ぶ
            yahooのように機能ごとのアイコンが並んだ画面がいいかなぁ?
            機能が階層化された一覧画面ぽいのがいいかなぁ?
            何を表示して何を表示しないのかが重要
        最初は面倒なんでログインすると未読記事画面でいいや。
    未読記事画面
        320x356の画面である
        PCサイトの未読記事のボックスのみが表示されるイメージ
        右側に [>] が表示されており、クリックすると記事一覧画面に遷移する。
    記事一覧画面
        タイトル、本文をメールアプリのように色や文字の大きさで表現する。
        右側に [>] が表示されており、クリックすると記事画面に遷移する。
    記事画面

ライブラリの調査

軽くググる
http://d.hatena.ne.jp/fujisan3776/20080902/1220355322
http://d.hatena.ne.jp/fujisan3776/20080903/1220452741
こんな記事を見つけたので、jpmobileとiUIを導入することに決定。

準備

GitHubでfork済みの自分のskipリポジトリをcloneしてくる。
git clone git@github.com:maedana/skip.git maedana_skip

iPhone開発用のブランチを作ってチェックアウト

cd maedana_skip
git checkout -b for_iphone master 

jpmobileは現時点(09/01/12)でiPhoneにはまだ対応してない(議論はしてて話は進んでいる模様)ので
上述の記事にしたがってjpmobileにちょっとコードを追加したものを導入。
追加したコードの差分は以下のとおり。
http://github.com/maedana/jpmobile/commit/876bcc64f9c06298aa39e0ee8af5d83f20034cfe

iUIは以下をDLして導入。
http://iui.googlecode.com/files/iui-0.13.tar.gz

実装

iUI同梱のサンプルがわかりやすいので基本的に詰まる部分はなかったが2ヶ所はまった。

既存のRailsのcontrollerを流用する際にハマる

LunchController#list から #detailへ遷移する際、application_mobile_iphone.rbが読み込まれると、HTMLのヘッダなど余分なものがくっついてしまい、レイアウトがおかしくなります。そのため、#detailのrender時、iPhoneからのアクセスの時はlayout => false にして、application_mobile_iphone.rbが読み込まれないようにする必要があります。

http://d.hatena.ne.jp/fujisan3776/20080902/1220355322

とあるように、既存のRailsのcontrollerを流用する場合はlayoutをfalseになるようにしてあげないとダメ。

既存のviewを流用する際のAjaxが動かなくてハマる(未解決)

iUIでは外部ページ(リンク)を取得する際にAjaxで取得するんだけど、その際にコールバックが外部から指定出来ない?みたい。
コールバックが指定できればその中で既存のAjax処理を呼ぶようにオーバーライドしてあげればいいと思うんだけど。
具体的には以下の部分
http://code.google.com/p/iui/source/browse/trunk/iui/iui.js#65

    showPageByHref: function(href, args, method, replace, cb)
    {
        var req = new XMLHttpRequest();
        req.onerror = function()
        {
            if (cb)
                cb(false);
        };
        
        req.onreadystatechange = function()
        {
            if (req.readyState == 4)
            {
                if (replace)
                    replaceElementWithSource(replace, req.responseText);
                else
                {
                    var frag = document.createElement("div");
                    frag.innerHTML = req.responseText;
                    iui.insertPages(frag.childNodes);
                }
                if (cb)
                    setTimeout(cb, 1000, true);
            }
        };

showPageByHrefの第5引数のcbでコールバック関数(処理成功時はcb(true), 失敗時はcb(false)を呼ぶようになっているが…

http://code.google.com/p/iui/source/browse/trunk/iui/iui.js#176

addEventListener("click", function(event)
{
    var link = findParent(event.target, "a");
    if (link)
    {
        function unselect() { link.removeAttribute("selected"); }
        
        if (link.href && link.hash && link.hash != "#")
        {
            link.setAttribute("selected", "true");
            iui.showPage($(link.hash.substr(1)));
            setTimeout(unselect, 500);
        }
        else if (link == $("backButton"))
            history.back();
        else if (link.getAttribute("type") == "submit")
            submitForm(findParent(link, "form"));
        else if (link.getAttribute("type") == "cancel")
            cancelDialog(findParent(link, "form"));
        else if (link.target == "_replace")
        {
            link.setAttribute("selected", "progress");
            iui.showPageByHref(link.href, null, null, link, unselect);
        }
        else if (iui.isNativeUrl(link.href))
        {
            return;
        }
        else if (!link.target)
        {
            link.setAttribute("selected", "progress");
            iui.showPageByHref(link.href, null, null, null, unselect);
        }
        else
            return;

showPagebyHrefを呼ぶ際にコールバック関数としてunselect関数を固定で渡してるんだよね。
コールバック関数に指定する関数を外部からオーバーライドできればいいと思うんだけどこのままじゃ出来ない…よね?(jsは苦手なので自信ない…)
なので、ちょっと改造していっそのことjQueryプラグイン化してしまおうと思ってる。(まだやってない。)

そんなこんなで以下のような感じのものが出来た

ログイン画面

メニュー画面(マイページ)

記事一覧画面

記事画面(コメントを書いたり、コメントに返信したり、コメントを並び替えたりといった処理をjQueryで行っているが今は動かない。(

ソース

出来上がったソースは以下においてある。
http://github.com/maedana/skip/tree/for_iphone

本体に取り込めるかどうかは…チームの意見を聞いてみないとわからない。

気がついたこととか

  • SKIP本体のmypage周辺のcontrollerの処理のいまいちな点に引きずられた実装になっている
    • mypage#indexがリクエストパラメタによっていろんな仕事やりすぎな点
    • user#blogとgroup/bbsが似すぎている点(モジュールで共通処理を切り出したほうがいい)
  • iUIが外部ページをAjaxで読み込む際にコールバック関数を外部から指定できないため、コメント関連のAjaxを効かせられない。
  • iphonecssを書きたいが、SKIP本体のhtml及びcssの整理を先にやったほうがいい。
    • style属性への直接指定をなくす
    • idやclass名をわかりやすいシンプルな名前にする。
    • divの使い方がひどい。なんでもdivにすればいいってものじゃないよなぁ。
  • 手元の環境だとjpmobileのspecが動かせなかった。原因はつかめなかった。

まとめ

  • 思っていたより簡単だった。iUI使えば割とさくっとiPhone対応サイトは作れると思う。
  • iUIが複数のui属性やdiv属性でページを切り替えたりすることが多いのでRailsよりsinatraと相性がよさそう。
  • ついに休みの日に昼間やってるアプリのコード書くようになったかー(OSSだから出来ることだよなぁ)
  • 久しぶりにまともなエントリ書いたなー