Develop Apps for iOSをやってみる〜その6〜

Pocket

Develop Apps for iOSをやってみる 〜その6〜

はじめに

DX推進室の野上です。
前回 の続きです


どこから?

今回はここから続けます。
全体のやっと半分です。


最初に

今回も用意されたプロジェクトファイルがありますが、今までと同じように無視します。


データバインディングでデータを渡す

Edit Viewにバインディングデータを渡す

DetailViewからEditViewにバインディングされてデータを渡します。
編集が完了したら、DetailView側でもデータが変更されるようにしましょう。

EditViewのscrumDataを@Stateから@Bindingに変更します。
@BindingをつけるとView間でデータを共有することができます。

import SwiftUI

struct EditView: View {
    @Binding var scrumData: DailyScrum.Data
    @State private var newAttendee = ""
    var body: some View {
        List {
            Section(header: Text("Meeting Info")) {
                TextField("Title", text: $scrumData.title)
                HStack {
                    Slider(value: $scrumData.lengthInMinutes, in: 5...30, step: 1.0) {
                        Text("Length")
                    }
                    .accessibilityValue(Text("\(Int(scrumData.lengthInMinutes)) minutes"))
                    Spacer()
                    Text("\(Int(scrumData.lengthInMinutes)) minutes")
                        .accessibilityHidden(true)
                }
                ColorPicker("Color", selection: $scrumData.color)
                    .accessibilityLabel(Text("color picker"))
            }
            Section(header: Text("Attendees")) {
                ForEach(scrumData.attendees, id: \.self) { attendee in
                    Text(attendee)
                }
                .onDelete { indices in
                    scrumData.attendees.remove(atOffsets: indices)
                }
                HStack {
                    TextField("New Attendee", text: $newAttendee)
                    Button(action: {
                        withAnimation {
                            scrumData.attendees.append(newAttendee)
                        }
                        newAttendee = ""
                    }) {
                        Image(systemName: "plus.circle.fill")
                            .accessibilityLabel(Text("add attendee"))
                    }
                    .disabled(newAttendee.isEmpty)
                }
            }
        }
        .listStyle(InsetGroupedListStyle())
    }
}

struct EditView_Previews: PreviewProvider {
    static var previews: some View {
        EditView(scrumData: .constant(DailyScrum.data[0].data))
    }
}

これでEditView側で受け取る準備はできました。
次はDetailView側でデータの送り側の変更をしましょう。

@Stateプロパティをつけてdataという名前のDailyScrum.Data型の変数を作ります。
そのdataをEditボタンが押された際に、EditViewに渡されるようにしましょう。
またDoneボタンが押されて戻ってきた場合にscrumを更新する処理を呼び出します。
送り側のscrumもletから@Bindingに変更しておきましょう。

import SwiftUI

struct DetailView: View {
    @Binding var scrum: DailyScrum
    @State private var data: DailyScrum.Data = DailyScrum.Data()
    @State private var isPresented = false
    var body: some View {
        List {
            Section(header: Text("Meeting Info")) {
                NavigationLink(
                    destination: MeetingView()) {
                        Label("Start Meeting", systemImage: "timer")
                            .font(.headline)
                            .foregroundColor(.accentColor)
                            .accessibilityLabel(Text("start meeting"))
                    }
                HStack {
                    Label("Length", systemImage: "clock")
                        .accessibilityLabel(Text("meeting length"))
                    Spacer()
                    Text("\(scrum.lengthInMinutes) minutes")
                }
                HStack {
                    Label("Color", systemImage: "paintpalette")
                    Spacer()
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundColor(scrum.color)
                }
                .accessibilityElement(children: .ignore)
            }
            Section(header: Text("Attendees")) {
                ForEach(scrum.attendees, id: \.self) { attendee in
                    Label(attendee, systemImage: "person")
                        .accessibilityLabel(Text("person"))
                        .accessibilityValue(Text(attendee))
                }
            }
        }
        .listStyle(InsetGroupedListStyle())
        .navigationBarItems(trailing: Button("Edit") {
            isPresented = true
            data = scrum.data
        })
        .navigationTitle(scrum.title)
        .fullScreenCover(isPresented: $isPresented) {
            NavigationView {
                EditView(scrumData: $data)
                    .navigationTitle(scrum.title)
                    .navigationBarItems(leading: Button("Cancel") {
                        isPresented = false
                    }, trailing: Button("Done") {
                        isPresented = false
                        scrum.update(from: data)
                    })
            }
        }
    } 
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            DetailView(scrum: .constant(DailyScrum.data[0]))
        }
    }
}

ここまででDetailViewとEditViewの間でデータの共有はできましたが、まだbuildエラーが出ます。
他のファイルも修正が必要なためです。
次はScrumsViewとDetailView間のデータバインディングを設定します。
ScrumsViewのscrumsを@Bindingに変更します。
Previewの方もconstantで渡すようにします。
また、scrumsから一つずつのbindingを取り出すメソッドbindingを追加します。
DetailViewにはbindingされたscrumを渡すようにします。

import SwiftUI

struct ScrumsView: View {
    @Binding var scrums: [DailyScrum]

    var body: some View {
        List {
            ForEach(scrums) { scrum in
                NavigationLink(destination: DetailView(scrum: binding(for: scrum))) {
                    CardView(scrum: scrum)
                }
                .listRowBackground(scrum.color)
            }
        }
        .navigationTitle("Daily Scrums")
        .navigationBarItems(trailing: Button(action: {}) {
            Image(systemName: "plus")
        })
    }
    
    private func binding(for scrum: DailyScrum) -> Binding<DailyScrum> {
        guard let scrumIndex = scrums.firstIndex(where: { $0.id == scrum.id }) else {
            fatalError("Can't find scrum in array")
        }
        return $scrums[scrumIndex]
    }
}

struct ScrumsView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            ScrumsView(scrums: .constant(DailyScrum.data))
        }
    }
}

これで、ScrumsViewとDetailView間のデータ共有ができました。
同じようにScrumdingerAppとScrumsView間のデータバインディングを設定します。
@Stateをつけたscrumsを定義して、このデータにバインディングされるようにします。
ScrumsViewにはscrumsのバインディングを渡します。

import SwiftUI

@main
struct ScrumdingerApp: App {
    @State private var scrums = DailyScrum.data
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ScrumsView(scrums: $scrums)
            }
        }
    }
}

アプリケーションを実行してDetailViewからEditしてみてください。
タイトルや色、時間などが変更できるようになっていて、DetailViewに戻ってきても反映されているはずです。


ここまでのまとめ

  • @Bindingを用いてデータバインディングする
  • 大元となるデータから引き継いでいく

次回は状態とライフサイクルの管理です。

Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です