I have finally made a contextual menu to save a Chart()
as a PNG file in MacOS with SwiftUI. See this question for the details.
I'd like to create a common saveAsImage()
function instead of duplicating the code in different places throughout the view.
This code works:
struct ButtonPlot : View
{
@EnvironmentObject private var mPlotter : DataPlotter
private var mOneBtn : TOneBtn
private var mXStart : Float = 0
private var mXEnd : Float = 0
private var mTrain : Int
init(train: Int, btn: TOneBtn)
{
mTrain = train
mOneBtn = btn
if let xs = mOneBtn.dataPointList.first?.x
{
mXStart = xs - 0.001
}
if let xe = mOneBtn.dataPointList.last?.x
{
mXEnd = xe + 0.001
}
}
var body: some View
{
Text("Button \(mTrain):\(mOneBtn.idx). \(mOneBtn.dataPointList.count) data points")
chartBlock
.contextMenu
{
Button("Save")
{
let cb = chartBlock.aspectRatio(2, contentMode: .fit)
let renderer = ImageRenderer(content: cb)
if let nsImage = renderer.nsImage
{
let imageRep = NSBitmapImageRep(data: nsImage.tiffRepresentation!)
if let pngData = imageRep?.representation(using: .png, properties: [:])
{
let url = URL(filePath: "/Users/danny/Desktop/snapshot.png")
do
{
try pngData.write(to: url, options: .atomic)
print("WROTE FILE")
}
catch
{
print("Error saving \(url.path()) - \(error)")
}
}
}
}
}
}
private var chartBlock : some View
{
Chart(mOneBtn.dataPointList)
{
LineMark(x: .value("x", $0.x), y: .value("y", $0.y))
}
.frame(width: 400, height: 100, alignment: .topLeading)
.chartXScale(domain: mXStart ... mXEnd)
}
}
However, trying to make either a global function or a class with method before the struct ButtonPlot
gives some errors I don't understand:
class FileSaver
{
func saveAsFile(theView: any View, title: String?)
{
// Error: Type 'any View' cannot conform to 'View'
let renderer = ImageRenderer(content: theView)
if let nsImage = renderer.nsImage
{
let imageRep = NSBitmapImageRep(data: nsImage.tiffRepresentation!)
// Error: Cannot infer contextual base in reference to member 'png'
if let pngData = imageRep?.representation(using: .png, properties: [:])
{
let url = URL(filePath: "/Users/danny/Desktop/snapshot.png")
do
{
// Error: Cannot infer contextual base in reference to member 'atomic'
try pngData.write(to: url, options: .atomic)
print("WROTE FILE")
}
catch
{
print("Error saving \(url.path()) - \(error)")
}
}
}
}
}
Any suggestions on what those errors mean and how I can create the common "save as" function?
Because protocol View
has an associated type requirement, so View
does not conform to View
. A solution could be add a generic constraint to type of theView
parameter to conform to View.
@MainActor
class FileSaver
{
func saveAsFile<T: View>(theView: T, title: String?)
{
let renderer = ImageRenderer(content: theView)
if let nsImage = renderer.nsImage
{
let imageRep = NSBitmapImageRep(data: nsImage.tiffRepresentation!)
// Error: Cannot infer contextual base in reference to member 'png'
if let pngData = imageRep?.representation(using: .png, properties: [:])
{
let url = URL(filePath: "/Users/danny/Desktop/snapshot.png")
do
{
// Error: Cannot infer contextual base in reference to member 'atomic'
try pngData.write(to: url, options: .atomic)
print("WROTE FILE")
}
catch
{
print("Error saving \(url.path()) - \(error)")
}
}
}
}
}