Swift Playgroundsで学ぶiOSプログラミング

テキストフィールドにコードを入力した地域の天気予報をアイコンで表示する

文●柴田文彦 編集●吉田ヒロ

2017年08月28日 17時00分

スクロール可能なUITextViewを使う

 前回から見ているように天気情報の中心となる「概況」は、テキストとしてそれなりの長さがあります。また、地域や天気の様子によって長さも一定ではありません。そこで、数行に渡るテキストを表示可能でその行数も可変、枠の中に表示しきれなければスクロールするという特徴を備えた表示機能が欲しいところです。

 iOSでテキストを表示する方法には、ラベル、テキストフィールドなど、これまでにも使ったことのあるものがありますが、上で挙げたような条件をすべて満たすのはテキストビュー(UITextView)です。そこで、概況を表すdescriptionを表示するための機能として、UITextViewのオブジェクトを配置することにしましょう。ついでに、入力したコードがどの地域のものかを確かめる意味でも、titleのテキストを表示するラベルも加えます。

表示する天気予報の地域名を表示するラベル(titleLabel)と天気概況を表示するテキストビュー(descView)をビューコントローラーに追加します

 ラベルはすでに使っていますが、その表示を見てもわかるように目に見える枠というものがありません。titleテキストも地域によって長さが変化するので、プログラムのデバッグ中はラベルの枠との関係がわかるようにラベルの背景に色(薄いグレー)を付けておきましょう。それに対してテキストビューには枠があるのですが、JSONから読み取って表示した内容であることを示すために、ラベルと同じ色を付けておきます。またテキストビューの内容はその場で編集可能にもできますが、ここではその必要はないので、編集は不可に設定しておきましょう。

titleLabelは、フォントサイズを20(ポイント)に変更し、背景色を薄いグレーに設定しています。descViewのフォントサイズは18で、ラベルと同じ背景色を設定しています。また、ユーザーが内容を編集できないように編集可能フラグをfalseに設定しました

UIのアップデートは必ずメインタスクで実行する

 これらの新たに配置したUIオブジェクトの配置については、もう少しあとで見るとして、まずそれらの表示内容を更新する部分だけを見ておきましょう。ここでは、それらを、DispatchQueue.main.asyncというファンクションの中で実行しています。

指定されたURLにアクセスしてデータを取ってくる動作は、メインタスクとは非同期の別タスクで実行されるので、その場でユーザーインターフェースを更新するのは不適切です。そこで、そこだけメインタスクで実行させるように、DispatchQueue.main.asyncを使います

 今回のような単純なプログラムの場合、あえてこのような措置を講じなくても動くことは動きます。それでも、目で見てはっきりわかる欠陥が現れます。それは、画面が更新される動作がかなり(数秒)待たされるということです。

 その理由は、ネットワークアクセスの結果を待って動くURLSessionのdataTaskファンクションが非同期で動作するものだからです。iOSももちろんそうですが、一般的なアプリ用のOSでは、ユーザーインターフェースの更新はメインのタスクの中で実行しなければならないことになっているのが普通です。この場合は、dataTaskファンクションの外で実行すれば、メインのタスクということになります。ところが、ユーザーインターフェースの更新は、ネットワークアクセスの結果が返ってからでなければ意味がありません。

 この虻蜂取らずの状況を解決してくれるのが、DispatchQueue.main.asyncなのです。このファンクション自体を、dataTaskというメインとは非同期に実行されるタスクの中で起動しても、そのファンクションの中に書いた内容はメインのタスクで実行されるのです。ちょっと大げさに例えれば、実行環境をワープさせるタイムトンネルのような働きをするものです。こうしておけば、表示するデータが取得できしだい、最短のタイミングでユーザーインターフェースも更新されるようになります。

 titleのラベルと、descriptionのテキストビューの配置は、もちろんviewWillLayoutSubviewsファンクションの中で設定しています。

新たに追加したラベルとテキストビューの配置も、先に配置したラベルとテキストフィールドの配置に続けて、viewWillLayoutSubviewsメソッドの中で設定します

 実行結果の枠の大きさや位置と見比べながらコードを確認してください。ここで右上のテキストフィールドをタップして地域コードを入力すれば、「東京都 東京 の天気」というtitleと、天気概況のdescriptionが表示されます。テキストビューの枠からはみ出した概況のテキストも、スクロールさせることで、すべて読むことができるはずです。

追加したラベルにはどこの地域の天気なのかを表すタイトルが、スクロール可能なテキストビューにはその地域の天気概況の長いテキストが表示されます

UIImageViewを配置してお天気アイコンを表示する

 今回の仕上げとして、もう1つの表示要素、お天気アイコンを追加しましょう。UIオブジェクトとしては、画像を表示するためのビューであるイメージビュー(UIImageView)を使います。天気予報を表す短いテキストを表示するラベルとともに、ビューコントローラーに追加します。

短い天気予報のテキストと、それを視覚的に表すアイコン画像を表示するためにもう1つのラベルと、イメージビュー(UIImageView)をビューコントローラーに追加します

 天気予報ラベルには、上の例と同じように、薄いグレーの背景色を付けておきます。イメージビューには、枠いっぱいに画像が表示されることになるので、背景に色を付けておいても、あまり意味がありません。こちらは、ラベルの後ろで、そのままコントローラーのビューに追加しておきましょう。

新たに追加するラベルにも薄いグレーの背景色を付けて、イメージビューはそのままビューコントローラーのビューに張り付けます

 天気予報の情報として読み込んだJSONの中には、今日、明日、明後日の簡単な天気予報とそれを画像で表したものが含まれています。今回は、まずいつの天気予報かを表すdateLabelと天気予報の内容を表すtelopを結合したものを、forecastLabelに表示します。その天気予報に対応するアイコン画像は、画像そのものではなく、それが格納されているURLとして与えられます。そのURLから実際の画像データを取得するには、再びURLSessionが必要となります。このような処理は、すべて最初のURLSessionのdataTaskの中に記述します。

天気予報テキストとそのアイコン画像も、dataTaskの中のDispatchQueue.main.asyncの内側で実際のユーザーインターフェースとして更新します

 結局、天気情報全体を取得するdataTaskの中で、お天気アイコンの画像を取得するdataTaskを実行することになります。そうして得られた画像データをイメージビューに書き込む際には、やはりDispatchQueue.main.asyncファンクションを使います。

天気予報テキストは元のデータでは、いつの天気かを示すdateLabelと天気自体を示すtelopを結合したものです。元データに含まれるのは画像のURLなので、もう一度URLSessionのdataTalkを利用して読み込んだデータを画像に変換して設定します

 レイアウトは、例によって実行結果の初期状態とviewWillLayoutSubviewsファンクションの記述を見比べて確認してください。

天気予報テキストとアイコン画像の配置は、例によってviewWillLayoutSubviewsの中で先に配置したテキストビューの下になるように設定しています。空の状態ではイメージビューの枠は見えません

 この実行結果の初期状態には、イメージビューの枠は表示されていません。

 ここでも、右上のテキストフィールドに地域コードを入力すると、その地域のタイトル、概況、天気予報、その画像が表示されるはずです。

プログラムを起動して地域コードを入力すればそのたびに表示する情報が更新され、指定した地域の最新の天気情報の抜粋がテキストとアイコン画像で画面に表示されます

次回の予定

 今回は、前回のプログラムを少しだけ発展させて、ユーザーがテキストフィールドに入力したコードに対応した地域の天気情報を画面に表示できるようにしました。しかし、このプログラムには重大な欠陥があります。それはユーザーが正確な地域コードを知らないと使えないということです。次回は、その欠点を解消する機能を実現する予定です。

mobileASCII.jp TOPページへ