This question is in reference to DanielGindi/Charts but I think some knowledge of NSView
rendering might help even if you're not familiar with the Charts
library.
I'm using Charts
in a MacOS project, and having trouble with getting MarkerView
to display correctly. I've copied RadarMarkerView
from the ChartsDemo-iOS project to ChartsDemo-macOS in order to use it in the MacOS RadarDemoViewController
. But the marker never appears in the view when I click on a value in the chart.
Here is the full implementation of RadarMarkerViewMac
:
public class RadarMarkerViewMac: MarkerView {
@IBOutlet var label: NSTextField!
public override func awakeFromNib() {
self.offset.x = -self.frame.size.width / 2.0
self.offset.y = -self.frame.size.height - 7.0
}
public override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
label.stringValue = String.init(format: "%.3f %%", (entry.y))
_ = self // see note 2
layout() // see note 1
}
}
As you can see if you look at the original RadarMarkerView, there are only two notable changes:
layoutIfNeeded()
is changed to layout()
because the former function doesn't exist in NSView
.self
was being drawn correctly (when paused for a breakpoint, hover mouse over self
, then click the eyeball button to see the view).The weird thing I discovered was this: Clicking on the chart highlights the selected value but doesn't show the marker. However, when I put a breakpoint on the layout()
line, then viewed self
with the preview popup, I saw the view just fine... and then once I continued the app running, the marker appeared as it should in the chart in all subsequent selections, even with the breakpoint disabled.
Also important to note, I think, is that if I put the breakpoint in and DIDN'T show the preview, the marker would not display on the chart.
I assume this has something to do with the NSView
rendering, but I haven't been able to make the app behave correctly here. Can you help?
It is not sufficient to only copy RadarMarkerView
.
You need to create a corresponding xib for macOS. In RadarDemoViewController
in viewDidLoad
load the view from xib and add it as a subview outside of visible area, so that it can be rendered:
let marker = RadarMarkerView.viewFromXib()!
marker.chartView = radarChartView
radarChartView.marker = marker
self.view.addSubview(marker)
marker.frame = NSRect(x: -100, y: -100, width: 64, height: 64)
As one can see in MarkerView a property nsuiLayer
is used, which basically corresponds to self.layer
.
Therefore you need to specify that you want the view to use a layer as its backing store.
public required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
wantsLayer = true
}
You can define an offset (depending on the size of your view):
open override func offsetForDrawing(atPoint point: CGPoint) -> CGPoint {
return CGPoint(x: -36, y: -40)
}
And in refreshContent
you specify that the view needs a layout pass before it can be drawn with
needsLayout = true
So the complete RadarMarkerView
looks something like this:
import AppKit
import Charts
public class RadarMarkerView: MarkerView {
@IBOutlet var label: NSTextField!
public required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
wantsLayer = true
}
open override func draw(context: CGContext, point: CGPoint) {
// NSColor.red.setFill()
// NSRect(x: point.x - 32, y: point.y - 32, width: 64, height: 64).fill()
super.draw(context: context, point: point)
}
open override func offsetForDrawing(atPoint point: CGPoint) -> CGPoint {
return CGPoint(x: -36, y: -40)
}
public override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
label.stringValue = String.init(format: "%d %%", Int(round(entry.y)))
needsLayout = true
}
}
Note the override of draw(context:point:)
. To make debugging easier, you can programmatically draw a red rectangle (just uncomment the two lines).
Test
As you can see the marker is then shown.