Avatar
Avatar
We
SwiftUIについてご質問させてください。 以下コードはTODOリストを実装したものです。TodoItemには完了/未完了を表すチェックボックスがあり、完了した項目は下になるようソートしています。 ですがObservableObjectがネストしているためチェックボックスの変更通知がContentViewまで届かず、リストが再描画されません。 この問題についてはobjectWillChangeとコールバック関数を組み合わせて解決しましたが、どうもSwiftUIの設計思想に反している気がします。 もっとSwiftの流儀に則ったうまい解決方法はありますでしょうか。 import SwiftUI struct ContentView: View { @StateObject var viewModel = TodoList() var body: some View { List { Button("Add Item") { viewModel.add() } ForEach(viewModel.items.sorted { !$0.completed && $1.completed }) { TodoItemView(viewModel: $0) } } } } class TodoList: ObservableObject { @Published var items: [TodoItem] = [] func add() { items.append(TodoItem(text: "item: \(items.count)") { self.objectWillChange.send() }) } } class TodoItem: ObservableObject, Identifiable { @Published var completed = false { didSet { onChanged() } } @Published var text: String private let onChanged: () -> Void init(text: String, onChanged: @escaping () -> Void) { self.text = text self.onChanged = onChanged } } struct TodoItemView: View { @StateObject var viewModel: TodoItem var body: some View { HStack { Button(action: { viewModel.completed.toggle() }, label: { Image(systemName: viewModel.completed ? "checkmark.square.fill" : "square") }) TextField("", text: $viewModel.text) } } }
子View(TodoItemView)でデータを変更したい場合は、Bindingを使用するのがSwiftUIでは使いやすいと思います。 Bindingするもの(TodoItem)はclassではなく、structでないとUIが更新されません。 objectWillChangeは使用場面は少ないと思います。 delegateなので値が変わるなどの際には、使用する機会があるかもしれません。 struct ContentView: View { @StateObject var viewModel = TodoList() var body: some View { List { Button("Add Item") { viewModel.add() } ForEach($viewModel.items.sorted { !$0.wrappedValue.completed && $1.wrappedValue.completed }) { TodoItemView(item: $0) } } } } class TodoList: ObservableObject { @Published var items: [TodoItem] = [] func add() { items.append(TodoItem(text: "item: \(items.count)")) } } struct TodoItem: Identifiable { let id = UUID() var completed = false var text: String init(text: String) { self.text = text } } struct TodoItemView: View { @Binding var item: TodoItem var body: some View { HStack { Button( action: { item.completed.toggle() }, label: { Image(systemName: item.completed ? "checkmark.square.fill" : "square") } ) TextField("", text: $item.text) } } } (edited)