子ビューと親ビューの間にSwiftUI接続を確立しようとしています。子ビューをクリックして、親ビュー全体ではなく、タップされたビューのみを再描画したいと思います。
以下の現在の実装では、派生値があるため、ビューをクリックしたときにビューを再描画することはできません。
BindableObject
プロトコルをCustomColor
に追加してさまざまなシナリオを試しましたが、成功しませんでした。
class CustomColor: Identifiable {
let id = UUID()
var color: Color
init(color: Color) {
self.color = color
}
func change(to color: Color) {
self.color = color
}
}
class ColorStore: BindableObject {
var colors: [CustomColor] = [] {
didSet {
didChange.send(self)
}
}
var didChange = PassthroughSubject<ColorStore, Never>()
init() {
self.colors = Array.init(repeating: CustomColor(color: .red), count: 10)
}
}
struct ContentView: View {
@EnvironmentObject var colorStore: ColorStore
var body: some View {
NavigationView {
List {
ForEach(colorStore.colors) { color in
ColorShape(color: color)
}
}.navigationBarTitle(Text("Colors"))
}
}
}
struct ColorShape: View {
var color: CustomColor
var body: some View {
Button(action:
{ self.color.change(to: .blue) }
, label: {
ShapeView(shape: Circle(), style: color.color)
})
}
}
微妙な違いのある3つのバージョンを提供できます。それらのすべてが個々のボタンを切り替えて、モデル全体を維持します-ColorStore
varを同期します。色の配列の要素を追加および削除できます。また、配列要素をリストするために、Identifiable
に準拠しなくてもよいことに注意してください。
バージョン1.質問に最も近い:すべてのモデルはclasses
です。
class CustomColor: ObservableObject, Identifiable {
var didChange = PassthroughSubject<CustomColor, Never>()
let id = UUID()
var color: Color {
didSet {
objectWillChange.send()
}
}
init(color: Color) {
self.color = color
}
func change(to color: Color) {
self.color = color
}
}
class ColorStore: ObservableObject {
var didChange = PassthroughSubject<ColorStore, Never>()
var colors: [CustomColor] = [] {
didSet {
objectWillChange.send()
}
}
init() {
(0...10).forEach { _ in colors.append(CustomColor(color: .red)) }
}
}
struct ContentView: View {
@ObservedObject var colorStore: ColorStore = ColorStore()
var body: some View {
NavigationView {
List(colorStore.colors) { c in
ColorShape(color: c)
}
// will work without `Identifiable`
// List(colorStore.colors.indices, id: \.self) { c in
// ColorShape(color: self.colorStore.colors[c])
// }
.navigationBarTitle(Text("Colors"))
.navigationBarItems(leading:
Button(action: { self.colorStore.colors.append(CustomColor(color: .green)) }) {
Text("Add")
}, trailing:
Button(action: {
self.colorStore.colors.removeLast()
print(self.colorStore.colors)
}, label: { Text("Remove") }))
}
}
}
struct ColorShape: View {
@ObservedObject var color: CustomColor
var body: some View {
Button(action:
{ self.color.change(to: .blue)
print(self.color)
}
, label: {
Circle().fill(color.color)
})
}
}
バージョン2. CustomColor
はstructに書き直されました。
// No need for manual `ObservableObject, Identifiable` conformance
struct CustomColor /*: Identifiable */ {
// let id = UUID()
var color: Color
init(color: Color) {
self.color = color
}
mutating func change(to color: Color) {
self.color = color
}
}
class ColorStore: ObservableObject {
var didChange = PassthroughSubject<ColorStore, Never>()
// If `CustomColor` is a `struct` i.e. value type, we can populate array with independent values, not with the same reference by using `repeating:` init.
var colors: [CustomColor] = Array(repeating: CustomColor(color: .red), count: 10) {
didSet {
objectWillChange.send()
}
}
/* init() {
(0...10).forEach { _ in colors.append(CustomColor(color: .red)) }
} */
}
struct ContentView: View {
@ObservedObject var colorStore: ColorStore = ColorStore()
var body: some View {
NavigationView {
List {
// Strange, bu if we omit ForEach, we will get an error on element removal from array.
ForEach(colorStore.colors.indices, id: \.self)
{ c in
ColorShape(color: self.$colorStore.colors[c])
}
}
.navigationBarTitle(Text("Colors"))
.navigationBarItems(leading:
Button(action: { self.colorStore.colors.append(CustomColor(color: .green)) }) {
Text("Add")
}, trailing:
Button(action: {
self.colorStore.colors.removeLast()
print(self.colorStore.colors)
}, label: { Text("Remove") }))
}
}
}
struct ColorShape: View {
@Binding var color: CustomColor
var body: some View {
Button(action:
{ self.color.change(to: .blue)
print(self.color)
}
, label: {
Circle().fill(color.color)
})
}
}
バージョン3.メインモデルColorStore
とそのサブタイプCustomColor
は、構造体として書き直されます。 ObservableObject
に手動で準拠する必要はありません。
struct CustomColor /* : Identifiable */ {
// let id = UUID()
var color: Color
init(color: Color) {
self.color = color
}
mutating func change(to color: Color) {
self.color = color
}
}
struct ColorStore {
// If `CustomColor` is a `struct` i.e. value type, we can populate array with independent values, not with the same reference by using `repeating:` init.
var colors: [CustomColor] = Array(repeating: CustomColor(color: .red), count: 10)
}
struct ContentView: View {
@State var colorStore: ColorStore = ColorStore()
var body: some View {
NavigationView {
List{
ForEach(colorStore.colors.indices, id: \.self) { i in
return ColorShape(color: self.$colorStore.colors[i])
}
}
.navigationBarTitle(Text("Colors"))
.navigationBarItems(leading:
Button(action: { self.colorStore.colors.append(CustomColor(color: .green)) }) {
Text("Add")
}, trailing:
// Removing causes index out of bound error (bug?)
Button(action: {
self.colorStore.colors.removeLast()
print(self.colorStore.colors)}) {
Text("Remove") })
}
}
}
struct ColorShape: View {
@Binding var color: CustomColor
var body: some View {
Button(action: {
self.color.change(to: .blue)
print(self.color)
}) {
Circle().fill(color.color)
}
}
}
現在、特定の子ビューを更新する可能性はなく、期待することはできないと思います。 @StateプロパティまたはBindableオブジェクトを変更すると Swift UI セッションによるデータフロー-すべての変更はビュー階層を流れ、SwiftUIフレームワークはすべてのビューを比較しています変更されたものだけを再度レンダリングします。