Я пишу ChartView с использованием NSView с данными, полученными из rest api с помощью Combine. Структура PlotView - это представление SwiftUI, которое отображает диаграмму, ChartViewRepresentable - это мост между NSView с диаграммой и миром SwiftUI, а ChartView - это представление, которое я фактически использую.
RestRequest правильно получает данные из сети, и PlotView имеет к ним доступ без проблем. Когда данные получены, создается ChartViewRepresentable, содержащий данные, а ChartViewRepresentable создает ChartView с данными, и данные правильно сохраняются в его свойстве data.
Есть две проблемы: 1) метод рисования представления никогда не вызывается при загрузке данных и 2) если представление перерисовывается, SwiftUI создает новый ChartViewRepresentable (с новым ChartView), но без данных.
Я подключил RestRequest @StateObject всеми возможными способами, используя @Binding, используя @State, но пока безуспешно, поэтому я обесцениваю это как проблему, но со SwiftUI, который действительно знает. Неважно, как я загружаю данные, даже загружая данные вручную в ChartView, он никогда не вызывает метод рисования сам по себе при получении данных, а затем, когда я, например, изменяю размер окна, чтобы вызвать вызов рисования, он вызывает метод рисования, но в новой структуре 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, вы устанавливаете data в init и никогда больше не прикасаетесь к нему.
Это означает, что ваш ChartView будет иметь набор данных перед, ваш вызов API onAppear когда-либо запускается и возвращает данные.
Чтобы исправить это, вам нужно использовать функцию 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. Еще раз спасибо.