この節では2-5で作成したリストのプログラムをベースに, Appのデータを後から変更できるようにします. まず2-5までのコード作成が完了した状態をつくります.

  • SFSwiftUI03-1.swiftpm を開く (Xcode) (Playground)
    • 2-5を完了させるとできあがるコードとほぼ同様のものとなる
      • ContentView : リスト画面
      • MyApp : 起動時の処理
      • PhotoData : 写真データ構造体
      • SceneryDetail : 写真データの詳細画面
      • SceneryRow : 写真リストのビュー
    • ただし簡単のため, FoodAndDrink関連のデータと画面は除いている

コードの詳細は2-5-1を参考にしてください. 実行すると次のような画面の動きになります.

起動したとき

swiftui_3_1_1_1.png

リスト項目を押して画面遷移

swiftui_3_1_1_2.png

今回は, このコードをスタートにまずデータを変更できるように変更します.

  • ContentViewを開き, var photos@State var photos に変更する
  • ForEachに使うデータをphotosから$photosに変更する
    • 繰り返しで得られるデータがBinding型になる. 変数名も bind_photo にしてわかりやすくする
    • SceneryDetailにbind_photoを渡す
    • SceneryRowにbind_photoを直接渡せないため, bind_photo.wrappedValue を指定する. Bindingで包んだ中身(PhotoData型)を取り出せる

コード1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import SwiftUI

struct ContentView: View {
    @State var photos:[PhotoData] = [
        PhotoData( type:"景色", imageName:"CC0_01", title:"景色1", message:"森と山です" ),
        PhotoData( type:"景色", imageName:"CC0_04", title:"景色2", message:"きれいな湖です" ),
        PhotoData( type:"景色", imageName:"CC0_06", title:"景色3", message:"フィレンツェです" ),
        PhotoData( type:"景色", imageName:"CC0_07", title:"景色4", message:"ひまわりです" ),
        PhotoData( type:"景色", imageName:"CC0_10", title:"景色5", message:"海に日が沈みます" )
    ]
    
    var body: some View {
        List {
            Section( "景色写真" ) {
                ForEach( $photos ) { bind_photo in
                    NavigationLink( destination:{
                        SceneryDetail( data:bind_photo )
                    },
                    label: {
                        SceneryRow( data:bind_photo.wrappedValue )
                    })
                }
            }
        }
        .listStyle( .grouped )
        .navigationTitle( "写真集" )
    }
}

@Stateをつけたことでphotosは値を変更できるようになりました. しかしphotosが [PhotoData]型からState<[PhotoData]>型に変わった ことで, ForEachで得られる値も Binding になりました.

遷移先のSceneryDetailにも変更できるこの値を渡したいので, SceneryDetailも@Bindingを追加して受け取れるようにします. またせっかくのBinding型で値を変更できるので, 編集できるような工夫もしてみます.

  • SceneryDetailを開き, var photoを @Binding var photo に変更する

    • 値が変更できるようになり, かつBindingへの変更は大元のStateにも伝わるようになる
  • タイトルを編集できるようText( data.title )を TextField( “タイトル”, text:$data.title ) に変更する

    • TextFieldには, Binding型を渡す必要があるため, $data.title を渡す

コード2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import SwiftUI

struct SceneryDetail : View {
    @Binding var data:PhotoData

    var body: some View {
        VStack( alignment:.leading ) {
            Image( data.imageName )
            .resizable()
            .scaledToFit()
            .padding( .bottom, 20 )
            
            TextField( "タイトル", text:$data.title )
            .font( .title2 )
            .padding( .bottom, 20 )
            
            Text( data.message )
            .padding( .bottom, 20 )
            
            Spacer()
        }
        .padding()
        .navigationTitle( data.title )
    }
}

dataに@Bindingを追加することで, ContentViewの@Stateの値を受け取れるようになりました. またdataのタイトル表示をTextFieldに変更したため, 値が変更できるようになりました. SceneryDetailでタイトルを変更すると, 元のリストの値も変わっていることがわかります.

SceneryDetailでタイトルを変更

swiftui_3_1_1_3.png

戻ったリストでタイトル変更が反映されている

swiftui_3_1_1_4.png