2

I have created an Xcode swift based software that is menu based. One of the buttons I have created is intended to capture a screenshot and save the file to a specific location.

I have found sources explaining how to do this on iOS, but I'm looking for macOS functionality. The article: Programmatically Screenshot | Swift 3, macOS has responses that have gotten me close but I think some of it is deprecated.

How can I implement this in a software developed for macOS with Xcode & Swift 5.

Here is the code for the function:

@objc func TakeScreenshot(_ sender: Any){

    func CreateTimeStamp() -> Int32
    {
        return Int32(Date().timeIntervalSince1970)
    }

    var displayCount: UInt32 = 0;
        var result = CGGetActiveDisplayList(0, nil, &displayCount)
        if (result != CGError.success) {
            print("error: \(result)")
            return
        }
        let allocated = Int(displayCount)
        let activeDisplays = UnsafeMutablePointer<CGDirectDisplayID>.allocate(capacity: allocated)
        result = CGGetActiveDisplayList(displayCount, activeDisplays, &displayCount)

        if (result != CGError.success) {
            print("error: \(result)")
            return
        }

        for i in 1...displayCount {
            let unixTimestamp = CreateTimeStamp()
            let fileUrl = URL(fileURLWithPath: "~/Documents" + "\(unixTimestamp)" + "_" + "\(i)" + ".jpg", isDirectory: true)
            let screenShot:CGImage = CGDisplayCreateImage(activeDisplays[Int(i-1)])!
            let bitmapRep = NSBitmapImageRep(cgImage: screenShot)
            let jpegData = bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!


            do {
                try jpegData.write(to: fileUrl, options: .atomic)
            }
            catch {print("error: \(error)")}
        }

}


 menu.addItem(NSMenuItem(title: "Take Screenshot", action:
            #selector(AppDelegate.TakeScreenshot(_:)), keyEquivalent: ""))

The second portion of code is the menu item that is a button. I want this button to take a screenshot of the screen and then save the file to a location I specify.

I get this error when I use the button on my application: Error

Gereon
  • 17,258
  • 4
  • 42
  • 73
Alex Tudor
  • 23
  • 3
  • 1
    Welcome Alex! You wrote _have gotten me close_ - show us the code with an explanation what's wrong, what doesn't work, etc. I'd highly recommend to visit the help center and read about [mre](https://stackoverflow.com/help/minimal-reproducible-example), [good question](https://stackoverflow.com/help/how-to-ask), ... The more info you provide, the better & faster we can help. – zrzka May 12 '20 at 08:47
  • Thanks! Just updated my original post with code. Thanks for the advice! – Alex Tudor May 12 '20 at 15:14
  • 1
    In the future, please don't post images of text, post the text as text. – Ken Thomases May 12 '20 at 16:35

1 Answers1

0

The error has to do with saving the file. You are constructing the URL badly.

First, expanding ~ to the home directory is generally a shell feature, not an OS feature. The underlying file path APIs (e.g. open()) just treat that as a normal character in a path. Foundation does support expanding ~ in path strings (not URLs), but you have to specifically request it with expandingTildeInPath. It's never automatic and it's never meaningful in URLs.

Next, I suspect you were trying to build a URL to a file within the Documents directory. However, you did not put a path separator (/) between the name of the directory and the name of the file. In other words, you constructed ~/Documents10989439875_1.jpg, not ~/Documents/10989439875_1.jpg.

You should use FileManager().urls(for:.downloadsDirectory, in:.userDomainMask)[0] to get the URL to the Downloads folder and then append a path component to that using appendingPathComponent(_:isDirectory:).

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Thanks Ken! I tried your suggestions and heres what I got: I created `let dirPath = FileManager().urls(for:.downloadsDirectory, in:.userDomainMask)[0]` and `let downloadDir = dirPath.appendingPathComponent(_:isDirectory:)` under my TakeScreenshot function. Then where I had `"~/Documents"` I replaced with `downloadDir`. I am now getting a "Cannot convert value of type 'URL' to expected argument type 'String'". I am not too sure on how to implement what you recommended but I tried it this way and cant seem to find the correct way. PS: Sorry for formatting – Alex Tudor May 13 '20 at 03:20
  • It's not quite clear exactly what the new code looks like. You could edit into your question. But, I think you should replace the `let fileUrl ...` line with `let fileUrl = dirPath.appendingPathComponent("\(unixTimestamp)" + "_" + "\(i)" + ".jpg", isDirectory:false)`. You shouldn't need the `downloadDir` variable, as near as I can see. – Ken Thomases May 13 '20 at 04:57
  • Thanks! This worked perfectly. Makes sense now how to make it work. – Alex Tudor May 13 '20 at 15:29