J'essaie d'établir une connexion SwiftUI entre la vue enfant et la vue parent. En cliquant sur une vue enfant, je souhaite redessiner uniquement la vue qui a été tapée, pas la vue parent entière.
L'implémentation actuelle ci-dessous ne vous permet pas de redessiner la vue lorsque vous cliquez dessus, car elle a une valeur dérivée.
J'ai essayé différents scénarios en ajoutant le protocole BindableObject
à CustomColor
, mais sans succès.
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)
})
}
}
Je peux proposer trois versions avec de subtiles différences. Chacun d'eux fait basculer les boutons individuels et garde le modèle entier - ColorStore
var synchronisé. Permet d'ajouter et de supprimer des éléments dans le tableau de couleurs. Notez également que nous pouvons nous passer de la conformité Identifiable
pour que les éléments du tableau les répertorient.
Version 1. Le plus proche de la question: tous les modèles sont 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)
})
}
}
Version 2. Le CustomColor
est réécrit en 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)
})
}
}
Version 3. Le modèle principal ColorStore
et son sous-type CustomColor
sont réécrits sous forme de structures. Pas besoin de se conformer manuellement à 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)
}
}
}
À l'heure actuelle, il n'y a aucune possibilité de mettre à jour la vue enfant spécifique et on ne peut pas s'attendre à ce que je pense. Comme cela a été dit sur Flux de données à travers Swift UI session une fois que vous modifiez la propriété @State ou l'objet Bindable - toutes les modifications descendent dans la hiérarchie des vues et le cadre SwiftUI compare toutes les vues et le rendu seulement ce qui a changé.