【问题标题】:SwiftUI NSViewRepresentable can't read data from @PublisherSwiftUI NSViewRepresentable 无法从@Publisher 读取数据
【发布时间】:2021-04-07 09:06:05
【问题描述】:

我正在使用 NSView 编写一个 ChartView,其中的数据是使用 Combine 从 rest api 获得的。 struct PlotView 是显示图表的 SwiftUI 视图,ChartViewRepresentable 是带有图表的 NSView 和 SwiftUI 世界之间的桥梁,而 ChartView 是我实际绘制的视图。

RestRequest 正确地从网络获取数据,并且 PlotView 可以毫无问题地访问它。收到数据后,会创建一个 ChartViewRepresentable 并包含数据,然后 ChartViewRepresentable 会使用数据创建一个 ChartView,并且数据会正确存储在其 data 属性中。

有两个问题:1)视图的 draw 方法在加载数据时永远不会被调用,2)如果视图被重绘,SwiftUI 会创建一个新的 ChartViewRepresentable(带有一个新的 ChartView)但没有数据。

我已经以所有可能的方式连接了 RestRequest @StateObject,使用@Binding,使用@State,到目前为止没有运气,所以我将其视为问题,但使用真正知道的 SwiftUI。无论我如何加载数据,甚至将数据手动加载到 ChartView 中,它在接收数据时都不会自行调用 draw 方法,然后当我例如调整窗口大小以强制进行绘图调用时,它会调用draw 方法,但在一个没有数据的新 ChartViewRepresentable 结构上。

我做错了什么?这是除了我知道的 RestRequest() 结构之外的所有代码,因为到目前为止我一直在其他视图上可靠地使用它。任何线索甚至提示都将不胜感激。

struct PlotView: View {
    @StateObject var request = RestRequest()
    
    var body: some View {
        Group {
            ChartViewRepresentable(data: ChartData(array: ChartData.createArray(from: request.response.data)))
                .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        }
        .onAppear{
            let params: [String: String] = [
                "limit": "10",
            ]
            request.perform(endPoint: "http://localhost:4000/api/testdata", parameters: params)
        }
    }
}


struct ChartViewRepresentable: NSViewRepresentable {
    typealias NSViewType = ChartView
    
    var chart: ChartView
    
    init(data: ChartData) {
        chart = ChartView(data: data)
    }
    
    func makeNSView(context: Context) -> ChartView {
        return chart
    }
    func updateNSView(_ nsView: ChartView, context: Context) {
    }
}

class ChartView: NSView {
    
    private var data: ChartData
    
    init(data: ChartData) {
        self.data = data
        print("\(data)")
        super.init(frame: .zero)
        wantsLayer = true
        layer?.backgroundColor = .white
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ dirtyRect: NSRect) {
        print("draw call - Frame: \(self.frame), Data: \(data.array.count)")
        
        super.draw(dirtyRect)
        
        guard let context = NSGraphicsContext.current else { return }
        context.saveGraphicsState()
        
        if data.array.count > 0 {
            //detect data present on ChartView
            let ctx = context.cgContext
            ctx.setFillColor(NSColor.green.cgColor)
            ctx.fillEllipse(in: CGRect(x: 10, y: 10, width: 10, height: 10))
        }
        
        context.restoreGraphicsState()
    }
}

【问题讨论】:

    标签: swift swiftui core-graphics combine uiviewrepresentable


    【解决方案1】:

    现在,在您的ChartViewRepresentable 中,您将data 设置在init 中,然后再也不要触摸它。

    这意味着您的ChartView 将在您的onAppear API 调用运行并返回数据之前拥有其数据集。

    要解决此问题,您需要使用 updateNSView 函数。

    struct ChartViewRepresentable: NSViewRepresentable {
        typealias NSViewType = ChartView
        
        var data: ChartData //store the data -- not the chart view
        
        func makeNSView(context: Context) -> ChartView {
            return ChartView(data: data)
        }
        
        func updateNSView(_ chart: ChartView, context: Context) {
            chart.data = data //update the chart view's data
        }
    }
    

    而且,您需要响应该数据的更新并强制重绘:

    class ChartView: NSView {
        
        var data: ChartData {
            didSet {
                self.needsDisplay = true //<-- Here
            }
        }
        
        init(data: ChartData) {
            self.data = data
            print("\(data)")
            super.init(frame: .zero)
            wantsLayer = true
            layer?.backgroundColor = .white
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func draw(_ dirtyRect: NSRect) {
            print("draw call - Frame: \(self.frame), Data: \(data.array.count)")
            
            super.draw(dirtyRect)
            
            guard let context = NSGraphicsContext.current else { return }
            context.saveGraphicsState()
            
            if data.array.count > 0 {
                //detect data present on ChartView
                let ctx = context.cgContext
                ctx.setFillColor(NSColor.green.cgColor)
                ctx.fillEllipse(in: CGRect(x: 10, y: 10, width: 10, height: 10))
            }
            
            context.restoreGraphicsState()
        }
    }
    

    模拟 API 调用的完整工作示例:

    struct ChartData {
        var array : [Int]
    }
    
    struct ContentView: View {
        @State var chartData : ChartData = ChartData(array: [])
        
        var body: some View {
            Group {
                ChartViewRepresentable(data: chartData)
                    .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
            }
            .onAppear{
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    print("New data!")
                    chartData = ChartData(array: [1,2,3,4])
                }
            }
        }
    }
    
    
    struct ChartViewRepresentable: NSViewRepresentable {
        typealias NSViewType = ChartView
        
        var data: ChartData
        
        func makeNSView(context: Context) -> ChartView {
            return ChartView(data: data)
        }
        
        func updateNSView(_ chart: ChartView, context: Context) {
            chart.data = data
        }
    }
    
    class ChartView: NSView {
        
        var data: ChartData {
            didSet {
                self.needsDisplay = true
            }
        }
        
        init(data: ChartData) {
            self.data = data
            print("\(data)")
            super.init(frame: .zero)
            wantsLayer = true
            layer?.backgroundColor = .white
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func draw(_ dirtyRect: NSRect) {
            print("draw call - Frame: \(self.frame), Data: \(data.array.count)")
            
            super.draw(dirtyRect)
            
            guard let context = NSGraphicsContext.current else { return }
            context.saveGraphicsState()
            
            if data.array.count > 0 {
                //detect data present on ChartView
                let ctx = context.cgContext
                ctx.setFillColor(NSColor.green.cgColor)
                ctx.fillEllipse(in: CGRect(x: 10, y: 10, width: 10, height: 10))
            }
            
            context.restoreGraphicsState()
        }
    }
    

    【讨论】:

    • 谢谢!做到了。你是对的,我错误地使用了 NSViewRepresentable。再次感谢您。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多