如果@Bindig对象属性已更改,如何刷新视图?

时间:2020-07-05 09:02:35

标签: swift binding swiftui

我有两个班级:

class Glyph: ObservableObject {
    ...
    @Published var manager: CoordinatesManager {
        didSet {
            print ("MANAGER DID SET IN GLYPH, \(manager.currentCoordinates)")
        }
    }

    @Published var elements: [ MultidimensionalPoints ]

    var path: Path {
        var resultPath = Path()
        if self.elements.count > 1 {
            //count path, path elements depends on manager.currentCoordinates
            }
        }
        return resultPath
    }

}

class CoordinatesManager {
    ...
    @Published var currentCoordinates: [ Coordinate ]
    ...
}

现在我尝试构建SwiftUI视图层次结构。在一个视图中,我需要显示Glyph对象,在另一个视图中,需要显示一些滑块以更改manager.currentCoordinates。当然,每个更改都应在第一视图中反映出来,因为它更改了glyph.manager.currentCoordinates

public struct ContentView: View
{
    @EnvironmentObject var glyph: Glyph
    
    var body: some View {
        NavigationView {
            GlyphView()
            VStack {
                CoordinatesView(coordinates: $glyph.manager.currentCoordinates)
                Text ("updated manager coordinates\(glyph.manager.currentCoordinates.description)").controlSize(.mini)
            }
        }
    }
}

CoordinatesView没什么特别的:只是滑动条就可以了。 glyph.manager didSet打印更改的值。

MANAGER DID SET IN GLYPH, ["width" <min>, "weight" <min>, "contrast" <min>]
MANAGER DID SET IN GLYPH, ["width" 1.72, "weight" <min>, "contrast" <min>]
MANAGER DID SET IN GLYPH, ["width" 3.45, "weight" <min>, "contrast" <min>]
....

GlyphView:

public struct GlyphView: View
{       
    @EnvironmentObject var glyph: Glyph
    
    public var body: some View {
        ZStack {
            // This is called only when i change View size
            // GlyphShape generates some Shape based on Glyph.currentState
            GlyphShape(glyph: glyph).stroke()
            Text("Overlay for test")
        }
    }
}

此视图应该对glyph.manager.currentCoordinates 中的更改做出反应,但是没有,我也不知道该怎么做。 仅当我更改视图大小时发生反应(通过鼠标)

struct GlyphShape: Shape

{
    @ObservedObject var glyph: Glyph
    
    func path(in rect: CGRect) -> Path {
        let glyphPath = glyph.path
        ... // transform
        print ("MAKING SHAPE @\(glyph.manager.currentCoordinates) \(resultPath)")
        return resultPath
    }
}

每次我在GlyphShape的导航器视图func path(in: rect)上方滚动(而不是移动)光标时,都会触发并在终端中打印实际的manager.currentCoordinates和新路径。

MAKING SHAPE @["width" <min>, "weight" <min>, "contrast" 29.94] TransformedShape<Path>(shape: 50 70 m 158.264 295.569 l 150 100 l 650 410 l, transform: __C.CGAffineTransform(a: 1.0433333333333332, b: 0.0, c: 0.0, d: 1.0433333333333332, tx: 0.0, ty: 0.0)) CCC
MAKING SHAPE @["width" <min>, "weight" <min>, "contrast" 29.94] TransformedShape<Path>(shape: 50 70 m 158.264 295.569 l 150 100 l 650 410 l, transform: __C.CGAffineTransform(a: 1.0433333333333332, b: 0.0, c: 0.0, d: 1.0433333333333332, tx: 0.0, ty: 0.0)) CCC

路径已重新定义,但视图未刷新。

我应该如何告诉Glyph manager.coordinates已更改并将其反映在GlyphView中?

这是一个简化的Playground文件。

import AppKit
import PlaygroundSupport
import SwiftUI

let nibFile = NSNib.Name("MyView")
var topLevelObjects : NSArray?

Bundle.main.loadNibNamed(nibFile, owner:nil, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as! Array<Any>).filter { $0 is NSView }

// Present the view in Playground
PlaygroundPage.current.liveView = views[0] as! NSView


protocol AxisProtocol {
    var name: String {get}
    var bounds: ClosedRange<Double> {get}
}
struct Axis: AxisProtocol {
    var name: String
    var bounds: ClosedRange<Double>
}

struct Coordinate<Axis:AxisProtocol> {
    var axis: Axis
    var at: Double
}

class CoordinatesManager {
    @Published var currentCoordinates: [ Coordinate<Axis> ]
    init (at: [Coordinate<Axis>]) {
        self.currentCoordinates = at
    }
}

class ItsCompliacated {
    func getSomeValue(at: [Coordinate<Axis>]) -> NSPoint {
        //Counting this value is rather comlicated thing...
        return NSPoint(x: Double.random(in: 0...500), y: Double.random(in: 0...500))
    }
}

class Glyph: ObservableObject {
    @Published var manager: CoordinatesManager {
        didSet {
print (manager)
}
    }
    var elements: [ ItsCompliacated ] = []
    var path: Path {
        var result = Path()
        if elements.count > 1 {
            result.addLines(elements.map({$0.getSomeValue(at: manager.currentCoordinates)}))
        }
        return result
    }
    
    init (elements:[ItsCompliacated], manager:CoordinatesManager) {
        self.manager = manager
        self.elements = elements
    }
}

let x = Axis(name: "x", bounds: 0...1000)
let y = Axis(name: "y", bounds: 0...1000)
let z = Axis(name: "z", bounds: 0...1000)

let cx = Coordinate(axis: x, at: 100)
let cy = Coordinate(axis: y, at: 200)
let cz = Coordinate(axis: z, at: 200)

let manager = CoordinatesManager(at: [cx, cy, cz])
let glyph = Glyph(elements: Array(repeating: ItsCompliacated(), count: 300), manager: manager)

public struct NavigatorView: View
    
{
    @EnvironmentObject var glyph: Glyph
    
    public var body: some View {
        ZStack {
            GlyphShape(glyph: glyph).stroke()
        }
    }
}
public struct CoordinateView : View {
    @Binding var coordinate: Coordinate<Axis>
    public var body : some View {
        VStack (alignment: .leading, spacing: 0 ) {
            HStack(alignment: .top, spacing: 0) {
                Text(coordinate.axis.name).font(.system(size: 8))
                Spacer(minLength: 5)
                Text("\(String(format: "%.2f", coordinate.at))").font(.system(size: 8))
            }
            Slider(value: $coordinate.at, in: coordinate.axis.bounds)
                .controlSize(.mini)
                .labelsHidden()
                .padding(EdgeInsets(top: -8, leading: 0, bottom: -10, trailing: 0))
        }
    }
}

public struct CoordinatesView  : View {
    @Binding var coordinates: [Coordinate<Axis>]
    public var body: some View {
        VStack (alignment: .leading, spacing: 0 ) {
            List ((0..<coordinates.count), id:\.self) { coordinateIndex in
                CoordinateView(coordinate: self.$coordinates[coordinateIndex])
            }
        }
    }
}

struct GlyphShape: Shape {
    
    @ObservedObject var glyph: Glyph
    func path(in rect: CGRect) -> Path {
        let glyphPath = glyph.path
        
        print ("MAKING SHAPE")
        
        return glyphPath
    }
}

struct ContentView: View {
    
    @EnvironmentObject var glyph: Glyph
    
    var body: some View {
        VStack {
            NavigatorView()
                .frame(width: 500, height: 500, alignment: .top)
        
                CoordinatesView(coordinates: $glyph.manager.currentCoordinates)
                    .frame(minWidth: 500, idealWidth: 500, maxWidth: 500, minHeight: 300, idealHeight: .none, maxHeight: 500, alignment: .bottom)

            
            //.frame(minWidth: .none, idealWidth: 500, maxWidth: .none, minHeight: .none, idealHeight: .none, maxHeight: .none, alignment: .bottom)
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView().environmentObject(glyph))

2 个答案:

答案 0 :(得分:0)

字形和CoordinatesManager应该符合ObservableObject

也看不到它们的初始化位置。

如果您传递manager而不是glyph.manager,那么显然glyph不会收到通知。

只需使用@EnvironmentObject,您就不必在整个地方传递Binding。

平整您的结构,例如;字形和管理器位于同一级别,而不是嵌套。可能会简化调试。

答案 1 :(得分:0)

好。错误出在GlyphShape中。代替@BindningObject应该是@EnvironmentObject

所以NavigatorView应该打电话

GlyphShape(glyph: _glyph).stroke()

仅此而已。