初めてのSwift5 その9

画面間のデータ連携しよう・その2

前回はかなり長くなってしまったのでぐったりしている人もいるのではないでしょうか?
前回は、グローバル変数を使ったデータ連携方法でした。

今回は、NavigationLinkからのデータ連携を考えてみたいと思います。
ただ最初から作成すると長くなるので前回のソースコードを少し修正して実現を考えてみましょう。

画像1

今回は前回の画面に、もう一枚追加して、リストに表示されているアイテムを選択すると、選択されたアイテム情報を今回追加する画面に表示するというものです。

ゲームであれば装備品をリストから選んで詳細情報を見たり、レストランのメニューであれば、料理のカロリーやアレルゲンの情報が表示させたり色々考えられますね。

スクリーンショット 2020-05-14 10.14.57

前回作業したItemListView.swiftを修正して、イメージも追加できるようにします。トグルボタンを交互にスライドさせることで追加する絵を変更することができることとします。

そして、追加ボタンを押すと前回追加されたことがわかりにくかったので、追加されたよっていうメッセージも上部に表示することとします。
このメッセージは1秒後にテキストが消える仕様とします。

なんかこう、文章で書くとモリモリいれている感じがすると思いますが単純に絵が追加できるようになって、その絵も二種類用意してそれぞれ設定でき且つ追加したらメッセージが表示されるようになり、自動的にそのメッセージも一秒後には消える、そういったものです。

今回ここで全く新しい技術は「タイマー」ぐらいですね。
それ以外は今まで取り扱ってきた記事の中にあるものばかりです。

スクリーンショット 2020-05-14 10.25.15

今回新しく追加する画面は、上記のようなものです。
リストから選択したそれぞれのアイテムに入っているデータを取り出してその内容を表示するというものです。

以上が今回の修正範囲です。
長い説明になりましたが、やっていることはシンプルですね。

アイテム構造体の変更

画面は、それぐらいですが構造体の方もイメージ情報を管理する為にメンバー変数を追加する必要があります。

//  プログラムで管理したいデータ構造体
struct ItemInfo: Codable, Equatable, Identifiable {
   //  管理する為にID
   //  Identifiableで必要。
   //  説明すると長いのでとりあえずつけとばいいかぐらいのノリでいいと思います
   var id: UUID
   //  ----- ここから下が実際にプログラムで使用するメンバー変数です -----
   var name: String    //  名前。実質これだけしか管理してません(笑)まあ分かりやすくする為にシンプルにしています
   //  イメージ名です
   var img: String
}

前回と変更がある部分は、イメージ名を持てるようにしたところです。
具体的には

var img: String

この部分のみを追加してあります。
これでイメージを管理することができるようになりました。
次にイメージデータをプロジェクトにいれていきます。

プロジェクトにイメージデータ追加

スクリーンショット 2020-05-14 10.31.13

Assets.xcassetsに追加したいイメージをFinderからドラッグアンドドロップします。名前はそれぞれ「img001」「img002」にして下さい。
これでイメージの方は準備OKです。

アイテム情報を表示する画面を作る

XCodeのFileメニュー→New→Fileを選択してSwiftUIを追加しファイル名は
frmTestView.swiftとします。
そして以下のコードを追加します。

struct frmTestView: View {
   //  ContentViewから渡されて来るアイテム構造体だよ。
   let item : ItemInfo
   var body: some View {
       //  縦に並べるぜ
       VStack{
           //  アイテムのイメージ表示しちゃうよ
           Image(item.img)
               .resizable()
               .frame(width: 512, height: 256)
           //  アイテムテキストも表示
           Text("受け渡されたデータ >\(item.name)")
       }
   }
}

この画面は、後で説明するContentView.swiftから渡されてくるItemInfo構造体の内容を表示するというシンプルなものです。
つまり、ContentView.swiftのリストから選んだアイテムの情報を表示しているだけです。
シンプルでしょ?

リストからアイテム情報を表示画面に渡す

今回は、リストに表示されているアイテム情報を選択すると表示画面に遷移しその個別アイテムの情報を表示するという形に持っていく必要があります。

ということで、以下のコードを追加します。

var body: some View {
   NavigationView{
       //  縦に並べちゃうぞ!
       VStack{
           //  リスト表示するぜっ
           List{
               //  二次元配列のデータ全部ここに表示するまでグルグル回るよ
               ForEach(lstitems.item){ item in
                   HStack{

                       NavigationLink(destination: frmTestView(item: item)){
                           Text(item.name)
                       }
                   }
               }
           }
       }
   }
}

前回と異なるところは、VStackの上にNavigationViewで囲ったところにあります。
遷移した画面はこの囲った部分に出すという意味ですね。

次に変更したのは、HStackで囲った中を変更しています。
NavigationLinkをここにセットし、リストアイテムが選択されたら選択されたアイテム情報を画面に表示させる為ですね。

ここで気がついたと思いますが、ここで先程作成した画面に個別のアイテム構造体を画面に渡しているのがわかると思います。
これもシンプルでしょ?

アイテム情報を追加するロジックを変更

ここが少し複雑ですが、ほんの少しです。
前回とどう変わったのかソースをみていきましょう。

struct ItemListView: View {
   //  グローバル変数を参照するよって意味ぐらいに覚えて下さい
   @EnvironmentObject var lstitems: lstItems
   //  追加した時のメッセージアナウンス
   @State var Msg: String = ""
   //  追加イメージフラグ
   @State var Flag : Bool
   var body: some View {
       //  縦に並べまっせ〜!
       VStack{
           Text(self.Msg)
           //  トグルを追加
           Toggle(isOn: $Flag) {
               if( self.Flag ){
                   //  img001を表示
                   Image("img001")
                   .resizable()
                       .frame(width: 128, height: 64)
               }
               else{
                   //  img002を表示
                   Image("img002")
                   .resizable()
                       .frame(width:128, height:64)
               }
           }
           //  追加ボタン
           Button(action:{
               //  タイマーを追加して、追加メッセージは1秒後に非表示にします
               Timer.scheduledTimer(withTimeInterval: 1, repeats: false){ _ in
                   //  1秒後、メッセージは消します
                       self.Msg = ""
                   }
               //  ここでグローバル変数に値を追加しています
               self.lstitems.add(obj: ItemInfo(id:UUID(),name: "これはテストです(\(self.lstitems.item.count + 1)個目)", img: self.Flag ? "img001" : "img002") )
               //  上記で追加されているので数は増えてるので+1は省略
               self.Msg = String("これはテストです(\(self.lstitems.item.count)個目)を追加しました")
               
           }){
               //  ボタンのキャプション
               Text("追加")
           }
       }
   }
}

全体はこんな感じです。
今回追加した部分は、色々あります。

//  追加した時のメッセージアナウンス
@State var Msg: String = ""

この部分は、追加ボタンを押した時メッセージ表示する変数になります。
ここに表示したいテキストをセットすれば画面に出力されるようになります。

//  追加イメージフラグ
@State var Flag : Bool

次はトグルボタンをスライドさせた時の状態を管理するフラグです。
今回はめんど・・・じゃなくて二枚の絵だけですのでどちらかを管理します。
このフラグ管理は、Swiftに丸投げできるので我々はその状態だけを判断すればいいのです。

//  トグルを追加
Toggle(isOn: $Flag) {
    if( self.Flag ){
        //  img001を表示
        Image("img001")
        .resizable()
        .frame(width: 128, height: 64)
    }
    else{
        //  img002を表示
          Image("img002")
          .resizable()
          .frame(width:128, height:64)
    }
}

上記のコードで、それぞれtrueかfalseかによって表示する内容を変更してあります。

もっともごちゃついているのはボタンですね。

//  追加ボタン
Button(action:{
    //  タイマーを追加して、追加メッセージは1秒後に非表示にします
    Timer.scheduledTimer(withTimeInterval: 1, repeats: false){ _ in
        //  1秒後、メッセージは消します
        self.Msg = ""
    }
    //  ここでグローバル変数に値を追加しています
    self.lstitems.add(obj: ItemInfo(id:UUID(),name: "これはテストです(\(self.lstitems.item.count + 1)個目)", img: self.Flag ? "img001" : "img002") )
    //  上記で追加されているので数は増えてるので+1は省略
    self.Msg = String("これはテストです(\(self.lstitems.item.count)個目)を追加しました")
               
}){
    //  ボタンのキャプション
    Text("追加")
}

ここで新しく登場したのがタイマーですね。
ボタンを押した時に、タイマーイベントをセットして1秒後にメッセージを削除するようにします。
このあたりもシンプルでしょ?

次にアイテムデータを追加する部分にも修正を加えています。
そう、イメージ部分ですね。

img: self.Flag ? "img001" : "img002"

と書いてますが、一寸見慣れない人がでてくるかもしれません。
これはどういう意味なのかというと、self.Flag(トグルボタンの状態ですね)
がflaseの時とtrueの時で追加するイメージが異なるということです。

self.Flagがtrueの時は ? の次の値である、img001がセットされます。
逆にfalseの時は : の向こう側にあるimg002をセットします、という意味です。

他の言語である省略形なので、最初は混乱すると思いますがそういったものだと思って下さい。

以上が今回の追加した内容です。

それでは実行してみましょう

追加したコードはそれほどないのに何だかまた長くなっちゃいましたね。
では実際に動作確認してみましょう。

スクリーンショット 2020-05-14 11.13.53

起動直後は何も変わりません。
またこの画面については見た目はわからないと思います。

スクリーンショット 2020-05-14 11.14.43

アイテム追加タブを選択すると画面が変わっていることがわかると思います。
前回にはなかった、イメージの表示とトグルボタンがでていると思います。
トグルボタンを変更してみましょう。

スクリーンショット 2020-05-14 11.16.12

画像が切り替わったのがわかると思います。
次に追加ボタンを押してみましょう

スクリーンショット 2020-05-14 11.17.55

ボタンを押すと、追加された内容が表示されるようになっていますね。
すぐに表示は消えると思います。

次に、リスト表示画面に戻りましょう。

スクリーンショット 2020-05-14 11.19.41

追加されたアイテムの横に「>」がありますね。
ナビゲーションリンクが設定されていることが確認できます。
では選択してみましょう。

スクリーンショット 2020-05-14 11.20.43

こんな感じでイメージとテキストが表示されるのが確認できました!
因みに画像はいき付けのバイク屋(残念ながら閉店しちゃいました)に飾っていた、「わんおふ -one off-」のスクーターです。(数年前になるのか。)
OVAなので、放送されないから買うしかみれないですね。

ブルーレイ、DVDかなにかのプレゼント企画だったみたいですが、私がバイク屋さんにいったその日に声優さんがお店にきてイベントしてたそうで、サインがはいったヘルメットが見えますね。

当時、当選された方はラッキーですね!今も乗られているんですかね?

すみません、余談が長いですね。
興味があれば別の記事で書いてみたいと思います。

如何でしたか?
今回追加したソースコードは大したことはありません。
ほぼ説明です(笑)

それでは、また。

今回の全コード

※前回のコードと合わせてみてください。今回は修正したソースコードしか載せていません。
イメージについては、ご自身で適当なものご用意ください。

lstData.swift

//
//  lstData.swift
//  HogeHoge7
//
//  Created by melon on 2020/05/12.
//  Copyright © 2020 melon-group. All rights reserved.
//

import SwiftUI

//  プログラムで管理したいデータ構造体
struct ItemInfo: Codable, Equatable, Identifiable {
   //  管理する為にID
   //  Identifiableで必要。
   //  説明すると長いのでとりあえずつけとばいいかぐらいのノリでいいと思います
   var id: UUID
   //  ----- ここから下が実際にプログラムで使用するメンバー変数です -----
   var name: String    //  名前。実質これだけしか管理してません(笑)まあ分かりやすくする為にシンプルにしています
   //  イメージ名です
   var img: String
}

ItemListView.swift

//
//  ItemListView.swift
//  HogeHoge7
//
//  Created by melon on 2020/05/12.
//  Copyright © 2020 melon-group. All rights reserved.
//

import SwiftUI

//  作成した後気がついたけど名前と実際やっていることが一致していないことは内緒(笑)
//  ごめん、面倒なのでそのままにしてます。
struct ItemListView: View {
   //  グローバル変数を参照するよって意味ぐらいに覚えて下さい
   @EnvironmentObject var lstitems: lstItems
   //  追加した時のメッセージアナウンス
   @State var Msg: String = ""
   //  追加イメージフラグ
   @State var Flag : Bool
   var body: some View {
       //  縦に並べまっせ〜!
       VStack{
           Text(self.Msg)
           //  トグルを追加
           Toggle(isOn: $Flag) {
               if( self.Flag ){
                   //  img001を表示
                   Image("img001")
                   .resizable()
                       .frame(width: 128, height: 64)
               }
               else{
                   //  img002を表示
                   Image("img002")
                   .resizable()
                       .frame(width:128, height:64)
               }
           }
           //  追加ボタン
           Button(action:{
               //  タイマーを追加して、追加メッセージは1秒後に非表示にします
               Timer.scheduledTimer(withTimeInterval: 1, repeats: false){ _ in
                   //  1秒後、メッセージは消します
                       self.Msg = ""
                   }
               //  ここでグローバル変数に値を追加しています
               self.lstitems.add(obj: ItemInfo(id:UUID(),name: "これはテストです(\(self.lstitems.item.count + 1)個目)", img: self.Flag ? "img001" : "img002") )
               //  上記で追加されているので数は増えてるので+1は省略
               self.Msg = String("これはテストです(\(self.lstitems.item.count)個目)を追加しました")
               
           }){
               //  ボタンのキャプション
               Text("追加")
           }
       }
   }
}

struct ItemListView_Previews: PreviewProvider {
   static var previews: some View {
       /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
   }
}

ContentView.swift

//
//  ContentView.swift
//  HogeHoge7
//
//  Created by melon on 2020/05/12.
//  Copyright © 2020 melon-group. All rights reserved.
//

import SwiftUI

//  この画面で、グローバル変数に追加された構造体をリスト表示してます。
struct ContentView: View {
   @EnvironmentObject var lstitems: lstItems
   var body: some View {
       NavigationView{
           //  縦に並べちゃうぞ!
           VStack{
               //  リスト表示するぜっ
               List{
                   //  二次元配列のデータ全部ここに表示するまでグルグル回るよ
                   ForEach(lstitems.item){ item in
                       HStack{

                           NavigationLink(destination: frmTestView(item: item)){
                               Text(item.name)
                           }
                       }
                   }
               }
           }
       }
   }
}

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
       ContentView()
   }
}

frmTestView.swift

//
//  frmTestView.swift
//  HogeHoge7
//
//  Created by melon on 2020/05/13.
//  Copyright © 2020 melon-group. All rights reserved.
//

import SwiftUI

struct frmTestView: View {
   //  ContentViewから渡されて来るアイテム構造体だよ。
   let item : ItemInfo
   var body: some View {
       //  縦に並べるぜ
       VStack{
           //  アイテムのイメージ表示しちゃうよ
           Image(item.img)
               .resizable()
               .frame(width: 512, height: 256)
           //  アイテムテキストも表示
           Text("受け渡されたデータ >\(item.name)")
       }
   }
}

いいなと思ったら応援しよう!