SwiftUI NSViewRepresentable не может читать данные из @Publisher

Я пишу 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()
    }
}
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
25
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прямо сейчас, в вашем 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. Еще раз спасибо.

Alejandro 08.04.2021 03:29

Другие вопросы по теме