Swiftが3.0になってから全く触れていなかったので、もう一度簡単なアプリを作りながら勉強してみようと思います。
今回のテーマはお天気アプリ。データの取得から表示まで、一通りできるところまでを目指していきます。ライブラリなどは使うと便利だけど使えるまでが初心者には大変なので使いません。
サンプルは記事の最後にあります。
まずは下ごしらえ
気象情報サイトを指定する
最初に気象データの取得元をご紹介します。その名は「ライブドア天気情報」。一見普通すぎるデザインですが、大事なのはそこではありません。このサイトには「Weather Hacks」という日々の気象情報データを配信してくれる便利なサービスがあるんです。しかもJSONというよく使われている形式なので、アプリだけでなくWebサービスなどにも簡単に組み込めるようにできています。
ということでさっそく使っていきます。東京エリアの天気を出力するコードは130010なので、urlを定義しましょう。Xcodeを起動して新規プロジェクトを作成(Single View Applicationを選びます)したら、ViewController.swiftのsuper.viewDidLoad()の下に以下のように記述します。
let areaCode = "130010" // 東京エリア let urlWeather = "http://weather.livedoor.com/forecast/webservice/json/v1?city=" + areaCode
いきなり初心者の壁が
これでオッケー!と思ったら、問題が発生しました。実はiOS9からはhttpで始まる通信は非推奨になっており、設定を変更しなければなりません。
といってもそんなに難しいことではありません。Info.plistを編集して、ATS(App Transport Security)Settingsを追加するだけです。詳しいことは以下が参考になります。
とりあえず動くか試してみる
これで、お天気情報を取得する下地は整いました。試しに、本当に通信できるか試して見ましょう。先ほど定義した2行の下に、以下のコードを貼り付けて、いきなり実行ボタンを押してみましょう。getJsonという変数に入っているJSONデータをそのまま出力するprint文によって何か吐き出されたら成功です。文字化けしているのはまだ気にしなくても大丈夫です。
if let url = URL(string: urlWeather) { let req = NSMutableURLRequest(url: url) req.httpMethod = "GET" let task = URLSession.shared.dataTask(with: req as URLRequest, completionHandler: {(data, resp, err) in //print(resp!.url!) //print(NSString(data: data!, encoding: String.Encoding.utf8.rawValue) as Any) // 受け取ったdataをJSONパース、エラーならcatchへジャンプ do { // dataをJSONパースし、変数"getJson"に格納 let getJson = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary print(getJson) } catch { print ("json error") return } }) task.resume() }
コードは以下を参考にさせていただきました(ほぼそのまんま)。出て来なかった場合は先ほどのATS設定あたりを調べてみてください。
必要なデータを取り出します
一番簡単なデータからトライ
とりあえずここまで来れば、ほぼできたも同然です。あとはゴチャゴチャしたデータの中から必要な項目だけを取り出します。
アプリからアクセスしているURLを直接ブラウザで見るとわかりますが、いろんな情報が複雑に入ってますので、簡単なところからいきましょう。
まずはpublicTime。ここには天気予報の発表日時が入っているようです。JSONデータには簡単に分類すると値型、オブジェクト型と配列型という種類があって、これは値型になりますね。果物屋でメロンくださーい!といったらメロンひと玉もらえるわかりやすい型です。まずは取り出したデータを格納する変数を定義するため、import UIKitの下に以下の1行を追加します。
var publicTime = ""
次に先ほどコピーしたコードの print(getJson)の部分を消して、代わりに
publicTime = (getJson["publicTime"] as? String)! print("\(publicTime)")
を追加します。getJsonの中にあるpublicTimeをくださーい!とお願いしているだけです。細かいことは気にせず、こう書くとデータが取れるんだ、くらいの認識で大丈夫ですよ。
次はオブジェクト型だ!
次にlocationというデータを取り出して見ましょう。データを見ると、locationの中に、さらにcity、area、prefectureというデータが入っています。これがオブジェクト型です。スイカくださーい!とお願いしてひと玉もらったら、その中にある赤い身とタネを簡単に選別できるような感じです。なんて便利な手段。厳密に言うと値もオブジェクト型みたいだけど、まぁいいか。
またimport UIKitの下に定義を先に用意します。descriptionというデータの中身も同様に取り出すため、先に定義を書いています。
var area = "" var city = "" var prefecture = "" var descriptionText = "" var descriptionPublicTime = ""
今度はデータを取得する処理を書きます。先ほどのpublicTimeの処理の下に、こんな感じに書きます。
let location = (getJson["location"] as? NSDictionary)! area = (location["area"] as? String)! city = (location["city"] as? String)! prefecture = (location["prefecture"] as? String)! print("\(area):\(city):\(prefecture)") let description = (getJson["description"] as? NSDictionary)! descriptionText = (description["text"] as? String)! descriptionPublicTime = (description["publicTime"] as? String)! print("\(descriptionText):\(descriptionPublicTime)")
やってることはそんなに難しくありません。getJsonの中にあるlocationというデータをオブジェクト型(後ろのNSDictionaryという部分ですね)として取得して、次にそのlocationの中にあるareaという値を取得する、という2段階構造になっています。同様に他のデータも取得しています。
最後は面倒な配列型だ!
最後に一番大事な天気予報データを取得します。これはforecastsというデータの中にあるのですが、これはちょっと特殊で、同じようなデータが何個も並んでいます。予報データは、今日、明日の分と、もしかすると明後日も出てくるかもしれないので、いくつ入っているかはそのとき次第なんです。
ブドウ1つくださーい!と入ってひと房もらったけど、何粒身が付いているかは数えて見ないとわからない。そんな感じです。ということで数える必要が出てきます。
取得するデータの数も多いし、何個必要になるのか不明なので、入れ物を準備します。またimpoert UIKitの下あたりに定義していきます。
struct Weather { var dateLabel: String var telop: String var date: String var minTemperatureCcelsius: String var maxTemperatureCcelsius: String var url: String var title: String var width: Int var height: Int init(dateLabel: String, telop: String, date: String, minTemperatureCcelsius: String, maxTemperatureCcelsius: String, url: String, title: String, width: Int, height: Int) { self.dateLabel = dateLabel self.telop = telop self.date = date self.minTemperatureCcelsius = minTemperatureCcelsius self.maxTemperatureCcelsius = maxTemperatureCcelsius self.url = url self.title = title self.width = width self.height = height } } var weather = [Weather]()
ちょっと長いんですけど、このままコピペ使えます。こうして構造体を作っておけば、天気も温度もまとめて保存できますし、何日分データがあってもどんどん追加していけるようになります。
構造体の使い方がわからなくても、こんなんあるんだ〜と思っていただければ良いかと。
以下はデータ取得のコードです。ちょっと長いんですけど、取ってくるデータの数が多いだけですので安心してください。大事なのは1行目でgetJsonの中にある”forecasts”をくださーい!とお願いしする部分と、2行目でforecastsの中に入っているデータの数だけ処理を繰り返すfor文の処理になります。最後にあるweather.appendという部分で、細かく取り出したデータをまとめてweatherという変数に登録しています。appendとついてるので上書きされずにどんどんデータを足していくことができます。
let forcasts = (getJson["forecasts"] as? NSArray)! for dailyForcast in forcasts { let forcast = dailyForcast as! NSDictionary let dateLabel = (forcast["dateLabel"] as? String)! let telop = (forcast["telop"] as? String)! let date = (forcast["date"] as? String)! let temperature = (forcast["temperature"] as? NSDictionary)! let minTemperature = (temperature["min"] as? NSDictionary) var minTemperatureCcelsius: String if minTemperature == nil { minTemperatureCcelsius = "-" }else{ minTemperatureCcelsius = (minTemperature?["celsius"] as? String)! } let maxTemperature = (temperature["max"] as? NSDictionary) var maxTemperatureCcelsius: String if maxTemperature == nil { maxTemperatureCcelsius = "-" }else{ maxTemperatureCcelsius = (maxTemperature?["celsius"] as? String)! } let image = (forcast["image"] as? NSDictionary)! let url = (image["url"] as? String)! let title = (image["title"] as? String)! let width = (image["width"] as? Int)! let height = (image["height"] as? Int)! weather.append(Weather(dateLabel: dateLabel, telop: telop, date: date, minTemperatureCcelsius: minTemperatureCcelsius, maxTemperatureCcelsius: maxTemperatureCcelsius, url: url, title: title, width: width, height: height)) }
データの取り方は先に学んだ値型とオブジェクト型のやり方そのままですが、最低/最高気温が入ってない場合もあるようなので、そのチェックを組み込んでいます。
最後にデータを見て見ましょう
これでデータを取れるようになったので、目で確認できるようにします。これもデータが何個あるのかわからないのでfor文を使ってデータの数だけ処理を繰り返します。これは一度for文を抜けた後に追加します。
for w in weather { print("\(w.dateLabel):\(w.telop):\(w.telop):\(w.date):\(w.minTemperatureCcelsius):\(w.maxTemperatureCcelsius):\(w.url)") }
これで今回の分は完成!実行すると、日本語もちゃんと表示されていると思います。あとは綺麗に表示できれば便利なアプリが完成しそうですね!
おわりに
JSONは便利だけどプログラムで扱うのはちょっと難しいですね。なので「SwiftyJSON」とか「Alamofire」などのライブラリを利用するのが一般的なようです。
でもプログラムを学ぶのにはとても良い題材ですね。プログラムっぽいことできるし、結果は目に見えるし。
次はストーリーボードを使って、取得したデータをアプリの画面に表示する部分に挑戦したいと思います。
今回のサンプルはこちらに置いてあります。
【Swift】お天気アプリを作る(2)気象予報データをテーブルビューで表示しよう
[amazonjs asin=”4797389818″ locale=”JP” title=”絶対に挫折しない iPhoneアプリ開発「超」入門 増補改訂第5版 【Swift 3 & iOS 10.1以降】 完全対応 (Informatics&IDEA)”]