SwiftUI:发布对自定义形状的更新

SwiftUI: Publish updates to custom shape

在下面的 SwiftUI 代码中,我有 3 个主要组件:

TestView - 持有一个 TestClass 实例,显示 MyShape,并在点击时更新 TestClass 实例的计数。

TestClass - 一个简单的 class 来存储 count,它在更改时发布。

MyShape - 一个简单的形状,其位置(应该)随着 TestViewTestClass 实例的计数变化而变化。

问题是计数更新时MyShape的位置没有改变。

我知道计数正在更新,因为如果我在文本视图中显示计数,它会在点击时发生变化,因此当计数发生变化时,形状不会随着计数变化而更新。为什么形状没有更新/为什么发布者没有达到形状?

struct TestView: View {

    @ObservedObject var counter: TestClass = TestClass()
    
    var body: some View {
        ZStack {
            MyShape(counter: counter)
                .stroke(Color.red, lineWidth: 50)
                .onTapGesture {
                    counter.count += 1
                }
        }
    }
}



class TestClass: ObservableObject {
    @Published var count = 0
}



struct MyShape: Shape {
    
    var counter: TestClass
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: counter.count, y: counter.count))
        return path
    }
}

SwiftUI 仅在其属性更改时更新 ViewShape

现在,MyShape 在第一次渲染后永远不会更改其属性,因为 TestClass 是一个引用类型(即在这种情况下为 class)——SwiftUI 只看到属性是相同的,即使 TestClass 的内部属性之一已更改,也不会费心呈现 MyShape 的新版本。

View 中,我们可以使用 @ObservedObject 属性 包装器,以便 View 知道在 ObservableObject@Published 属性更改(就像您在 TestView 中所做的那样),但该包装器在 Shape.

中不可用

此处最简单的解决方案是传递 count 而不是 countercount 是一个 Int (即值类型),这意味着每当它发生变化时,SwiftUI 就会知道更新 Shape:

struct TestView: View {

    @ObservedObject var counter: TestClass = TestClass()
    
    var body: some View {
        ZStack {
            MyShape(count: counter.count)
                .stroke(Color.red, lineWidth: 50)
                .onTapGesture {
                    counter.count += 1
                }
        }
    }
}

class TestClass: ObservableObject {
    @Published var count = 10
}

struct MyShape: Shape {
    var count: Int
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: count, y: count))
        return path
    }
}

另一个可能更适合 SwiftUI 原则的替代解决方案是将您的模型更改为 struct。然后,可以将模型传递给 Shape,由于它是值类型,因此 Shape 将更新。当然,这是假设您的 real-world 模型更复杂并且具有 Shape 需要的多个 属性 —— 否则,保持最初的解决方案,即只公开它的内容需求(即 Int)可能是更好的需求。

struct TestView: View {
    @State var counter: TestStruct = TestStruct()
    
    var body: some View {
        ZStack {
            MyShape(counter: counter)
                .stroke(Color.red, lineWidth: 50)
                .onTapGesture {
                    counter.count += 1
                }
        }
    }
}

struct TestStruct {
    var count = 10
}

struct MyShape: Shape {
    var counter: TestStruct
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: counter.count, y: counter.count))
        return path
    }
}

注意:我更新了初始的count,这样一开始点击视图就更容易了