How to update a single child view in SwiftUI?

前端 未结 3 392
一向
一向 2021-02-06 17:01

I am trying to establish a SwiftUI connection between the child view and the parent view. By clicking on any child view, I want to redraw only the view that has been tapped, not

3条回答
  •  佛祖请我去吃肉
    2021-02-06 17:49

    I can offer three versions with subtle differences. All Of them toggle individual buttons and keep the whole model - ColorStore var in sync. Allows adding and removing of elements in the array of colours. Also note, we can go without Identifiable conformance for array elements to list them.

    Version 1. The closest to the question: all models are classes.

    class CustomColor: ObservableObject, Identifiable {
    
        var didChange = PassthroughSubject()
    
        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()
    
        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. The CustomColor is rewritten as 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()
        // 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. The main model ColorStore and it's subtype CustomColor are rewritten as structs. No need to manually conform to 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)
            }
        }
    }
    

提交回复
热议问题