swiftmacosswiftui

Error making common save image function: "Type 'any View' cannot conform to 'View'"


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.

enter image description here

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?


Solution

  • 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)")
                    }
                }
            }
        }
    }