SwiftUI NSViewRepresentable 无法从@Publisher 读取数据
SwiftUI NSViewRepresentable can't read data from @Publisher
我正在使用 NSView 编写一个 ChartView,其中包含使用 Combine 从 rest api 获得的数据。 struct PlotView 是显示图表的 SwiftUI 视图,ChartViewRepresentable 是带有图表的 NSView 和 SwiftUI 世界之间的桥梁,而 ChartView 是我实际绘制的视图。
RestRequest 正确地从网络获取数据并且 PlotView 可以毫无问题地访问它。收到数据后,会创建一个 ChartViewRepresentable 并包含数据,而 ChartViewRepresentable 使用数据创建一个 ChartView 并将数据正确存储在其数据中 属性。
有两个问题:1) 加载数据时永远不会调用视图的绘制方法,以及 2) 如果重绘视图,SwiftUI 创建了一个新的 ChartViewRepresentable(带有一个新的 ChartView)但没有数据.
我已经以各种可能的方式连接了 RestRequest @StateObject,使用 @Binding,使用 @State,到目前为止没有运气,所以我不认为这是问题,但 SwiftUI 谁真的知道。无论我如何加载数据,甚至手动将数据加载到 ChartView 中,它都不会在接收数据时自行调用 draw 方法,然后当我调整 window 的大小以强制绘制时调用它确实调用了 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()
}
}
现在,在你的 ChartViewRepresentable
中,你在 init
中设置了 data
,然后再也不碰它了。
这意味着您的 ChartView
将在 在 您的 onAppear API 调用运行之前拥有其数据集和 returns 数据。
要解决此问题,您需要使用 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()
}
}
我正在使用 NSView 编写一个 ChartView,其中包含使用 Combine 从 rest api 获得的数据。 struct PlotView 是显示图表的 SwiftUI 视图,ChartViewRepresentable 是带有图表的 NSView 和 SwiftUI 世界之间的桥梁,而 ChartView 是我实际绘制的视图。
RestRequest 正确地从网络获取数据并且 PlotView 可以毫无问题地访问它。收到数据后,会创建一个 ChartViewRepresentable 并包含数据,而 ChartViewRepresentable 使用数据创建一个 ChartView 并将数据正确存储在其数据中 属性。
有两个问题:1) 加载数据时永远不会调用视图的绘制方法,以及 2) 如果重绘视图,SwiftUI 创建了一个新的 ChartViewRepresentable(带有一个新的 ChartView)但没有数据.
我已经以各种可能的方式连接了 RestRequest @StateObject,使用 @Binding,使用 @State,到目前为止没有运气,所以我不认为这是问题,但 SwiftUI 谁真的知道。无论我如何加载数据,甚至手动将数据加载到 ChartView 中,它都不会在接收数据时自行调用 draw 方法,然后当我调整 window 的大小以强制绘制时调用它确实调用了 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()
}
}
现在,在你的 ChartViewRepresentable
中,你在 init
中设置了 data
,然后再也不碰它了。
这意味着您的 ChartView
将在 在 您的 onAppear API 调用运行之前拥有其数据集和 returns 数据。
要解决此问题,您需要使用 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()
}
}