見出し画像

Apollo iOS チュートリアル (4)

詳細ビューを完成させます。

1. 詳細ビューの情報取得方法

詳細ビューの情報を取得する方法は、いくつかあります。

(1) LaunchListクエリで、全ての詳細ビューの情報を取得
(2) 別のクエリで、個別の詳細ビューの情報を取得

今回は、(2)の方法を使います。GraphQLの利点の1つは、ページに表示する必要のあるデータを正確に照会できることにあります。追加情報を表示しない場合は、必要になるまでデータを要求しないことで、帯域幅、実行時間、バッテリー寿命を節約できます。

2. GraphiQLでのクエリの確認

詳細ビューの情報を取得するクエリ「LaunchDetails」を作成します。
(1) 以下のクエリをGraphiQLで入力。

・(GraphiQL)

query LaunchDetails($id:ID!) {
  launch(id: $id) {
    id
    site
    mission {
      name
      missionPatch(size:LARGE)
    }
    rocket {
      name
      type
    }
    isBooked
  }
}

「ID!」の「!」は、nullにできないの意味になります。

【注意】
GraphQL と Swift で null の扱いが 異なるので注意してください。Swiftでは、プロパティの型に「?」「!」の注釈を付けない場合、そのプロパティはnullにできません。GraphQLでは、フィールドの型に「!」を付けない場合、そのフィールドはnullにできます。

(2) GraphiQLの画面の左下の「QUERY VARIABLES」を選択して、以下を入力。

・(GraphiQL)

{ "id": "25" }

これは、クエリの実行時に$idに25を入力するという設定になります。

(3) 再生ボタンを押す。
「id 25」の情報が表示されます。

画像1

(3) LaunchDetails.graphql にクエリをコピーして、アプリをビルド。

3. 詳細ビューの編集

(1) DetailViewController.swift を以下のように編集。

・DetailViewController.swift

import UIKit
import Apollo

class DetailViewController: UIViewController {
    // ランチID
    var launchID: GraphQLID? {
        didSet {
            self.loadLaunchDetails()
        }
    }
   
    // クエリ
    private var launch: LaunchDetailsQuery.Data.Launch? {
        didSet {
            self.configureView()
        }
    }
   
    // UI
    @IBOutlet private var missionPatchImageView: UIImageView!
    @IBOutlet private var missionNameLabel: UILabel!
    @IBOutlet private var rocketNameLabel: UILabel!
    @IBOutlet private var launchSiteLabel: UILabel!
    @IBOutlet private var bookCancelButton: UIBarButtonItem!

    // ビューの設定
    func configureView() {
        // launchの取得
        guard
            self.missionNameLabel != nil,
            let launch = self.launch else {
            return
        }
       
        // ミッション
        self.missionNameLabel.text = launch.mission?.name
        self.title = launch.mission?.name

        // イメージ
        let placeholder = UIImage(named: "placeholder")!
        if let missionPatch = launch.mission?.missionPatch {
            self.missionPatchImageView.sd_setImage(
                with: URL(string: missionPatch)!, placeholderImage: placeholder)
        } else {
            self.missionPatchImageView.image = placeholder
        }

        // サイト
        if let site = launch.site {
            self.launchSiteLabel.text = "Launching from \(site)"
        } else {
            self.launchSiteLabel.text = nil
        }
  
        // ロケット名
        if
            let rocketName = launch.rocket?.name ,
            let rocketType = launch.rocket?.type {
            self.rocketNameLabel.text = "🚀 \(rocketName) (\(rocketType))"
        } else {
            self.rocketNameLabel.text = nil
        }
           
        // 予約
        if launch.isBooked {
            self.bookCancelButton.title = "Cancel trip"
            self.bookCancelButton.tintColor = .red
        } else {
            self.bookCancelButton.title = "Book now!"
            self.bookCancelButton.tintColor = self.view.tintColor
        }
    }

    // ビューロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()

        // UIをクリア
        self.missionNameLabel.text = "Loading..."
        self.launchSiteLabel.text = nil
        self.rocketNameLabel.text = nil
        self.configureView()
    }
   
    // ランチ詳細の読み込み
    private func loadLaunchDetails() {
        // ランチIDの取得
        guard
            let launchID = self.launchID,
            launchID != self.launch?.id else {
            return
        }
       
        // ランチ詳細の読み込み
        Network.shared.apollo.fetch(query: LaunchDetailsQuery(id: launchID)) {
            [weak self] result in
            
            // selfの取得
            guard let self = self else {
                return
            }
       
            switch result {
            // エラー時
            case .failure(let error):
                print("NETWORK ERROR: \(error)")
            // 成功時
            case .success(let graphQLResult):
                if let launch = graphQLResult.data?.launch {
                    self.launch = launch
                }
                if let errors = graphQLResult.errors {
                    print("GRAPHQL ERRORS: \(errors)")
                }
            }
        }
    }
} 

4. ストーリーボードの編集

◎ StackViewの追加
(1) 「Detail Scene → Detail → View」に「(Horizontal) StackView」を追加。
(2) 制約を、次のように指定。

画像2

(3) 属性を、次のように指定。

画像3

◎ ImageViewの追加
(1) 「StackView」に「UIImageView」を追加し、名前に「Mission Patch Image View」を指定。

(2) 「View」と「UIView」を両方選択し、以下の制約を追加。

画像5

画像5

(3) 追加した制約を選択し、次のように更新。
画面のサイズに関係なく、画像の幅が常に画面の40%になる制約になります。

画像6

画像7

(4) 「Mission Patch Image View」を選択し、以下の制約を追加。

画像8

(5) 追加した制約を選択し、次のように更新。
幅と高さが常に等しくなる制約になります。

画像9

画像10

(6) 「Mission Patch Image View」を「missionPatchImageView」アウトレットに接続。

◎ 2番目のStackViewの追加
(1) 2番目の「(Vertical) StackView」を1番目の「StackView」に追加。

(2) 属性を、次のように指定。

画像11

◎ Labelの追加
(1) 2番目の「StackView」に「Label」を追加し、以下の属性を指定。

画像12

(2) 「Label」を「missionNameLabel」アウトレットに接続。

◎ 2番目のLabelの追加
(1) 2番目の「StackView」に2番目の「Label」を追加し、以下の属性を指定。

画像13

(2) 2番目の「Label」を「rockageNameLabel」アウトレットに接続。

◎ 3番目のLabelの追加
(1) 2番目の「StackView」に3番目の「Label」を追加し、以下の属性を指定。

画像14

(2) 3番目の「Label」を「launchSiteLabel」アウトレットに接続。

◎ BarButtonItemの追加
(1) 「BarButtonItem」をナビゲーションバーの右上にドラッグし、以下の属性を指定。

画像15

画像16

(2) 「BarButtonItem」を「bookCancelButton」アウトレットに接続。

◎ 完成

画像18

3. 実行

ビルドしてアプリを実行すると、次のようにランチ情報が表示されます。

画像18


この記事が気に入ったらサポートをしてみませんか?