web-dev-qa-db-fra.com

Comment mettre à jour une vue enfant unique dans SwiftUI?

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)
        })
    }

}

Here is the UI I have

3
Enzo Sterro

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)
        }
    }
}
0
Paul B

À 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é.

0
DenFav