質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

SwiftUIでWebAPIから結果を表示したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 107

yosuke302

score 4

HotPepperAPIから取得した飲食店情報をリスト表示するために、下記コードを書きました。

import SwiftUI
import Foundation
import Combine

class RestaurantStore: ObservableObject {

    @Published var restaurants: [Restaurant] = []

    init() {
        load()
    //④
    }

    func load() {
        let url = URL(string: "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&large_area=Z011&format=json")
        URLSession.shared.dataTask(with: url!) { data, response, error in
            DispatchQueue.main.async {
                self.restaurants = [try! JSONDecoder().decode(Restaurant.self, from: data!)] 
       //③
            }
        }.resume()
        // ②
    }
}

struct Restaurant: Decodable{
    var results: Results

    struct Results: Decodable {
        var shop: [Shop]

        struct Shop: Decodable, Identifiable {
            var id: String
            var name: String
        }

    }
}

struct SearchView: View {

    @ObservedObject var store = RestaurantStore()

    var body: some View {
        List(store.restaurants[0].results.shop) {(res) in //①
            RestaurantRow(res: res)
        }
    }
}

struct RestaurantRow: View {
    var res: Restaurant.Results.Shop
    var body: some View {
        Text(res.name)
    }
}
{
results: {
 api_version: "1.26",
 results_returned: "10",
 results_start: 1,
 shop: [
  {
   name_kana: "しんじゅく ぜんせきこしつ いざかや とりきち しんじゅくてん",
   other_memo: "様々なご要望承ります◇チーズフォンデュと個室の肉バル横丁 新宿店◇",
   name: "鶏吉 新宿店",
   genre: {
    name: "居酒屋",
    catch: "歓迎会 送別会 新宿 肉バル 個室 女子会",
    code: "G001"
   },
   open: "月~木、日: 17:00~翌0:00 (料理L.O. 23:00 ドリンクL.O. 23:30)金、祝前日: 17:00~翌3:30 (料理L.O. 翌2:30 ドリンクL.O. 翌3:00)土: 16:00~翌3:30 (料理L.O. 翌2:30 ドリンクL.O. 翌3:00)祝日: 16:00~翌0:00 (料理L.O. 23:00 ドリンクL.O. 23:30)",
   close: "無休 休日や週末でも使えるお得なクーポン有♪◇新宿 全席個室 居酒屋 鶏吉 新宿店◇【新宿 居酒屋 宴会 個室】【肉バル 個室】",
   service_area: {
    name: "東京",
    code: "SA11"
   },
   station_name: "新宿",
  },
  {(別の飲食店情報)},
  {(別の飲食店情報)},
  {(別の飲食店情報)},
 ], 
}
}


(上記jsonデータは内容を一部省略したものです。)

エラー

①で、Fatal error: Index out of rangeが発生します。

やったこと

そもそもデータが取得できていないのではと思い、
②、③、④の位置でprint(self.restaurants)を書いてみたところ、

③ではデコードされたデータを出力でき、print(self.restaurants[0].results.shop[0].name)で店名までアクセスできました。
しかし、②、④では[]とのみ出力され、データは出力されませんでした。

なぜ、②、④ではデータが出力されず、また、上記エラーが発生しているのかがわかりませんでした。
解決方法をお教えいただけますと幸いです。

補足情報

  • Xcode Version 11.2
  • Apple Swift version 5.1
  • macOS catalina version 10.15.1
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

②と④は通信が終わっていないので空配列のままですね。

下記コードで動くと思います。
おそらく必要な情報は Shop だけだと思うのでモデルを出しました。

class RestaurantStore: ObservableObject {

    // Shop の配列の変更をViewに通知
    @Published var restaurants: [Shop] = []

    init() {
        load()
    }

    func load() {
        let url = URL(string: "https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&large_area=Z011&format=json")
        URLSession.shared.dataTask(with: url!) { data, response, error in
            guard let data = data else { return }
            let decoder: JSONDecoder = JSONDecoder()
            do {
                // Restaurant型で一旦データを受ける
                let searchedResult = try decoder.decode(Restaurant.self, from: data)
                DispatchQueue.main.async {
                    // 結果の[Shop]をプロパティに代入 
                    self.restaurants = searchedResult.results.shop
                }
            } catch {
                print("json convert failed in JSONDecoder. " + error.localizedDescription)
            }
        }.resume()
    }
}

struct Restaurant: Decodable{
    var results: Results

    struct Results: Decodable {
        var shop: [Shop]
    }
}

// ここを分けました; RestaurantRowのプロパティも短くできます
struct Shop: Decodable, Identifiable {
    var id: String
    var name: String
}

struct SearchView: View {

    @ObservedObject var store = RestaurantStore()

    var body: some View {
        List(store.restaurants) {(res) in
            RestaurantRow(res: res)
        }
    }
}

struct: View {
    var res: Shop
    var body: some View {
        Text(res.name)
    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/09 23:03

    ありがとうございます!上記コードで動きました!
    DispatchQueue内でデータの取得&プロパティへの代入を一気にやっていたせいだったのですね。。。
    このエラーでだいぶ悩ませれていたので、大変助かりました!
    ベストアンサーにさせていただきます!

    キャンセル

  • 2019/11/10 08:15

    良かったです!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる