2015年1月9日金曜日

ファイル閲覧機能のライブラリ化(全体設計編)

今まで作ったアプリには、Documentsフォルダ内のファイルを閲覧する機能を付けました。アプリで作成したデータや、環境設定ファイルなどを、Documentsフォルダに保存しているからです。ファイル閲覧機能があると、どんな状態なのか簡単に調べられ、トラブル発生時に役立つと考えたからです。その機能を最近になって独自ライブラリに加え、どのアプリにも簡単に追加できる形に仕上げました。それを何回かに分けて紹介します。

 

Documentsフォルダのファイル閲覧機能と言っても、作り方は様々です。私が作ったのは、画面の左側にTableViewを配置し、そこにファイル名の一覧を表示させます。その中の1ファイルを選択すると、画面の右側に配置したTextViewにファイルの内容が表示されます。全部を一度に表示できない場合でも、スクロール可能に設定してあるため、ファイルの内容は最後まで見れます。

表示されたファイルは編集できないものの、全体または部分を選択して、クリップボードへコピーする機能を付けています。選択機能はiOSに付属のものを利用して、選択した部分だけコピーする形です。さらに使い勝手を考慮し、ファイル全体をコピーするために専用ボタンも追加しました。

ちなみにクリップボードですが、iOS上ではペーストボードという名前です。しかし、開発したアプリのユーザーの多くがパソコンと併用しているため、アプリが表示する名前をクリップボードで統一してあります。これ以降の説明でもクリップボードで通しますで、ご注意を。

アプリがDocumentsフォルダに保存するファイルはテキスト形式だけなので、該当する形式のファイル名だけをTableViewに表示しています。その中の1つを選択すると、選ばれたファイルの内容をTextViewに表示するという形です。

このように単純な機能なのですが、アプリのDocumentsフォルダの状態が簡単に見れて、iPad実機で何か調べたいときに重宝します。実機のままだと、Documentsフォルダが簡単に覗けませんので。

 

ライブラリ化するために、よく考えて仕様を決める必要があります。まずは、設計方針からです。このような機能を作る場合、画面表示も含めて、いくらでも凝れます。しかし、凝れば凝るほど、iOSが何度も更新されるに従って、互換性が低下する可能性が増します。できるだけ長く、修正せずに済ませられるように、まったく凝らないという設計方針にしました。

具体的には、次のように配慮します。互換性が一番低下しそうなのは、UI部品でしょう。そこで、UI部品は素のまま使い、余計な機能は何も追加しません。TableViewの選択表示も素のまま使い、TextViewの選択機能もそのまま利用します。

実現する形としては、どの画面にでも貼付けやすくしたいので、UIViewのサブクラスとして作ることにします。そうすればUIViewが持つすべての機能が使えるとともに、余計な機能を作らなくて済むため、将来への互換性も高く維持できるはずです。

UIViewではなく、UIViewControllerとして作るという方法もあります。この2つの違いというか使い分けに関して、いろいろと調べましたが、納得できるほどの方法は得られていません。今のところ、画面全体を扱う場合はUIViewControllerを、画面の一部として貼付ける場合はUIViewを用いています。この使い分け方が、それぞれの本来の役割に近い形ではないかと思うからです。今回のものは画面の一部になるため、UIViewを選びました。

 

UIView形式で作るときに問題となるのが、表示する大きさに合わせたレイアウト変更です。指定されたサイズに合わせて、表示するUI部品の大きさも変更しなければなりません。こうしたレイアウトの表示ルールをきちんと決めておかないと、見た目や使いやすさなどが低下してしまいます。

ただし、どんな大きさを指定しても大丈夫に作るのは無理ですから、最初に最低の大きさを決めます。今回の場合は、あえて小さく表示する必要がないため、とりあえず幅が800で高さが500と仮決めしました。このような値は、作成後に使ってみて調整すれば良いでしょう。

次に決めるのが、要素の大きさと配置です。今回のUIViewは配置が決まってますから、各UI部品の大きさだけです。左側のTableViewは、幅を狭くすると長いファイル名が読めなくなるので、幅は固定としました。右側のTextViewの幅だけが可変です。縦方向は、TableViewもTextViewも同じ高さで、両方とも可変です。

その他のUI部品としては、右下にコピーボタンを付けます。TextViewで選択したファイル内容を、クリップボードにコピーするための機能です。さらに、コピーボタンの左側にメッセージ欄を加えます。コピーの成功など、操作結果を知らせるためのメッセージ表示に使います。

あと全体で、余白も付けることにしました。UIViewの境界ギリギリにUI部品などを表示させると、高解像度のモニターで描画が正しくない可能性もあるので、安全のために余白は必須だと思います。

 

境界ギリギリも含めて、描画については少し説明が必要でしょう。iOSの描画機能は、座標をCGFloatで管理し、最終的な表示装置に向けてビットマップ画像を生成します。これが、実は曲者なのです。

すべての線には、描く太さがあります。例として、太さ1(単位の名前は不明:CGFloatの値が1)の黒線を描く場合を考えましょう。描く座標の位置は、値が10の位置とします。解像度が2倍のレチナ・ディスプレイなら、2ピクセル幅の黒線として描かれます。

しかし、解像度が1倍の通常ディスプレイなら、1ピクセル幅の黒線ではなく、2ピクセル幅のグレー線として描かれます。こうなる理由は、描く座標の位置に深く関係しています。座標10の位置は、ディスプレイのピクセルの境目にあります。そこを中心に描くのですから、座標10の位置の両側に、幅0.5の線が描かれる結果となります。しかし、通常ディスプレイの解像度は1です。幅0.5の線は描けず、幅1のグレー線として描かれるわけです。幅0.5の線2本を描いた結果として、2ピクセル幅のグレー線になってしまいます。

逆に、解像度が2倍のレチナ・ディスプレイは、ピクセルの幅が0.5ですから、座標10の両側に幅0.5の線を描くのに適しています。結果として、2ピクセル幅(合わせた線の太さは幅1)の黒線が描かれるというわけです。

以上のように、ディスプレイの解像度の違いにより、また描く座標位置の違いにより、同じ線でも描かれ方は少しずつ変わります。さらに、これがUIViewの境界に接していたら、どうなるでしょうか。考えたくもないですね。描く線の幅にもよりますが、最低でも幅1以上の余白を確保するのが、極めて賢明な選択だと分かるでしょう。

実際には、後から線を太くする変更もあり得るため、その際でも余白の値を変更しなくてよい形に作るべきです。そのためには、もっと多くの余白を確保したほうが安全です。今回は、境界ギリギリまで描く必要がないため、キリのよい値として上下左右とも幅10の余白を付けることにしました。こうすれば、UIView境界の描画を気にする必要はありません。

 

次は、TableViewに表示するファイル一覧に関してです。今まではテキスト形式のファイルだけでしたが、ライブラリ化する以上、それ以外のファイルが存在する前提で考える必要があります。テキスト形式以外のファイルが含まれていても正常に動くのはもちろん、それらが存在することが分かるように作るべきでしょう。

ファイル名の一覧には、別な役割もあります。Documentsフォルダ内のファイル名一覧を表示することは、フォルダ内の状態を見せる意味もあります。表示できないファイルも含めて、フォルダ内にある全部のファイル名を表示したら、それはそれで何かの役に立つでしょう。

ということで、次のような仕様にしました。テキスト形式以外のファイル名も一覧には表示し、テキスト形式ファイルと明らかに分かっているファイルだけ、右側のTextViewに内容を表示します。テキスト形式かどうかは、ファイル名の拡張子で判断します。予め決められた拡張子のファイルだけで、ファイル内容を表示する形です。

 

もう1つ考えなければならないのは、ファイル名一覧の更新機能です。これまで作ったものは、更新機能がありません。表示したときに一覧を表示して、そのまま最後まで使います。

更新機能を付けないのには、理由があります。iOSアプリの場合は、ウィンドウを開いて使い終わったら、消してメモリーを開放するというのがマナーになっています。見終わったら削除し、再び見るときには新たに表示するので、更新機能が不要というわけです。今回も、そのマナーに従い、更新機能は付けないことにしました。

 

ここまでの検討で、全体の仕様が見えてきました。箇条書きで整理すると、次のようになります。

ファイル閲覧機能の大まかな仕様
・UIViewのサブクラスとして作る
・表示サイズをチェックして、最低値より小さいならエラー
・凝った機能は追加せず、UI部品を素のままで使う
・構成するUI部品は以下のとおり
 ・TableView:ファイル名の一覧で、1つを選択可能
 ・TextView:選択したファイルの内容を表示(テキスト形式のファイルのみ)
 ・Button:TextViewで選択したテキストを、クリップボードにコピー
 ・Label:各種メッセージの表示
・レイアウトに関して
 ・上下左右に幅10の余白
 ・横位置の計算:TableViewは固定、TextViewは可変
 ・縦位置の計算:TableViewもTextViewも可変
・実装する機能
 ・ファイル名の一覧を、TableViewに表示する(オープン時に自動で実行)
 ・TableViewで選んだファイルの内容を、TextViewに表示する
 ・TextViewの表示内容を、クリップボードにコピーする

今まで作ってきたクラスとは異なり、用意したメソッドを呼び出して動かす形ではありません。最初に表示した状態で、それぞれのUI部品に機能を割当てます。UI部品が操作されたときに、割り当てられた機能が実行されるという形です。

こうした形なので、仕様では「メソッドの一覧」の代わりに、「実装する機能」としてまとめました。

 

ここまで仕様が固まると、あとは作るだけです。しかし今回は、過去のアプリ内で作ったSwiftコードがあります。それを使えば、最初から作るよりは、はるかに簡単です。

UIViewのサブクラスとして作るわけですから、全体の大枠は変わります。でも、ソースコードの多くは、該当する箇所へコピーして使えました。仕様が変わった部分だけ手直しして、変更も意外に早く終わりました。いったん作ったものなので、構成を変えるだけだと簡単ですね。

次回の投稿では、そのSwiftコードを取り上げます。

0 件のコメント:

コメントを投稿