使用更新的数据不断重绘路径

Continuously Redrawing a Path with Updated Data

我正在开发一个音频可视化器 MacOS 应用程序,我想使用 Quartz/CoreGraphics 渲染与播放音频协调的时变频谱。我的渲染器代码是:

import Cocoa

class 渲染器:NSView {

override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)
    NSColor.white.setFill()
    bounds.fill()
    
    guard let context = NSGraphicsContext.current?.cgContext else {return}

    var x : CGFloat = 0.0
    var y : CGFloat = 0.0

    context.beginPath()
    context.move(to: CGPoint(x: x, y: y))

    for bin in 0 ..< 300 {
        x = CGFloat(bin)
        y = CGFloat(Global.spectrum[bin])
        context.addLine(to: CGPoint(x: x, y: y))
    }
    
    context.setStrokeColor(CGColor( red: 1, green: 0, blue: 0, alpha: 1))
    context.setLineWidth(1.0)
    context.strokePath()

    self.setNeedsDisplay(dirtyRect)
}

}

这会绘制一次路径 - 使用 spectrum[] 数组的初始全零值 - 然后无限期地继续绘制相同的全零线。它不会使用 spectrum[] 数组中的新值进行更新。我使用 print() 语句来验证值本身是否正在更新,但绘制函数不会使用更新后的光谱值重新绘制路径。我做错了什么?

以下演示展示了如何使用计时器在单独的 class 中创建的随机数来更新 NSView,以期模拟您的项目。 运行 in Xcode 通过为 MacOS 设置一个 Swift 项目,copy/pasting 将源代码放入一个名为 'main.swift' 的新文件中,并删除 AppDelegate由 Apple 提供。使用了类似于您发布的绘图功能。

import Cocoa

var view : NSView!
var data = [Int]()

public extension Array where Element == Int {
    static func generateRandom(size: Int) -> [Int] {
        guard size > 0 else {
            return [Int]()
        }
        return Array(0..<size).shuffled()
    }
}

class DataManager: NSObject {
var timer:Timer!

@objc func fireTimer() {
data = Array.generateRandom(size:500)
view.needsDisplay = true
}

func startTimer(){
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
}

func stopTimer() {
 timer?.invalidate()
}

}
let dataMgr = DataManager()

class View: NSView {

override func draw(_ rect: NSRect) {
 super.draw(rect)
 NSColor.white.setFill()
 bounds.fill()
    
 guard let gc = NSGraphicsContext.current?.cgContext else {return}

  var xOld : CGFloat = 0.0
  var yOld : CGFloat = 0.0
  var xNew : CGFloat = 0.0
  var yNew : CGFloat = 0.0
  var counter : Int = 0

  gc.beginPath()
  gc.move(to: CGPoint(x: xOld, y: yOld))

  for i in 0 ..< data.count {
    xNew = CGFloat(counter)
    yNew = CGFloat(data[i])
    gc.addLine(to: CGPoint(x: xNew, y: yNew))
    xOld = xNew;
    yOld = yNew;
    counter = counter + 1
  }
    
  gc.setStrokeColor(CGColor( red: 1, green: 0, blue: 0, alpha: 1))
  gc.setLineWidth(1.0)
  gc.strokePath()
}

}

class ApplicationDelegate: NSObject, NSApplicationDelegate {
 var window: NSWindow!

@objc func myStartAction(_ sender:AnyObject ) {
  dataMgr.startTimer()
}

@objc func myStopAction(_ sender:AnyObject ) {
  dataMgr.stopTimer()
}

func buildMenu() {
let mainMenu = NSMenu()
 NSApp.mainMenu = mainMenu
 // **** App menu **** //
 let appMenuItem = NSMenuItem()
 mainMenu.addItem(appMenuItem)
 let appMenu = NSMenu()
 appMenuItem.submenu = appMenu
 appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
}

func buildWnd() {

data = Array.generateRandom(size: 500)

 let _wndW : CGFloat = 800
 let _wndH : CGFloat = 600

 window = NSWindow(contentRect: NSMakeRect( 0, 0, _wndW, _wndH ), styleMask:[.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false)
 window.center()
 window.title = "Swift Test Window"
 window.makeKeyAndOrderFront(window)

// **** Start Button **** //
 let startBtn = NSButton (frame:NSMakeRect( 30, 20, 95, 30 ))
 startBtn.bezelStyle = .rounded
 startBtn.title = "Start"
 startBtn.action = #selector(self.myStartAction(_:))
 window.contentView!.addSubview (startBtn)

// **** Stop Button **** //
 let stopBtn = NSButton (frame:NSMakeRect( 230, 20, 95, 30 ))
 stopBtn.bezelStyle = .rounded
 stopBtn.title = "Stop"
 stopBtn.action = #selector(self.myStopAction(_:))
 window.contentView!.addSubview (stopBtn)

// **** Custom view **** //
 view = View( frame:NSMakeRect(20, 60, _wndW - 40, _wndH - 80)) 
 view.autoresizingMask = [.width, .height]      
 window.contentView!.addSubview (view)
    
// **** Quit btn **** //
 let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
 quitBtn.bezelStyle = .circular
 quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
 quitBtn.title = "Q"
 quitBtn.action = #selector(NSApplication.terminate)
 window.contentView!.addSubview(quitBtn)
}
 
func applicationDidFinishLaunching(_ notification: Notification) {
 buildMenu()
 buildWnd()
}

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
 return true
}

}
let applicationDelegate = ApplicationDelegate()

// **** main.swift **** //
let application = NSApplication.shared
application.setActivationPolicy(NSApplication.ActivationPolicy.regular)
application.delegate = applicationDelegate
application.activate(ignoringOtherApps:true)
application.run()