如何在SwiftUI中更新单个子视图?

时间:2019-06-08 12:58:13

标签: swiftui

我正在尝试在子视图和父视图之间建立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)
        })
    }

}

Here is the UI I have

3 个答案:

答案 0 :(得分:0)

目前无法更新特定的子视图,我认为这是无法预期的。 正如在Data flow Through Swift UI会话中所讲的,一旦您更改@State属性或Bindable对象-所有更改将向下流过视图层次结构,SwiftUI框架将比较所有视图并仅呈现已更改的内容。

答案 1 :(得分:0)

我想我已经找到了解决方案。 第一个问题是我通过重复相同的元素而不是添加独立的元素来初始化颜色数组。

更重要的是,CustomColor本身应该具有BindableObject一致性,而不是模型一致性(我们不更改颜色数组,而是更改每种颜色)。 最后,我们不需要将对象包装在 ForEach 元素中(以这种方式释放可重用性),而是将它们放在 List 元素中。

使用此实现,将仅重绘已更改的视图,而不是整个集合。

代码如下:

class CustomColor: BindableObject, Identifiable {

    var didChange = PassthroughSubject<CustomColor, Never>()

    let id = UUID()
    var color: Color {
        didSet {
            self.didChange.send(self)
        }
    }

    init(color: Color) {
        self.color = color
    }

    func change(toColor color: Color) {
        self.color = color
    }

}

class ColorStore {

    var colors: [CustomColor] = []

    init() {
        (0...10).forEach { _ in colors.append(CustomColor(color: .red)) }
    }

}


struct ContentView: View {

    let colorStore: ColorStore

    var body: some View {
        NavigationView {
            List(colorStore.colors) { color in
                ColorShape(color: color)
            }.navigationBarTitle(Text("Colors"))
        }
    }

}

struct ColorShape: View {

    @ObjectBinding var color: CustomColor

    var body: some View {
        Button(action: { self.color.change(toColor: .blue) }, label: {
            ShapeView(shape: Circle(), style: color.color)
        })
    }

}

答案 2 :(得分:0)

我可以提供三个版本,两者之间有细微的差别。 它们全部切换单个按钮,并使整个模型-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)
        }
    }
}