12月 | 2012 | HMDT Blog | ページ 2

カテゴリー : 2012年 12月

OS Xのお仕事は楽しみ


久しぶりに、OS X向けの開発のお仕事ができそう。嬉しいなったら、ランランラン。

やっぱりアレだよな!OS Xだと燃えるよな!iOSは、もちろん嫌いじゃないし、大好きだけど、なんつーか、画面が小さくて入力デバイスが限られているから(タッチインタフェース)、どうしてもシンプルになる、というと聞こえはいいけど、実際のところは低機能になるっていうか、どうしてもオモチャ感があるというか、App Store通すとなると好き勝手できないというか。なんか、言い訳が多い。

その点OS Xは、遠慮せずにできるってとこがいいね!CPUだってメモリだって、使いたい放題だし!思う存分高機能に振ってやっていいし!App Store通さなくとも配布できるし!あーこの、思いっきりブン回しても構わない、って感覚が嬉しいねぇ。

Amazonってやっぱりすごいね


いまさらながら、やっぱりAmazonってスゲーなー、と思った事。

nasneを買おうと思ったんですよ。もうすぐVitaにtorneが出るってんで、ようし、そろそろ用意しないとな、と思って。で、そうすると、うちの環境だと分波器を買わないといけないのかな。あと、たしか有線のLANもつながないといけないんだっけ。あー、そうなるとハブもいるのか。調べないといけないな、めんどくせーなー、と思ってたんですよ。

ま、とりあえずAmazonでnasne検索してみるか、と思って検索したら、「この商品を買った人はこんな商品も買っています」のところに、全部出てきてるじゃないすか。結局検索したのはnasneだけで、その他必要なものは、芋づる式にリンククリックするだけでそろっちゃった。これじゃあ、リアル店舗行かなくなるわな。

思った事。疑問に対する回答を得るには、実際に行動した人に聞くのがいちばん早い。「nasneを使うために何が必要なの?」よりも、「nasneを買った人は他に何を買ったの?」の方が、適切な質問なわけだ。

もう一つ。行動を記録するという事は、苦労が伴わないから、みんながやってくれる。ブログを書くのも、Facebookに書くのも、Twitterにつぶやくのも、文字を書くという苦労が伴うから、なかなかみんなやってくれないし、情報もたまらない。でも、買うという行動はそれ自体が目的だから、みんなやってくれる。そういうとこには情報が集まって、そこから強みが生まれる。

deprecatedには、こまめに対応しないといかん


UIImageを引き延ばして利用するために、昔からstretchableImageWithLeftCapWidth:topCapHeight:っていうメソッドがあった。これ、iOS 5.0でdeprecatedになって、resizableImageWithCapInsets:を使え、ってことになっている。

Deprecatedになったメソッドの嫌らしいところは、この宣告がなされたとしても、とりあえずは使える。ビルドは通るし、実行もできる。だけど、バージョンアップを繰り返してある日気付くと、あれ?なんか動作がおかしい。調べてみると、deprecatedのせいだったりする。

最近問題になっていたのは、UISliderのsetMinimumTrackImage:とsetMaximumTrackImage:。スライダーの背景画像を設定するためのメソッドだ。これが、iOS 5.0までは問題なかったのに、6.0からどうも挙動が怪しい。描画が乱れるときがある。でも、毎回必ずって訳でもなくて、ちゃんと動いているときもある。

なんでだろう、とあちこちいじってたら、設定する画像をstretchableで引き延ばしていたのが問題だった。resizableにしたら、期待通りの動作になった。分かりにくいよー。

一見動作しているように見えても、後のバージョンのことを考えると、deprecatedにはこまめに対応するのが必要だ、と思わせる事でした。

Google Maps SDK for iOSがきた


Google Maps SDK for iOSがきたー!

ということで、ダウンロードして絶賛テスト中。

SKStoreProductViewControllerでハマったこと


iOS 6から、SKStoreProductViewControllerっていうクラスが追加された。アプリ内でApp Storeの購入画面を表示できるスグレもの。ユーザの導線をアプリ内から逃がさずに、他のアプリを紹介できる。

こいつには、loadProductWithParameters:completionBlock:っていうメソッドが用意されていて、これにストアで表示させる項目をいろいろと設定する。

    // Create controller
    SKStoreProductViewController*   controller;
    controller = [[SKStoreProductViewController alloc] init];
    controller.delegate = self;

    // Set parameter
    NSDictionary*   parameters;
    parameters = [NSDictionary dictionaryWithObjectsAndKeys:
            @"500860946", SKStoreProductParameterITunesItemIdentifier,
            nil];

    // Present controller
    [self presentViewController:controller animated:YES completion:^() {
        // Load product
        [controller loadProductWithParameters:parameters
                    completionBlock:^(BOOL result, NSError* error)
        {
            // For success
            if (result) {
                //...
            }
            // For error
            else {
                //...
            }
        }];
    }];

このコードで、表示できる事が確認できた。

ちなみにドキュメントの方を確認すると、このメソッドの使い方としてこう書いてある。

In most cases, you should load the product information and then present the view controller.
(多くの場合、プロダクト情報を読み込んでから、ビューコントローラを表示するべきでしょう)

これ、そのまんまの通りに解釈すれば、loadProductWithParameters:を呼んで、それが成功したらpresentViewController:する、って思うでしょ。始めそれでやってみたんだけど、それだとアクセスに失敗したときにエラーも何も返ってこない。completion blockがまったく呼ばれないの。最初に実験したとき、間違ったプロダクトIDを与えていて、それでなんの音沙汰もなし。

なんでじゃー、といろいろいじくっていたら、presentViewController:を呼んでおかないと、エラーのときのcompletion blockは呼ばれなかった。ということで、なにはともあれpresentViewController:することにした。でもそれなら、ドキュメントに書いてある事まぎらわしくねー?

モーダルビューとは何なのか


そもそもの発端は、UIViewControllerをモーダルで表示して、それを半透明にしたかった。ほら、モーダル表示すると、下からシュワンと出てきて、重なって表示されるでしょ。このとき、上のビューを半透明にしたらかっこいいんじゃないか、って思ったんだ。

やってみたら、できなかった。なんか、真っ黒になった。Google先生に聞いてみた。modalPresentationStyleをUIModalPresentationCurrentContextにすればいいよ、っていう記事が見つかる。確かにこれでできた。でも、なんか微妙に動きが怪しい。UIModalPresentationCurrentContextってやつも、何なのか、ドキュメントを読んでも、いまひとつ要領を得ない。

てか、そもそもモーダル表示って何なのよ?addSubview:するのと何が違うの?気になったので、少し調べてみた。モーダル表示を何度も重ねる事のできるサンプルを作った。あるUIViewControllerをモーダル表示して、そいつに対してまたモーダル表示して、ということでモーダルのモーダルのモーダルを実現する。その状態で、ビュー階層を調べてみた。

すると、いくらモーダルを重ねても、ビュー階層に残っているのは、一番上に表示されているUIViewControllerのビューだけだ、ということが分かった。モーダルの下になったコントローラ、つまりpresentViewController:animated:completion:を呼び出した側は、モーダルが表示されたらビュー階層から外れるんだ。知らなかったー。

これで、冒頭の半透明表示がうまくいかない理由が分かった。半透明にはなってたんだけど、下に透けて見えて欲しかったビューが、もう外れてしまっていたんだね。なるほど、納得。

じゃ、modalPresentationStyleをUIModalPresentationCurrentContextにするとどうなるかっていうと、この場合はビューが外れなかった。モーダルを重ねたら、その分すべてのビューが追加されたままになっている。うーむ、なるほど。それはそれで、嫌な事が起きそうだなー。

半透明にしたかったら、自分で制御可能な分、addSubview:した方が安全な気がする。ただその場合、viewWillAppear:とかviewDidAppear:とか、addChildViewController:とかを自前で呼ばないといけない。その変が面倒だし気をつけないといけないところだ。

電子書籍でページめくりが最も高速な訳


電子書籍、特に現状の電子書籍に対して批判的な文脈では、ページめくりアニメーションが何かと槍玉に上げられる。いわく、意味なく紙の模倣をしている、無駄に遅い、スクロールの方が電子的だ、など。最近だと、Dailyの廃刊にあわせて取り上げられたりとかね。

ページめくりvsスクロールは、2つの対立軸がある。1つめは、ページネーションを行うか否かだ。紙の本は、その昔にあった巻物ではページネーションの概念はなかったものの、ページという矩形領域に文字を収め、それを重ねる事で長文を表した。電子であれば、スクロールを使う事でページネーションは必要なくなる。Webページがそうだよね。

テキストをスクロールのみで読ませる事が適切か否かは、個人的な経験でいえば、その長さに依存すると思う。ニュースとかブログ記事みたいな、スクリーン1画面から3画面程度ならば、スクロールでいいでしょう。でも、夏目漱石の「こころ」のような長文テキストをスクロールだけで読ませるのは、やっぱり厳しい。これは、一画面に表示される文字数も関係すると思う。一度に目にする文字数が多すぎると、やはり疲れる。

ページネーションするということは、ページサイズという制限が生まれる事につながるので、レイアウトが発展する事になる。たとえば、雑誌だ。特に日本の。1ページという限られた領域に、情報を可読性と美しさに配慮しながらレイアウトする。その上で、ボリュームも突っ込む。これはページネーションという必然から生じた価値で、捨てるのはもったいない。あと、マンガみたいにページがあることが大前提になっている読み物もある。

で、ページネーションされた書籍をどうやって電子で読ませるかという事でも、スクロールとページめくりの対立がある。この場合、スクロールはページ区切りのスクロールってことになる。このとき、ページめくりに対しては、遅いとかアナクロニズムとかいう批判が寄せられる。でも、そんなことはない。実は、ページめくりというユーザインタフェースには、圧倒的な利点があるのだ、というのが今日の本題。

利点というのは、スピードだ。ページめくりというインタフェースは、その仕組み上、スクロールと比較して高速にページ間を移動する事ができる。うん?アニメーションが速いってこと?いや、そうじゃない。連続してめくりをするときに、前のページのめくりが完了する前に、次のページめくりを行うんだ。ちょうど、紙の本をパラパラとめくる感じだ。手前味噌で申し訳ないけど、HMDT BOOKSでめくるとこんな感じになる。

これができると、ページの移動が高速で直感的になる。たとえば5ページ進みたいな、と思えばトトトトトンッと5回連続でタップすればいい。スクロールだとこうはいかない。トーン、トーン、トーン、、、って感じで、ページスクロールが完了してからじゃないと次にいけない。さらに、スクロールはページ全体が動くから、視認性もよくない。ページめくりは、ページの位置自体は固定されているから、めくった隙間からチラリとのぞくことで、かなり確認できる。40ページとか50ページくらいなら、あっという間にめくれる。

ということで、ページめくりアニメーションはただのアナクロニズムじゃなくて、理にかなったユーザインタフェースなんだよ、ってお話でした。もしどっかのデバイスでこの種の連続ページめくりができないのであれば、それはハードとソフトがプアなだけで、ユーザインタフェースの問題とは関係ないね。

CATiledLayerやめました


CATiledLayerっていうレイヤーのクラスがある。主に巨大な画像、スクリーンの縦横4倍以上あるもの、を表示するときに使われる。画像をタイル状に分割して、必要なところだけ表示する事で、メモリの消費量を抑えているのだ。Appleのサンプルで積極的に登場している事もあって、よく使われているクラスだと思う。

でも、これ。使い込むと結構嫌なところが多くて。いちばん嫌なのは、タイル状の分割画像の読み込みをバックグラウンドのスレッドで行い、その生成と破棄の制御が全くできないこと。スレッドを立てるおかげでユーザインタフェースの操作は非常に滑らかなんだけど、いつ作られるか分からないし、作られたスレッドを中断させる事もできない。結果、主となるビューを頻繁に破棄するタイプのアプリだと(電子書籍とか)、結構な頻度でクラッシュが発生する。

どうにか折り合いをつけながらやってきたんだけど、iOS 6になったときかなり致命的に問題になったので、一念発起して使うのをやめる事にした。順次、独自のビュークラスへの置き換えを行っている。

独自のビュークラスっていってもたいした事やる訳じゃなくて、CATiledLayerがやっているのと同じ事を、自前でやるだけだ。画像分割したビュー(tiled viewって呼んでいる)を、画面の表示部分だけaddSubview:する。スクロールしたり、拡大縮小したりしたら、随時ビューの追加と削除を行う。

これを、メインスレッドだけで行っている。もちろん、サブスレッド使った方が動きが滑らかになるんだけど、現状のiOSデバイスの処理能力を持ってすれば、メインスレッドだけで全然いける。プログラミングモデルの単純さと安定性を考えれば、そっちの方が断然有利だ。ただ、表示する画像によってはやっぱりひっかかりが生じるので、そこはテクニックでカバーする。

基本的には力技で、それほど技巧的なものではない。ちょっと気をつけたいのは、tiled viewのtransformか。tiled viewはスクロールビューにaddSubview:するんだけど、普通にやるとスクロールビューのtransformにひきずられて、拡大したときにボケボケになってしまう。そこで、拡大率の逆数(2倍表示なら1/2、4倍表示なら1/4)のtransformを設定してやる。これでちゃんと拡大したときに高精度で表示されるようになる。