ラベル 問題解決 の投稿を表示しています。 すべての投稿を表示
ラベル 問題解決 の投稿を表示しています。 すべての投稿を表示

2014年5月10日土曜日

バージョン3.2.1以降で、なぜか実機でのみエラー発生

久しぶりに書きます。その前に、少し謝罪します。1年以上前にコメントによる質問をいただいたようですが、まったくアクセスしてなくて、しかもコメントが来た知らせをメール等で教えてくれないようで、回答せずに過ぎてしまいました。もう見てないと思いますが、ごめんなさい。知らせてくれる設定があるかどうか、あとで調べてみます。

 

さて本題です。以前に開発したiPad向け業務アプリですが、機能追加の依頼があり、久しぶりに手を入れることになりました。せっかくなので、Titaniumu MobileもXcodeも最新版に入れ替えておこうと、それぞれバージョンアップして開発を始めました。開発は順調に進み、エミュレータでのテストもクリアーしました。でも、実機に転送して動かしてみたら、ある機能だけが動きません。今回の機能追加とはまったく関係のない部分なので、何が起きたのか分かりませんでした。

とりあえず、エラーの内容を調べました。手順も含めて、少し詳しめに書いてみますね。iPadをMacに接続し、XcodeでiPadのコンソールログを見ます。開発したアプリ名で、しっかりエラーが発生していました。しかし、このままでは、エラーの箇所が特定できません。iOS用バッケージを作るときに、JavaScriptのminificationを設定しているので、エラー箇所がline 1になってしまってます。minificationをオフしてバッケージを生成し、実機に転送してエラー箇所を特定する必要があります。

エラーの内容は「'undefined' is not an object」でした。これは参照した変数にオブジェクトが入ってないときなどに発生するエラーです。エラーが発生したJavaScriptは以下のとおりです。

xxx = fileMix.read().text; // xxxは実際の名前ではない

このままでは、メソッド「read()」で発生しているのか、プロパティ「text」への参照で発生しているのか判断できません。そこで以下のように分割しました。

DebugX01 = fileMix.read();
xxx = DebugX01.text; // xxxは実際の名前ではない

ここで追加した変数「DebugX01」は、デバッグ用の変数であるため「Debug」という文字列で始めています。削除し忘れたりしても後から明確に分かります。また病気で突然倒れて開発を中断したとしても、この部分はデバッグのために追加したことが後から明確に思い出せます。こういう工夫は意外に大事ですね。

本題に戻りましょう。分割して実機で実行したら、「text」プロパティへの参照でエラーが発生していました。エミュレータではエラーが発生しないのに、実機だけで発生するというのも奇妙です。また、この部分は今回の修正範囲ではなく、以前は正常に動いていた箇所です。JavaScriptのコードが原因とは考えられません。別な原因で、何か問題が発生したと推測できます。たとえば、Titanium Mobileの仕様が変わって、以前のコードが動かなくなったとかです。

バージョン2.xでは正常に動いていたので、エラーが出る最新バージョンから順番に戻ってみることにしました。iOS用バッケージを生成し、実機に転送してテストするので、けっこう大変です。3.0、3.1、3.2などと大きな区切りで試し、変化したバージョンを特定するのが一番効率的です。そうして調べた結果、バージョン3.2.0では正常に動作し、3.2.1からエラーが発生していました。つまり、3.2.0から3.2.1への変更内容を調べれば、何か原因が分かりそうです。リリースノートを調べたのですが、関係しそうな機能が見付かりませんでした。

エラーが発生した箇所の機能を説明していませんでしたね。UI部品のWebViewを使って、グラフを表示する機能です。使いたいグラフ機能がJQueryを前提としているので、HTMLと組み合わせてWebViewで表示したというわけです。HTMLやJavaScriptやグラフ用データをテキストとして合体させ、WebViewのtextプロパティに設定して表示しています。変数fileMixには、Ti.Filesystem.Fileオブジェクトを入れ、readメソッドでファイルを読み込みます。読み込まれた内容はTitanium.Blobオブジェクトとなり、textプロパティで内容を参照できます。

エラーの意味ですが、DebugX01変数のtextプロパティを参照したら「DebugX01変数にはオブジェクトが入ってない」と怒られたという感じでしょうか。エラー発生箇所の一連の処理では、HTMLやJavaScriptなど何種類かのフィアルを呼んでいます。これら全部でエラーが発生してるわけではありませんでした。よく調べたら、JavaScriptを読み込むコードだけでエラーが発生していました。これが何かのヒントになりそうです。

試しに、JavaScriptのファイル名を変更してみました。ファイル名「xxx.js」を「xxx.js.txt」に変えただけです。そうして実機で実行してみると、な、な、何と、エラーが発生しないではありませんか。原因は、何なのでしょう。エミュレータでは動いて、実機ではエラーになるというのも不可解です。同じiOS7上で動作させ、Titanium Mobileのバージョン3.2.0以前では正常に動き、3.2.1以降ではエラーになります。いったい何が原因なのでしょうか。分かりません。原因は不明ですが、対処方法は見つけました。

少しだけ、原因につながりそうな考察をしましょうか。3.2.1以降のTitanium Mobileでアプリを作った場合、Ti.Filesystem.Fileオブジェクトで「.js」拡張子のJavaScriptテキストを読み込んだとき、それはTitanium.Blobオブジェクトにはならず、オブジェクトではない何かになってしまうということ。しかも、エミュレータではオブジェクトになり、実機ではオブジェクトにならないという奇妙な現象。また、同じファイルを「.txt」拡張子に変更すると、実機でもオブジェクトになってくれます。おそらく特定の条件を満たしたときだけ、JavaScriptテキストがコードとして解釈されて、オブジェクトではなくなるのでしょうね。その現象が、3.2.1以降のTitanium Mobileで、しかも実機だけで発生するという。

まあ、原因は不明ですが、対処方法が見付かって良かったです。同じ現象にあたった人がいたら、今回の事例を参考にしてください。そういう思いで、久しぶりに書きました。でも、また冬眠します。

2012年7月19日木曜日

iPadでは大きな画像のリサイズが遅い

やることが多くて忙しかったので、久しぶりの投稿です。コンテンツ待ちで保留中の、Titanium Mobileを使ったiPadアプリですが、少しずつ動き出しました。新しいiPadを入手して、レチナ・ディスプレイでもテストしたら、思いがけない発見がありました。

 

レチナではない普通のディスプレイでは普通に動いていたアプリですが、レチナ・ディスプレイで動かしたら、処理が極端に遅い部分を発見しました。普通のディスプレイだと1秒ぐらいで終わる処理が、数秒間も余計に待たされるのです。あきらかに遅く、このままでは公開できないほどでした。

遅い原因が分からないので、どの部分が遅いのか、まずは調べることにしました。メッセージを表示するためのラベルを追加して、処理の区切りごとに英字を表示させます。こうすると、どの部分で遅いのか簡単に特定できますから。実際に試してみると、予想もしない部分で遅くなっていました。この投稿のタイトルに書いたとおり、大きな画像のリサイズです。

 

このアプリは、コンテンツである画像の表示がメインとなります。用意した画像を画面いっぱいに表示したり、レイアウトの一部として画像を含め、縮小して表示したりします。画像の大きさは、普通のディスプレイ用の1024×768ピクセルと、レチナ・ディスプレイ対応版の2048×1536ピクセルの両方を用意しています。そのためレチナ・ディスプレイでは、2048×1536ピクセルの画像をリサイズして表示することになります。GPUを使わずCPUで処理しているためでしょうか、明らかに時間がかかっています。

本当にリサイズが重くて遅くなっているのか、リサイズした画像を追加して、リサイズ処理をなくしてみました。すると予想どおり、数秒間の待ちが完全に消えました。やはり大きな画像のリサイズが重かったのでした。パソコン用のCPUではなく、携帯電話用のCPUですから負荷が大きいのでしょう。

 

レチナ・ディスプレイに対応するため画像サイズが大きくなり、さらに画像の数が多いので、できるだけ容量を増やさないようにと考慮し、画像をリサイズして使う設計にしました。しかし、そんな方法が裏目に出てしまいました。処理が遅くないように作らないとイライラして使えません。容量が増えてしまいますが、リサイズした画像を追加で全部用意することにしました。

パソコンと違って、まだまだ考慮すべき点があるのですね。勉強になりました。

2012年5月7日月曜日

SDK 2.0.1でも描画問題への対処は必要(3)

Titanium SDK 2.0.1GA2で、描画関係のバグが解消されているか調べる話の続きです。iPhone用アプリの開発で経験した、画面を回転させたとき、ImageViewやLabelが変な位置に描画される問題を取り上げます。前の投稿で直っていると書きましたが、対処方法を実施しても消えなかったバグが出なくなっただけで、対処方法を不要するレベルで直っているかは不明でした。そこを調べたので報告します。

 

まずはバグの内容を。画面を回転させたときにアニメーションで表示されますが、縦横表示で同じViewを使い、View上のUI部品の位置を回転時に変更すると、設定したtopやleftの値とは全然違う位置に表示される問題です。発生する条件ですが、複数のImageViewの位置を変更させると起こるようです。単に位置が変になるだけではなく、本来なら下に隠れているImgeViewの一部も表示されてしまいます。対処方法は、次のような形でした。見えているUI部品だけの位置を変更し、下に隠れているUI部品はsetTimeoutで遅延させて変更する方法です。この対処方法でほとんど解消したのですが、たまに実機でのみ、一部のLabelが変な位置に表示されます。

以上にような状態のまま2.0.1GA2で再ビルドすると、実機でも変な位置の表示が解消されました。その点では、問題が解消されたといえます。しかし、本来なら特別な対処方法(一部の設定を遅延させる)を使わなくても、プロパティで指定した位置に表示すべきものです。それが直っているかどうか、シミュレータと実機の両方で確認してみました。

 

まず、対処方法を加えたコードです。画面が回転したときに呼び出される関数として作ってあります。表示中のUI部品だけは位置プロパティを変更し、残りのプロパティ変更は遅延した別関数として作りました。このような形で作ると、シミュレータ上ではバグが完全に消えました。ただし実機でのみ、少しバグが出ます。

// 1つだけImageViewを変更する
function changeOrientF() {
    var orient = Ti.Gesture.orientation;
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        imgView1.height = 240;
        imgView1.width = 320;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        imgView1.height = 320;
        imgView1.width = 427;
        ...
    }
    setTimeout(changeOrient2F, 200); // 0.2秒後に動かす
}
// 残りのImageViewを、時間差を付けて変更する
function changeOrient2F() {
    var orient = Ti.Gesture.orientation;
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        imgView2.height = 240;
        imgView2.width = 320;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        imgView2.height = 320;
        imgView2.width = 427;
        ...
    }
} 

このコードを、本来の形に戻します。遅延する関数として2つに分けるのではなく、すべてのプロパティ変更を一緒にして、1つの関数として作ります。具体的なコードは、次のようになります。

// すべての変更を1つにまとめる
function changeOrientF() {
    var orient = Ti.Gesture.orientation;
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        imgView1.height = 240;
        imgView1.width = 320;
        ...
        imgView2.height = 240;
        imgView2.width = 320;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        imgView1.height = 320;
        imgView1.width = 427;
        ...
        imgView2.height = 320;
        imgView2.width = 427;
        ...
    }
}

ご覧のように、難しい変更ではありません。バグが解消していれば、一部のプロパティ変更を遅延させなくても、正常な位置に表示されるはずです。

 

さて実際に動かした結果ですが、シミュレータでも実機でも、変な位置に表示されるバグは出ませんでした。ImageViewもLabelも、プロパティで設定した位置に表示されます。動作中のいろいろなタイミングでiPhoneを回転しましたが、途中の状態はさておいて、最後には正常な位置で表示しました。アプリのアニメーション中に回転アニメーションが加わっても、いつも正常な位置に収まります。2.0.1GA2では、バグが解消されているようです。

画面回転での表示バグは、いろいろな対処方法を試しましたが、どうしても解決しなかったものでした。SDKのバージョンアップで解消され、本当に良かったです。バグが消えたことで、特別な対処方法を用いる必要がなくなりました。

 

ここまで3回の投稿を整理すると、2.0.1GA2で対処が必要な描画問題は、フラッシュバック症状だけになりました。これは前から対処方法を見付けていますから、ぜんぜん大丈夫です。2.0.1GA2では対処方法をpostlayoutイベント処理で実現しますが、非常に簡単な変更でした。この1つだけで大丈夫になったということは、Titanium SDKのレベルアップではないでしょうか。まさに意味のあるバージョンアップですね。

2.0.1が安定したバージョンになれば、もう安心して公開できます。画像がメインのアプリなので、あとは画像とテキストの制作待ちですが、SDKの安定バージョンが出る頃には、制作も終わっているでしょう。めでたし、めでたし。

2012年5月3日木曜日

SDK 2.0.1でも描画問題への対処は必要(2)

Titanium SDK 2.0.1GA2を使って、描画関係の問題が解消しているか調べる話の続きです。その問題とは、hide中のViewで、ViewにaddしたUI部品の位置や内容を変更してから、Viewをshowしたとき、変更前の状態が一瞬表示されることです。フラッシュバック症状と呼んでいます。前回は、UI部品の変更を箇所を何も変えず、setTimeoutで処理していた箇所を、postlayoutイベント処理に変更した話でした。この簡単な変更だけでも、問題なく動きました。

 

いよいよ今回は、UI部品を変更する箇所の改良です。Titanium SDK 2.0.1に追加された、UI部品の複数プロパティ変更を、1つの変更のように扱う処理を利用します。単独で使うupdateLayout関数と、ペアで使うstartLayout関数とfinishLayout関数の、2種類が用意されています。どちらも、ViewまたはUI部品で使う関数なので、単独のViewまたは単独のUI部品が対象となります。

今回のアプリでは、View上の複数部品を一緒に変更するため、すべての変更が終わってから、変更が完了したと知らせる必要があります。ペアで使う関数しか役に立ちません。実際のコードは次のようにしました。

// 変更の開始を知らせる
imgView.startLayout();
label1.startLayout();
label2.startLayout();
view.startLayout();

// 各UI部品を変更
imgView.top = 120;
imgView.left = 200;
imgView.image = photoName[i];
...

// 変更の終了を知らせる
imgView.finishLayout();
label1.finishLayout();
label2.finishLayout();
view.finishLayout);

// viewを表示
view.show();

シミュレータ上で動かしてみると、まったく変わりません。変更前から正常に動いているため、当然でしょう。フラッシュバック症状が出るかどうか確認するために、postlayoutイベント処理をsetTimeout処理に戻し、遅延時間を1ミリ秒に設定して動かしました。しっかりとフラッシュバック症状が出ます。症状を消すためには、はやりpostlayoutイベント処理が必要でした。

この後、viewをshowする処理の位置などを変更しながら動きを観察していて、大事なことに気付きました。Viewをhideしている状態ですから、描画機能は動いてません。その状態でView上のUI部品を変更しても、プロパティの値が変更されるだけです。そしてViewがshowされたときに、設定されたプロパティで描画内容を生成します。そのshowの最初に、フラッシュバック症状が発生するというわけです。変更前のフラッシュバック症状がshowしたときに出るということこそ、showした時点から描き始めている証拠です。

整理すると、startLayout関数とfinishLayout関数は、showされてる状態で有効なのであって、hideされているときに使っても意味がないのです。もちろん、ここで試さなかったupdateLayout関数も同様です。今回のアプリのようにhide中の変更では、単に無駄に処理を加えているだけとなります。意味なしです。というわけで、startLayout関数とfinishLayout関数を削除しました。

 

新しい関数の使用条件を理解していなかったので、予想外の結果となりました。結局、フラッシュバック症状への対応は、前回の投稿と同じまま、setTimeout処理をpostlayoutイベント処理に変更するだけで完了です。回り道をしましたが、startLayout関数とfinishLayout関数などの役割を理解できたので、良しとしましょう。この結果を知っていたら、前の投稿と一緒に書いて構わなかったですね。まあ、こんなこともあります。

2012年5月1日火曜日

SDK 2.0.1でも描画問題への対処は必要(1)

このブログでは、Titanium Mobileを使った際の画面表示の問題と格闘してきました。少し前の投稿では、リリースされたSDK 2.0.1GA2を使うことで、最大の問題だったImageViewやLabelが変な位置に表示される症状が、直ったとの速報を書きました。あれから使い続けていますが、症状は1回も発生していません。直っているのは確実なようです。

描画関係では他にも問題があり、その1つ1つが直っているのか、直っていないなら同じ対処方法で大丈夫なのか、1つずつ見直したいと思います。まずは、Viewのレイアウト変更への対処を取り上げます。

 

以前の投稿「Viewはshowしたときに描き直されるもの?」で、レイアウトを変更したときの対処方法を書きました。おさらいすると、問題は、hideしていたView上にあるImageViewやLabelの画像やテキストや位置を変えた後、showすると変更前の状態が一瞬だけ表示されることでした。フラッシュバック症状と呼んでいます。対処方法は、Viewの透明度を限りなく透明に設定してからshowし、setTimeoutで遅れて透明度を不透明に戻す方法でした。

まず調べたのは、SDK 2.0.1GA2でも、UI部品を変更する前の状態が一瞬表示されるフラッシュバック症状が出るかどうかです。setTimeoutの遅延時間を極端に短い1に設定して、シミュレータ上の動作を見てみました。結果は、前と同じです。SDK 2.0.1GA2でも、同様のフラッシュバック症状が出ました。つまり、この部分の動きは変わっていないということです。

ただし、何も対処していないわけではありません。SDK 2.0からは、プログラムの作り方で対応するように、新しい機能が追加されています。the UI Layout Systemが更新され、UI部品の大きさに関するデフォルト値が変わりました。同時に、描画での作業終了を考慮した機能が追加されています。それぞれのUI部品ごとに、複数プロパティを変更するときの処理を1つとして扱い、全部が終ったら描画する形も可能になりました。具体的な方法が2つ用意されていて、まずupdateLayout関数では、複数のプロパティを一度に指定できます。もう1つのstartLayout関数とfinishLayout関数はペアで使い、この間にプロパティの変更処理を入れます。

さらに、描画内容を生成し終わるまでの待つ機能が加わっています。それがpostlayoutイベントで、変更するViewやUI部品にイベント処理を加えれば、setTimeoutで処理を遅らせる必要はありません。setTimeoutで遅らせる方法では、少し余裕を持った待ち時間を設定するため、全体として処理が遅くなります。ところがpostlayoutイベントで知らせる方法だと、描画内容を生成し終わったら始められますから、無駄な待ち時間は生じないはずです。

SDK 2.0.1での変更点は、Appceleratorの開発者向けドキュメントに記述してあります。興味のある方は「Transitioning to the New UI Layout System」を読んでみてください。

 

開発中のアプリで、SDK 2.0.1GA2での改良を反映させてみました。フラッシュバック症状は前と同様に出ますから、時間を遅らせて表示させる処理は必要のままです。ただし、実現方法としては、setTimeoutで遅延させる方式から、postlayoutイベントで処理を開始する方式へと切り替えます。これで無駄な待ちが少しは減るでしょう。まずは、これまで実施していたsetTimeoutによるコードです。

// 修正前(setTimeoutを使用)
view1.hide();          // view1を非表示にします
view1.opacity = 0.001; // view1を限りなく透明にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();                  // view1を再表示します
setTimeout(resetOpacityF, 50); // 50ms後に、不透明に戻すfunctionを起動させます
// この関数は、ここで終了

function resetOpacityF(){      // 時間差攻撃で、view1を不透明に戻します
    view1.opacity = 1;
}

これを、処理内容は同じまま、postlayoutイベントで処理するコードに切り替えます。具体的には、次のように作ります。

// 修正後(postlayoutイベントを使用)
view1.hide();          // view1を非表示にします
view1.opacity = 0.001; // view1を限りなく透明にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();                  // view1を再表示します
view1.addEventListener('postlayout', resetOpacityF); // イベント処理を設定します
// この関数は、ここで終了

function resetOpacityF(){      // postlayoutイベントで、view1を不透明に戻します
    view1.removeEventListener('postlayout', resetOpacityF); // イベント処理をクリアします
    view1.opacity = 1;
} 

setTimeoutの代わりとして、postlayoutイベント処理関数をaddEventListenerでViewに加えています。これで描画内容の生成終了待ちとなります。postlayoutイベントが発生すると、設定したイベント処理関数の実行が始まり、まず最初にremoveEventListenerでイベント処理関数を削除し、本来の処理を開始します。以上のように、ほとんど前と同じままで、変更が完了してしまいました。

 

view1をshowした直後にpostlayoutイベント処理を追加し、そのまま待ちます。本来ならshowする前にイベント処理を追加すべきなのですが、描くのに時間がかかるためでしょう、これでも問題なく動きました。さらには、UI部品の変更を1つにまとめる変更もしなければならないのですが、それをする前に試しに動かしたら、正常に動いてしまいました。すぐにremoveEventListenerを実行しているためでしょうね。アプリの動作としては、フラッシュバック症状が発生せず、とくに副作用もありません。こんなに簡単に動いて良いのでしょうか、と疑問に思うぐらい簡単に動きました。

本来であれば、UI部品の箇所も一緒に変更して公開すべきでしょう。でも、簡単な変更でも正常に動いたので、これも面白い情報だと思って公開しました。完全ではない変更でどのように動くかも、意外に貴重な情報となるからです。期待どおりに動かなかったケースで、こういう完全でない変更での動きが、解決方法を見付けるヒントになったりしますので。

 

動いたのを確認しただけでは、ちょっと満足感が不足です。postlayoutイベントが発生するまでの時間はどの程度なのか、やはり気になりますよね。そこで、イベント発生までの時間を計測してみました。addEventListenerの直前に時刻を計り、removeEventListenerの直後にも時刻を計って、差を求めるだけです。イベント処理の追加と削除を含めたのは、これらの処理も含めた経過時間を知りたかったからです。計算した時間差を表示する機能を加えて、実際に実行してみました。

描画内容を生成する時間は、UI部品の種類や数や変更内容によって左右されます。計測結果は、あくまで今回のアプリの場合です。postlayoutイベントを使った箇所は2つで、両方とも測定しました。1番目の箇所は、変更するUI部品の数が6つで、4つがLabel、2つがImageViewです。6つとも位置を変更し、それぞれの値であるテキストと画像も毎回変更します。たまにですが、一部のUI部品で位置だけ変更しない場合もあります。こうした条件のアプリをシミュレータ上で計測したところ、最低では0、最高で41の値となりました。数値の単位はミリ秒で、マシンは現行の13インチMacBook Air(Core i5 1.7GHz Dual)です。発生頻度が一番高いのは0で、全体の3割ぐらいを占めていました。0を含めた一桁台が全体の半分程度ありました。数値が極端に大きいときは、画像をメモリーに読み込んでいるとか、ガベージコレクタが動いているとか、特別な条件なのでしょうか。原因は不明です。

2番目の箇所は、変更するUI部品が半分の3つで、2つがLabel、1つがImageViewです。これらへの変更内容は1番目と同じですが、透明度を変更する処理が加わっています。上記のサンプル・コードは、この2番目の箇所のものでした。計測すると、50〜52と値はほぼ一定でした。このようにバラツキがほとんどないのが普通だと思います。1番目の箇所でバラツキが生じた理由が分かりません。

同じ計測を、実機でも試してみました。初代iPadで実行すると、1箇所目は最低が6で、最高が62でした。全体的に値が大きくなっています。頻度としては小さな値の比率が大きく、とくに一桁が半分程度を占めるという変な結果となりました。バラツキの傾向も非常に似ていて、シミュレータが正常に機能していることを証明した感じです。まあ、当たり前の結果でしょう。2番目の箇所もシミュレータと似ていて、値は56〜67とバラツキは小さいです。1番目の箇所と同様に、シミュレータよりも少し遅くなっています。

余談ですが、setTimeoutでは50に設定していたので、最高が67という結果では、修正前の50という値が小さすぎたのかも知れません。しかし、実機でかなり使いましたが、描画の問題は出ませんでした。postlayoutイベントが発生するタイミングに多少の余裕があるのか、描画内容としてギリギリだと目立たないのか、その辺は分かりません。修正前の50という値は、単なる偶然ですが、一番遅い初代iPadによる実機での動作としては絶妙な値だったのでしょう。

 

計測結果では、片方の描画は非常に短い時間が多くなりましたが、実機を触っている限り体感できません。もともと短い時間なので、少しぐらい減っても体感できないのでしょう。今回の修正により、固定した時間だけ遅らせるのではなく、実際の描画内容生成が終わるまで待つ形になりました。処理としては、より良い形になっています。あまりにも簡単な修正でしたが、最適化された形となりました。今後も、似たような状況では、postlayoutイベント処理を使うでしょう。

UI部品の変更は、次の投稿で書きます。また別な症状の確認も残っていますから、それは後でということで。

2012年4月22日日曜日

Titanium SDK 2.0.1でImageViewのバグが解消した様子

Titanium Studioも2.0.1にバージョンアップし、Titanium SDKも2.0.1.GA2のアップデートが来たので、ImageViewのバグがどのように変化しているのか、さっそく試してみました。ついでに、Xcodeも4.3.2を入れての組み合わせです。開発中のiPhoneアプリは、ソースコードなどを全く直さず、Titanium SDKとして、1.8.2を2.0.1.GA2に変更しただけです。

 

まずは、シミュレータでの動作確認。ここは前から問題なしだったので、そのまま通ると予想しました。確かに、動作としては問題なく表示されていました。ただし、コンソールにはワーニングがいくつも出ました。Titanium SDKが仕様変更されていて、直さなければならない箇所があるようです。あとで詳しく調べて、きっちり修正しましょう。

続いて、実機での確認です。ここもXcodeが4.3.2に変わっただけで、設定などは以前と同じです。Ad-Hocでアーカイブを作成し、iTunesでiPhoneに転送しました。ドキドキの動作確認ですが、まったく問題ありませんでした。今までだと、画面を何度も回転させると、ImageViewは何とか正常に表示されたのですが、一緒に表示するLabelの位置が変になっていました。以前はすぐに症状が出ましたが、かなり操作しても出ませんでした。どうやら直っているようです。

LabelもImageViewと同じ対策をすれば、前のSDKでも解決できたかも知れません。しかし、症状が出ている状態で、Titanium SDK 2.0.1.GA2での動作を確認したかったのです。直っていることを期待して、作成中のアプリで確かめたかったというわけです。

Titanium SDK 2.0では、描画関係に大きなメスが入れられたようで、根本的に改良されたのでしょう。それによって、一連のバグが直ったと思われます。今まで頑張って見付けた対策も、もしかしたら必要ないかも知れません。その辺も、あとで調べる必要があります。とりあえず、症状が消えたことで、安心しました。もっと本格的なテストは必要ですが、たぶん大丈夫でしょう。Titanium Mobileを避ける大きな理由が消えて、凄く嬉しいです。

 

Xcodeのみでの開発も、少しずつ勉強を進めています。Objective-Cでの開発も覚えておいて損はないでしょうから、このまま続けます。Titanium Mobileで作成中の3つめのアプリを、同じ形で作ろうと進行中です。まあ、保険という意味も少しはあります。もう必要なさそうですけどね。とりあえず、最初のWindowを表示しTableViewを動かす部分までは作りました。

Objective-Cを使って気付いたのは、Titanium Mobileでの作りやすさです。あらためて実感しました。まだObjective-Cに慣れてないという点もありますが、それを差し引いても、JavaScriptで気軽にさくさく作れる点は大きな魅力です。私自身、JavaScriptはブラウザ用に数行程度しか使ったことがなく、Titanium Mobileで本格的に勉強を始めました。ほとんど知識がなかったのですが、意外に早く作れるようになりました。やはりスクリプト言語の威力は凄いです。それに比べるとObjective-Cは、JavaやCを使ったときのような注意深さが必要です。

 

簡単なテストだけでしたが、Titanium SDK 2.0.1.GA2ではImageViewおよびアニメーション関連のバグが解消したようで、大きな懸念が取り払われました。2.0.1がどんどん使われ、安定した状態になるのを大いに期待してます。次のアプリも、Titanium Mobileで開発することになるでしょう。めでたし、めでたし。

2012年4月17日火曜日

画面回転で画像だけ変な位置に

Titanium Mobileを使った2つめのアプリも、必要な機能はすべて作り終わりました。画像をじっくり見せるアプリなので、あとは中身の画像を用意するだけです。これは別途に作業するため、開発は一旦終了。このアプリはiPad専用なのですが、ミニ版としてiPhoneに移植することになりました。iPad版は横長での使用に限定しましたが、iPhone版は縦長でも使用するという話に。というわけで、画面回転に対応させる必要が生じました。そこで新たな問題と遭遇。その顛末、解決までの道のりを書きます。

 

画面の回転に対応させるのは、とても簡単でした。「orientationchange」イベントの処理を追加して、そこでWindow内のUI部品のレイアウトを変更します。複数のWindowがあり、それぞれにレイアウト変更の処理を加えました。メモリー消費を考慮して、ウィンドウは閉じたらメモリー解放する形にしています。画面回転のイベント処理もクリアーする必要から、addEventListenerで追加した処理を、removeEventListenerで削除しなければなりません。削除しやすいように、イベント処理は名前付きの関数を利用します。次のような形で。

// 画面回転の処理
function changeOrientF() {
    var orient = Ti.Gesture.orientation; // 関数で引数eを使わないように、このプロパティで縦横を判断
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        view1.top = 100;
        view1.left = 0;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        view1.top = 0;
        view1.left = 200;
        ...
    }
}

考え方としては、UI部品をグループ分けして、それぞれ別なViewに貼り付けます。画面の縦横回転の際に、View単位で位置を調整してレイアウトを変更するようにしました。この考え方で、まったく問題ないはずでした。

 

いろいろなWindowで縦横回転を試すと、画像を入れたImageViewだけが、回転後に変な位置に表示されます。設定する値が間違ってるのかと思い、画面上にデバッグ用ボタンを追加し、回転アニメーションが終わってから、ImageViewのtopとleftの値を表示させました。すると、両方の値は正常に設定されていて、それとは関係ない位置にImageViewが表示されているのです。当然、明らかなバグです。しかし、どこのバグでしょうか。iOSか、Titanium Mobileか。

解決しなければならないので、症状を調べてみました。何十回と回転させると、表示させる位置にはパターンがあるようです。iPhoneの回転位置は4つあり、それぞれで決まった位置にImageViewが表示されました。また、何十回も続けて回転させると、ImageViewに表示される画像が少しずつ劣化していきます。縦位置と横位置ではImageViewの大きさが違い、一旦小さくなってから大きくなるとき、小さくなった画像を拡大して表示している様子でした。つまり、もとの画像ファイルを毎回読み込んでいるのではなくて、画面上に表示した画像を再利用して、回転したり拡大縮小しているというわけです。これらはすべて、アニメーション機能が動いていて、画質重視ではなく効率重視で設計されているのでしょう。

 

いろいろなWindowで観察していたら、ImageViewを1つしか使っていないと、正常な位置に表示されると分かりました。しかし、複数使っているWindowでも、1つ以外は下に隠れていて、画面上には表示されていません。そんな使い方のWindowでも、ImageViewが複数あると、すべてのImageViewが変な位置に表示されます。下に隠れているはずのImageViewまで、一部が表示されてしまうのです。そう、とんでもない症状です。

そこで、次のように考えました。回転し始めたときは、一番手前のImageViewだけ大きさと位置を変更し、他の下に隠れているImageViewは、時間差攻撃で大きさと位置を変更したら良いかも知れないと。JavaScriptは、次のようにしました。

// 1つだけImageViewを変更する
function changeOrientF() {
    var orient = Ti.Gesture.orientation;
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        imgView1.height = 240;
        imgView1.width = 320;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        imgView1.height = 320;
        imgView1.width = 427;
        ...
    }
    setTimeout(changeOrient2F, 200); // 0.2秒後に動かす
}
// 残りのImageViewを、時間差を付けて変更する
function changeOrient2F() {
    var orient = Ti.Gesture.orientation;
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        imgView2.height = 240;
        imgView2.width = 320;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        imgView2.height = 320;
        imgView2.width = 427;
        ...
    }
}

この0.2秒という時間差は、回転アニメーションの時間を見て決めました。最初は0.3秒にしたのですが、少し余裕がありそうに見え、0.2秒に変更して問題なかったので0.2秒に落ち着きました。実際のアニメーション時間より、少し短いかも知れません。結果は、見事に成功でした。ImageViewが正常な位置に表示されます。

しかし、ここで安心してはいけません。あくまで、シミュレータ上での成功だけです。実機に転送して、最終確認をしなければ。さっそく転送した試したところ、見事に失敗でした。最初にシミュレータ上で乱れた位置とは、また別な位置にImageViewが表示されます。振り出しに戻りました。この時点で、少し凹みましたね。いったん成功したと思ったわけですから。

 

こんなときは、上手い珈琲でも飲んで一休みです。珈琲を飲みながら、別な対処方法を思い浮かべます。ImageViewだけ乱れるなら、ImageViewとまったく同じ大きさのViewを用意して、ImageViewを囲むようにして使ったらどうだろうか。さっそく、次のようなJavaScriptを作って試しました。

// ImageViewを定義する部分
var view1i = bbb.createViewF(view1, 240, 320, 120, 0); // 入れ物のView
var imgView1 = bbb.createImgViewF(view1i, strPhotoName, 240, 320, 0, 0);

// 1つだけImageViewを変更する、画面回転の処理
function changeOrientF() {
    var orient = Ti.Gesture.orientation;
    if (orient == Ti.UI.PORTRAIT || orient == Ti.UI.UPSIDE_PORTRAIT) {
        view1i.height = 240;   // 入れ物のViewを変更
        view1i.width = 320;
        view1i.top = 120;
        view1i.left = 0;
        imgView1.height = 240; // ImageViewは、大きさだけ変更
        imgView1.width = 320;
        ...
    } else if (orient == Ti.UI.LANDSCAPE_LEFT || orient == Ti.UI.LANDSCAPE_RIGHT) {
        view1i.height = 320;   // 入れ物のViewを変更
        view1i.width = 427;
        view1i.top = 0;
        view1i.left = 53;
        imgView1.height = 320; // ImageViewは、大きさだけ変更
        imgView1.width = 427;
        ...
    }
    setTimeout(changeOrient2F, 200); // 0.2秒後に動かす
}

入れ物のViewと、入れるImageViewは、まったく同じ大きさにします。同じ大きさなので、ImageViewのtopとleftはゼロのまま固定。表示位置の変更は、入れ物となるViewのtopとleftの値で設定するわけです。

さて結果ですが、まずはシミュレータ上で試すと成功。ここで安心してはいけません。次に、実機へ転送して動作確認。やりました。大成功です。変な位置に表示される症状が出なくなり、本来の位置に表示されました。とりあえず、良かったです。

 

Titanium Mobileで開発を始めて、まだ3つめのアプリですが、いろいろと問題に遭遇しました。これからも遭遇しそうです。最初に作ったアプリでは、WebViewで苦労させられました。最近は、ImageViewで苦労しています。ここで以前に書いた、変更前の状態が一瞬だけ表示される症状も、ボタンやラベルで発生することはなく、WebViewとImageViewではほぼ確実に発生します。WebViewも、HTMLの評価結果を画像として表示していると捉えられ、画像を表示するViewが、Titanium Mobileの鬼門かも知れません。

開発効率を重視してTitanium Mobile/Desktopを選びましたが、意外に苦労が多くて困惑気味です。前からJavaやCを使っているので、Objective-Cでも苦労しないと思います。Xcodeに移行したら、今回のような問題が発生しないのであれば、移行する価値はありますね。というわけで、ちょっと悩み中です。

 

追記:表示する位置が変になる症状は、ImageViewだけではありませんでした。Labelでも発生することを発見しました。同じ対策が有効かどうか、あとで確認する予定です。

2012年4月13日金曜日

Viewはshowしたときに描き直されるもの?(続き)

何回か前に、「Viewはshowしたときに描き直されるもの?」のタイトルで投稿した内容に関して、実験しながら少し調べてみました。その辺の話をまとめて書きます。

 

そもそもの始まりは、ウィンドウ内のレイアウトを何種類かに変更する目的で、ImageViewやLabelの表示位置を変更したときの動きでした。ViewをhideしてからUI部品の表示位置や中身を変更すると、Viewをshowしたときに、変更前の状態が一瞬表示されてしまう問題です。それを回避しようと、Viewを限りなく透明にしたり、不透明へ戻すときに時間を遅らせて対応しました。

じゃあ、View上のUI部品の位置を変えなかったら、前の状態が一瞬表示される問題は発生しないのか。そんな疑問が残ったので、調べるために実際に動くサンプルを作って実験しました。まず、複数の固定レイアウトViewを用意して、UI部品の位置を変えない方法です。これでも、UI部品の中に表示するテキストや画像は変えますから、Viewをshowしたときの内容は、前に表示した内容と異なります。試した結果ですが、やはり変更前の内容が一瞬だけ表示されました。必ず表示されるわけではなくて、表示されることもあるという感じです。どの程度の割合で発生するかは、動作している環境によって変わりそうですが、意外に多いなと思いました。

次に試したのは、Viewの代わりにWindowを使う方法です。Viewの影響が生じないようにと、UI部品はWindowに直接addしました。Windowなので、showとhideではなくopenとcloseで、表示と非表示を切り替えます。この実験でも、UI部品の内容を変更して再表示し、古い内容を一瞬でも表示されるかを確かめます。気になる結果ですが、やはり同じでした。変更前の内容が、一瞬表示される症状が発生します。これも必ずではなく、たまに発生するという感じでした。

このような結果は十分に予測できました。UI部品を表示している部分のアルゴリズムが同じであれば、UI部品をaddする対象を変えたとしても、同じような動きになって当然です。やっぱり同じだったと確認できたのですが、本当は違ってほしかったところです。もし違えば、回避策の1つとして利用できたのですけど。

 

今回は複数の方法で同じ症状が発生しましたが、解決策がないわけではありません。UI部品に前の状態を作らなければ良いのです。表示する前に毎回、古いUI部品を削除して、新しいUI部品を生成する方法です。これなら前の状態が存在しないため、一瞬でも表示される症状は起こりえません。ただし、別な注意点が生まれます。再表示する度に、古いUI部品を捨てていくため、きっちりとメモリーを解放する必要があります。この辺の注意は、慣れている人なら問題ないでしょう。

個人的にですが、本来は必要ないのに、UI部品を次々に生成する方法は嫌いです。OS、開発ツール、自分のプログラムのどこかのバグに当りそうで、メモリーリークの危険度が増します。採用する気がないので、実験での確認はしませんでした。最初に対策した方法で、問題なく動いてますからね。

2012年4月9日月曜日

遅い処理での小さな心遣い

今回は、Titanium Mobileを使って開発しているアプリでの、ちょっとした心遣いのお話です。前回の投稿で、UI部品が100個以上にもなるViewの話が登場しました。実機でテストしていると、表示されるまでの遅さが気になります。たった2秒弱なのですが、表示されるまでの間に「あれ、動いてないのかな」と感じてしまいます。おそらく、人によっては画面をタップしてしまうでしょう。何か対策を打つのが、良い選択ではないかと考えました。

実際に処理中ですから、ActivityIndicatorを表示させるのが適切な選択でしょう。ActivityIndicatorが回っているのを見せれば、「処理してますから、少し待ってくださいね」と伝わります。ActivityIndicator自体は白いので、四角いグレーのViewを用意し、その中央で回ってもらいましょう。これも、いつものように生成関数を用意して、処理する側では1行で生成しました。JavaScriptは、次のようになります。

// 処理中を伝えるView表示
var view9 = bbb.createViewF(win, 256, 256, 220, 220);
view9.backgroundColor = '#ccc';
var activityIndicator = bbb.createActIndcF(view9, 110, 110);
activityIndicator.show(); 
// 本来のViewを生成(Viewをhideして多数のUI部品を追加)
var view1 = bbb.createViewF(win, 768, 768, 0, 0);
view1.hide();
var lblTitle1 = bbb.createLblF(view1, 'Viewタイトル', 24, 'left', 24, 'auto', 20,20);
...
// 処理中Viewを消す(削除する)
win.remove(view9);
// 本来のViewを表示
view1.show();

本来のview1を生成する前に、処理中だと伝えるview9を真っ先に生成して、そこにActivityIndicatorを追加してshowします。これで処理中の表示が始まりました。続いて、本来のview1とUI部品を生成します。ここが、一番時間のかかる処理ですね。生成が終わったら、処理中表示のview9をウィンドウから削除して、本来のview1をshowします。

上記のJavaScript全体は関数として呼び出す形なので、view1を生成すると終了します。処理中表示のview9は、ウィンドウから削除していますし、関数内の変数も関数の終了で消えてますから、view9で使ったメモリーは解放されるというわけです。本来のview1だけが画面に表示され、動き続けます。

実際に初代iPadで動かしてみると、受ける印象が全く違います。256x256サイズの四角いグレーの中に、ActivityIndicatorが回っているだけですが、待たされているという感じはぜんぜんありません。ActivityIndicatorが動いていて、それを眺めてしまうのが原因でしょうか。待っている際のイライラ感が完全に消えてしまいました。とりあえず、大成功のようです。

2012年4月2日月曜日

UI部品数が多いView表示の高速化

Titanium Mobileに限らず、iOS用のアプリ開発では、シミュレータで動作確認した後、実機に転送して確かめます。実機での動作で問題となるのが、シミュレータよりも遅い点です。開発に使っているのはMacBook Airの13インチ(Intel Core i5, dual 1.7GHz)で、Macとしては遅いほうに分類されます。そんなマシン上のエミュレータでさえ、iPadの実機よりも高速なのです。実機上のテストで遅いと、なんとか改良しなければなりません。

 

アプリでは複数のWindowやViewを扱いますが、UI部品が増えるほど描画が遅くなります。その限界は意外に少なく、View上に数十点のUI部品を付けると、気付く程度には遅くなります。今回は理由があって、あるViewだけは100個以上のUI部品を用いました。そのViewだけは、明らかに遅いです。

今回のアプリで描画が遅いのには、少し別な理由もあります。メモリーを節約しようと、描画が終わったらViewを破棄して、再度表示するときでも新しく生成し直しているのです。つまり、Viewを表示するたびに、UI部品まで含めて生成しています。このようなViewを、1つのWindow上で複数切り替えながら、使い分けています。Window上の一部だけ切り替えるため、Viewを切り替える方法がベストなのです。

生成するのがViewでなくWindowならば、openやcloseのタイミングで表示内容を描きます。しかしViewの場合は、openやcloseが用意されてなくて、別な方法で似たような機能を実現しなければなりません。それをせずに毎回生成すると、UI部品を1つずつ足しながら描いていくため、どうしても表示が遅くなってしまいます。

Viewでのopenやcloseの代わりは、showとhideでしょう。windowなら、最初からcloseした状態で生成されますが、Viewはshowした状態で生成されます。これを防ぐために、Viewを生成した直後にhideするか、hideした状態で生成します。試してみましたが、直後にhideする方法でも問題ないようです。通常は、Viewをshowした状態で生成するような関数を使ってますから、直後にhideする方法で問題ないなら、生成関数が1つで済むため都合がよいのです。具体的なJavaScriptは、次のようになります。

// view1を生成直後にhideする
var view1 = bbb.createViewF(win, 768, 768, 0, 0);
view1.hide();
// UI部品を加える
var label1 = bbb.createLblF(view1, 'ラベル', 14, 'right', 20, 100, 10, 580);
var Button1 = bbb.createBtnF(view1, 'ボタン', 24, 40, 200, 180, 150);
// UI部品を加え終わったら、view1を表示させる
view1.show();

この方法を初代iPadで試したところ、UI部品が数十個のViewでは、違いが分かるほど高速化できました。しかし、UI部品が100個以上あるViewでは、高速化にも限界がありました。Viewが現れるまで2秒弱ほど待たされます。それでも、hideせずに描くよりは充分に高速化されています。この状態で我慢してもらうしかないのでしょうね。

初代iPadはRAM容量が少ないため、メモリーを節約するような作り方をしないと不安です。画面表示が少しは高速化できたので、この方法で最後まで進めたいと思います。

2012年3月27日火曜日

Viewはshowしたときに描き直されるもの?

Titanimu Mobileを使って、新しいアプリを開発中です。いろいろと試しながらの開発なので、実際に作る工程よりも、どんな動きをするのか細かな部分の解析に時間を取られています。使い始めて2つめのアプリなので、仕方ないでしょうね。

 

今回は、Viewの描画で困ったことが起こりました。普通にViewを作成して、普通に表示しているだけなら、とくに問題はありません。でも、少し凝った機能が必要になって、ViewにaddしたUI部品のレイアウトを途中で何度も変更する機能を実装しました。UI部品のtopやleftを途中で変更し、1つのViewのままで、複数のレイアウトを実現する機能です。メモリー効率も良さそうですし。

さすがに、表示したままレイアウトを変更するわけにはいきません。Viewをいったんhideして、hideした状態でUI部品のレイアウトを変更し、変更が終わってからViewをshowしたら問題ないだろう考えました。他の工夫(viewをhideしても問題が生じない工夫)と組み合わせる必要はありますが、Viewに関するJavaScriptは、次のようになります。

view1.hide();      // view1を非表示にします
label1.top = 40;   // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();      // view1を再表示します

これが期待した動きになりませんでした。UI部品のレイアウトを変えてから、Viewをshowした場合は、ほんの一瞬だけ変更前のレイアウトが表示され、直後に新しいレイアウトを描いて安定します。フラッシュバックで一瞬だけ古い画像が表示されたような感じです。バグでしょうか、仕様なのでしょうか。こんな使い方をしている人がいないためか、検索しても情報は見付かりませんでした。普通に考えると、表示する内容を内部で作り終わってから、画面に描画するように作るのではと思います。でも、動きから推測するに、そうなってはいないようです。

真剣に困りました。この方法が使えないと、複数のレイアウトのViewを用意しておき、それぞれに値を設定し直してから切り替える必要があります。レイアウトを増やす場合も、新しいViewを追加しなければなりません。美しくないですね。何とか回避できないかと、かなり悩みました。

悩んだ結果、1つ思い付きました。透明度を変えるopacityを使えないかと。opacityの値で限りなく透明にした状態でViewを描かせ、直後に不透明状態に戻せば、フラッシュバックが消えるのではないかと。完全に透明にしてしまうと、描く処理が開始しないと思い、限りなく透明な値として0.001を選んでいます。さっそく、次のようなJavaScriptで試しました。

view1.hide();          // view1を非表示にします
view1.opacity = 0.001; // view1を限りなく透明にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();          // view1を再表示します

結果は、惨敗でした。何も変わらず、フラッシュバックが再現されました。まだ、あきらめません。限りなく透明にする位置が悪いのではないかと考えました。hideしてから限りなく透明にしても、その状態を描いていません。限りなく透明にするのを、hideする前に行えば、表示されている時間内に限りなく透明で描いたことになり、有効だと考えました。試したJavaScriptは、次のような形です。

view1.opacity = 0.001; // view1を限りなく透明にします
view1.hide();          // view1を非表示にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();          // view1を再表示します

またまた惨敗しました。ここでいったん敗北宣言です。こんなときこそ、美味しい珈琲を飲んで休息です。時間を置いてから、再び考えました。もしかしたら、時間差攻撃が有効かもと。Viewをshowした直後にopacityを戻しているからダメなので、少し時間を経過してからなら大丈夫ではないかと。限りなく透明に設定する処理をhideの後ろに戻し、次のJavaScriptで試しました。

view1.hide();          // view1を非表示にします
view1.opacity = 0.001; // view1を限りなく透明にします
label1.top = 40;       // view1上のUI部品のプロパティを変更して、画面上のレイアウトを変えます
label1.left = 30;
imgView.top = 120;
...
view1.show();                  // view1を再表示します
setTimeout(resetOpacityF, 50); // 50ms後に、不透明に戻すfunctionを起動させます
// この関数は、ここで終了

function resetOpacityF(){      // 時間差攻撃で、view1を不透明に戻します
    view1.opacity = 1;
}

今度は、大成功でした。フラッシュバックがまったく出ません。勘で決めた50msですが、時間差攻撃が成功です。最近の高速コンピュータにとっては、50msでも長い時間なのでしょう。もっと短くなるかも知れませんが、不安が増すので今のところ50msで固定です。

以上は、シミュレータでの動きでした。実機ではフラッシュバックが出ないかも知れませんし、この解決方法で実機も大丈夫という保障もありません。ただし、show後に描き直しているという動きから考えて、非常に有効な解決方法です。

開発の基本としては、実機でもシミュレータでも正常に動作することが大事です。実機で動いたからといって、その実機だけかも知れません。機種もOSバージョンも違う環境が、何種類も存在します。すべての環境でテストすることは無理なので、実機でもシミュレータでも動くことを、最低条件とするわけです。また、問題の解決にあたっては、たまたま直ったのではなく、こういう現象だからこうすれば直るはずと、理論的な裏付けのある解決方法が大事です。

今回の問題は苦労しましたが、何とか解決できて良かったです。まだ実機でのテストが残っていますが、最悪の場合でも、時間差の数値を変更するだけで大丈夫でしょう。複数レイアウトのViewを用意することだけは、ぜったいにやりたくないですからね。

 

Titanimu Mobileの経験が浅いので、もしかしたら、別な解決方法があるかも知れません。ご存じのことがいたら、ぜひ教えてください。一応、viewのプロパティなども本家のAPIページで全部見ましたが、それらしいものは見付かりませんでした。唯一使えそうだったのが、opacityというわけです。