1

私の質問がばかげている場合は申し訳ありませんが、私はプログラミングの初心者です。ビュー モデルの配列から生成されたリストから詳細ビューへのナビゲーション リンクがあります。詳細ビューで、タップした要素のプロパティの 1 つを変更できるようにしたいのですが、これを行う方法がわかりません。うまく説明できなかったと思うので、ここにコードを示します。

// model
struct Activity: Identifiable {
    var id = UUID()
    var name: String
    var completeDescription: String
    var completions: Int = 0
}

// view model
class ActivityViewModel: ObservableObject {
    @Published var activities: [Activity] = []
}

// view
struct ActivityView: View {
    @StateObject var viewModel = ActivityViewModel()
    @State private var showingAddEditActivityView = false
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(viewModel.activities, id: \.id) {
                        activity in
                        NavigationLink(destination: ActivityDetailView(activity: activity, viewModel: self.viewModel)) {
                            HStack {
                                VStack {
                                    Text(activity.name)
                                    Text(activity.miniDescription)
                                }
                                Text("\(activity.completions)")
                            }
                        }
                    }
                }
            }
            .navigationBarItems(trailing: Button("Add new"){
                self.showingAddEditActivityView.toggle()
            })
            .navigationTitle(Text("Activity List"))
        }
        .sheet(isPresented: $showingAddEditActivityView) {
            AddEditActivityView(copyViewModel: self.viewModel)
        }
    }
}

// detail view
struct ActivityDetailView: View {
    @State var activity: Activity
    @ObservedObject var viewModel: ActivityViewModel
    
    var body: some View {
        VStack {
            Text("Number of times completed: \(activity.completions)")
            Button("Increment completion count"){
                activity.completions += 1
                updateCompletionCount()
            }
            Text("\(activity.completeDescription)")
        }
    }
    func updateCompletionCount() {
         var tempActivity = viewModel.activities.first{ activity in activity.id == self.activity.id
         }!
        tempActivity.completions += 1
    }
}

// Add new activity view (doesn't have anything to do with question)

struct AddEditActivityView: View {
    @ObservedObject var copyViewModel : ActivityViewModel
    @State private var activityName: String = ""
    @State private var description: String = ""
    var body: some View {
        VStack {
            TextField("Enter an activity", text: $activityName)
            TextField("Enter an activity description", text: $description)
            Button("Save"){
                // I want this to be outside of my view
                saveActivity()
            }
        }
    }
    func saveActivity() {
        copyViewModel.activities.append(Activity(name: self.activityName, completeDescription: self.description))
        print(copyViewModel.activities)
    }
}

詳細ビューでは、その特定のアクティビティの完了カウントを更新しようとしており、ビュー モデルを更新しています。上記で試した方法はおそらく意味がなく、明らかに機能しません。私は試したことを示すためにそれを残しました。

助けや洞察をありがとう。

4

2 に答える 2

2

問題はここにあります:

struct ActivityDetailView: View {
    @State var activity: Activity
    ...

これは@Binding、変更が親ビューに反映されるために必要です。in 全体を渡す必要もありませviewModel@Binding

// detail view
struct ActivityDetailView: View {
    @Binding var activity: Activity /// here!
    
    var body: some View {
        VStack {
            Text("Number of times completed: \(activity.completions)")
            Button("Increment completion count"){
                activity.completions += 1
            }
            Text("\(activity.completeDescription)")
        }
    }
}

しかし、どうやってバインディングを取得するのでしょうか? iOS 15 を使用している場合は、次のように直接ループできます$viewModel.activities

/// here!
ForEach($viewModel.activities, id: \.id) { $activity in
    NavigationLink(destination: ActivityDetailView(activity: $activity)) {
        HStack {
            VStack {
                Text(activity.name)
                Text(activity.miniDescription)
            }
            Text("\(activity.completions)")
        }
    }
}

また、iOS 14 以下の場合は、代わりにインデックスをループする必要があります。しかし、それは機能します。

/// from https://stackoverflow.com/a/66944424/14351818
ForEach(Array(zip(viewModel.activities.indices, viewModel.activities)), id: \.1.id) { (index, activity)  in
    NavigationLink(destination: ActivityDetailView(activity: $viewModel.activities[index])) {
        HStack {
            VStack {
                Text(activity.name)
                Text(activity.miniDescription)
            }
            Text("\(activity.completions)")
        }
    }
}
于 2021-09-10T18:56:47.983 に答える
1

tempActivity の値を変更してインクリメントするため、メインの配列やデータ ソースには影響しません。

ビュー モデル内に更新関数を 1 つ追加し、ビューから呼び出すことができます。

ビュー モデルは、この更新を担当します。

class ActivityViewModel: ObservableObject {
    @Published var activities: [Activity] = []
    
    func updateCompletionCount(for id: UUID) {
        if let index = activities.firstIndex(where: {$0.id == id}) {
            self.activities[index].completions += 1
        }
    }
}
struct ActivityDetailView: View {
    var activity: Activity
    var viewModel: ActivityViewModel
    
    var body: some View {
        VStack {
            Text("Number of times completed: \(activity.completions)")
            Button("Increment completion count"){
                updateCompletionCount()
            }
            Text("\(activity.completeDescription)")
        }
    }
    func updateCompletionCount() {
        self.viewModel.updateCompletionCount(for: activity.id)
    }
}

それ以上のアクションがない場合、詳細ビューに @State または @ObservedObject は必要ありません。

于 2021-09-10T18:55:55.687 に答える