MacOS NSWorkspace.shared.open "The application can’t be opened. -50" - swift

Following some other Stack Overflow code examples of NSWorkspace.shared.open(), I came up with this:
#IBAction func mailFileVacuum(_ sender: NSButton) {
let receiver = sender.alternateTitle
let sendAddress = String(format: "mailto:%##filevacuum.com?subject=FileVacuum %#", receiver, receiver).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
NSLog("receiver %#", receiver)
let mailUrl = URL(string: sendAddress)
NSLog("mailUrl %#", mailUrl!.absoluteString)
if NSWorkspace.shared.open(mailUrl!) {
print("Default browser was successfully opened to send email. 😊📬")
}
}
☝️ that gets us this 👇
I tried changing the default browser, that's not the issue.
NSLog(mailUrl) logs this mailto%3AFeedback%40filevacuum.com%3Fsubject=FileVacuum%20Feedback
Look good maybe 🤔...
¿Que Paso?

You should not be percent encoding the entire URL; but rather, its individual components. See the Apple URL Scheme Reference.
The correctly formatted URL should look like (i.e. NSLog(mailUrl) should output):
mailto:Feedback#filevacuum.com?subject=FileVacuum%20Feedback

Related

Got an error when dragging files using NSEvent. (macOS)

I wanna drag files to my window and then perform actions.
I tried to use snippets below provided in this answer to distinguish whether you're dragging a file or a window.
// In my window controller
class MyWindowController: NSWindowController {
init() {
// Some initialization steps below are omitted
let win = NSWindow(...)
super.init(window: win)
let contentView = DropView(frame: win.frame)
win.contentView?.addSubview(contentView)
registerGlobalMouseEvent()
}
func registerGlobalMouseEvent() {
self.window?.acceptsMouseMovedEvents = true
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// Codes below will cause errors
let pasteBoard = NSPasteboard(name: .drag)
guard let fileNames = pasteBoard.propertyList(forType: .init(rawValue: "NSFilenamesPboardType")) as? NSArray else { return }
let changeCount = pasteBoard.changeCount
if fileNames.count > 0 && lastChangeCount != changeCount {
lastChangeCount = changeCount
// My actions when dragging
}
})
}
}
Then I ran my codes and started dragging, I got three errors:
[sandbox] Failed to get a sandbox extension
[Framework] Failed to issue sandbox extension for /Users/roy/Downloads/test.txt with error 1
[default] Failed to issue sandbox token for URL: 'file:///Users/roy/Downloads/test.txt' with error: 'Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Cannot issue a sandbox extension for file "/Users/roy/Downloads/test.txt": Operation not permitted}'
 
But when I just do
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// My actions
})
, then everything went fine.
 
The first error seems harmless since it didn't prevent my app from running.
The second and the third ones are deadly and directly caused my app to crash.
I wonder if there are any problems in his code? Any useful thoughts would be great! :)
 
You need to know about Bookmarks and Security Scoped URLs when working with sandbox . A dragged URL gives your app process permission just once to read or read/write a “user selected file” depending on how you configure entitlements.
You can save a bookmark (blob of data) to keep access over subsequent sessions as long as the file isn’t updated by another process at which point the bookmark becomes stale and you will need to encourage the user to select the file again.
Handing a URL to another process across an XPC boundary like sharing requires that you own the file so may involve a copy to your sandbox cache.
e.g:
let dragurl = url_of_dragged_file //at this point you have at-least read access
let cachepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last!
let cachedir = URL(fileURLWithPath: cachepath)
let cacheurl = cachedir
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension(dragurl.pathExtension)
try FileManager.default.copyItem(at: dragurl, to: cacheurl)
At this point you have a copy in your local sandbox cache that can be handed off to a share sheet.
So I finally got a solution for this. :)
It appears that it indeed have something to do with the snippets I mentioned above, and here's the correction:
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
let pasteboard = NSPasteboard(name: .drag)
let changeCount = pasteboard.changeCount
if lastChangeCount != changeCount {
lastChangeCount = changeCount
if pasteboard.canReadObject(forClasses: [NSURL.self], options: [:]) {
/// actions
}
}
})
In this way, I got no errors and my codes run perfectly!

Security Scoped Bookmark - bookmark resolves but still can't access the file

EDIT: Additional information added at the bottom
I have a sandboxed, document based application that loads a user selected quicktime movie into an AVPlayer, and everything was working perfectly.
Now I am upgrading the code so that it will use Security Scoped bookmarks to get the URL rather than just storing a URL string so that the persistent store will allow the movie to be loaded upon relaunch of the application. When the bookmark is created it is stored in a Data variable of a managed object.
For some reason, this has broken the AVPlayer. While I have created a bookmark from the user selected URL, and can resolving the URL from the bookmark when the application is relaunched, the movie is not getting loaded into the AVPlayer correctly and I can't figure out why... I have confirmed that the URL being resolved from the bookmark does point to the movie file.
I have also added the appropriate entitlements to the project.
Here is my code:
Function Where User Selects a Movie To Load and Bookmark is Created
#IBAction func loadMovie(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.title = "Select Video File To Import"
openPanel.allowedFileTypes = ["mov", "avi", "mp4"]
openPanel.begin { (result: NSApplication.ModalResponse) -> Void in
if result == NSApplication.ModalResponse.OK {
self.movieURL = openPanel.url
self.player = AVPlayer.init(url: self.movieURL!)
self.setupMovie()
if self.loadedMovieDatabase.count > 0 {
print("Movie Object Exists. Adding URL String")
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
} else {
print("No Movie Object Exists Yet. Creating one and adding URL String")
let document = NSDocumentController.shared.currentDocument as! NSPersistentDocument
let myManagedObjectContext = document.managedObjectContext!
let newMovie = NSEntityDescription.insertNewObject(forEntityName: "Movie", into: myManagedObjectContext) as! MovieMO
self.loadedMovieDatabase.append(newMovie)
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
}
// create Security-Scoped bookmark - Added 2/1/18
do {
try self.loadedMovieDatabase[0].bookmark = (self.movieURL?.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil))!
} catch {
print("Can't create security bookmark!")
}
}
}
}
Function where Bookmark is Resolved into URL and Movie is Loaded
// initialize AVPlayer with URL stored in coreData movie object if it exists and is a valid path
if loadedMovieDatabase.count > 0 {
// initialize with saved movie path if it is valid (from security bookmark data)
// let myURL = URL(string: loadedMovieDatabase[0].urlString!) <- replaced with new code below
print("Loading URL from Bookmark")
var urlResult = false
var myURL : URL
do {
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStale: &urlResult)!
print("URL Loaded from Bookmark")
print("URL is", myURL)
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
print("IsSecured = ", isSecuredURL)
player = AVPlayer.init(url: myURL)
print("Setting Up Movie")
setupMovie()
} catch {
// No Data in bookmark so load default ColorBars movie instead
print("No Security Bookmark Available. Reverting to Default Color Bars")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
} else {
// load default ColorBars movie instead
print("Nothing was loaded so just set up a new document.")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
I am new to Security-Scoped Bookmarks, so I'm hoping that this may be obvious to anyone who has worked with them before.
I'm wondering if it's a problem with:
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
Perhaps I'm calling this incorrectly? Sometimes I find Apple's documentation to be vague and confusing... Any insight would be appreciated!
EDIT:
I believe I know why this is happening, but I'm not sure how to fix it...
myURL.startAccessingSecurityScopedResource()
always returns FALSE... per the documentation that would mean that it's not working. Additionally, while the movie file is located on my Desktop, the Resolved URL comes up as the following (this may be normal, I don't know.):
file:///Users/me/Library/Containers/myapp/Data/Desktop/sample_on_desktop.mov
The apple docs make reference to the fact that a Document Scope can not use files in the system (aka "/Library"), but my entitlements are setup to use application-scope bookmarks, and my bookmark was created using the nil flag for relativeURL: so this shouldn't be an issue.
I just stumbled upon the answer accidentally...
For starters, when I was resolving the URL, I was not using the method which allows you to include OPTIONS, so my URL was resolved WITHOUT the security-scope. My original code to resolve was:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStable: &urlResult)!
When I should have been using the version with options here:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, Options: URL.bookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStable: &urlResult)!
Basically, I used the first init option Xcode presented in the predictive list with the words "resolvingBookmarkData:" when I should have looked further down the list. (This is how I found my error.)
NOTE also that it's important to use...
URL.bookmarkResolutionOptions.withSecurityScope
and not
URL.bookmarkCreationOptions.withSecurityScope
...when you're resolving your URL or it doesn't appear to work correctly.
Thus ends my frustration with this problem :) I hope this explanation might help others facing this problem!

Link to multiple websites

I want to create a link, but when pressed it randomly chooses a link from a list. I have the code to take the button a link already, but how how would i modify it so I add more urls to it randomly choose when clicked different times.
Current link code:
#IBAction func Website(_ sender: Any) {
if let url = NSURL(string: "http:heeeeeeeey.com/"){
UIApplication.shared.openURL(url as URL)
}
}
Try something like:
#IBAction func Website(_ sender: Any) {
let websites = ["http://website1.com",
"http://website2.com",
"http://website3.com"]
if let url = URL(string: websites[Int(arc4random_uniform(UInt32(websites.count)))]) {
UIApplication.shared.openURL(url as URL)
}
}
This should do the trick for you.
Explanation:
websites is an array of Strings, so put all the URLs you want the button to pick from in there.
Int(arc4random_uniform(UInt32(websites.count))) is the magical part that picks a random number between 0 and websites.count(which is the last item you have in the array.
If you are new to Swift (or programming), this might sound confusing to you, don't freak out, just keep practicing.
Best of luck!
First, to create an array of URL strings is pretty straightforward:
var urls = [
"http://www.url1.com",
"http://www.url2.com",
"http://www.url3.com"
]
Now, you could get a random element of this urls array with this long line of code:
let randomURL = urls[Int(arc4random_uniform(UInt32(urls.count)))]
However, another way you could do it is to add an extension to Array that works on all arrays:
extension Array {
public var random: Element? {
let index = Int(arc4random_uniform(UInt32(self.count)))
return self.count>0 ? self[index] : nil
}
}
Now, getting a random element of the urls array is as easy as:
urls.random
This returns an Optional (this is because if there are no elements in an array, the random property will return nil). So in your code you'll also need to unwrap the result of the random property:
#IBAction func Website(_ sender: Any) {
if let urlString = urls.random,
let url = URL(string: urlString) {
UIApplication.shared.openURL(url as URL)
}
}
P.S. A couple of comments on your code:
I recommend you rename Website to openRandomWebsite (remembering to change the storyboard connections too). Methods should explain what they do, and begin with a lower case letter. If you're interested, Swift general code conventions here.
The openURL method has been deprecated in iOS 10, so I recommend you use the open(_:​options:​completion​Handler:​) method.
Your code would look like:
UIApplication.shared.open(url, options: [:], completionHandler: { (success) in
//URL opened
})

How to get e-mail subject from message:// URL in OSX Swift

I have a desktop app that receives e-mail URLs ("message://" scheme) from the drag&drop pasteboard and I want to get the Subject from the relevant message. The only clue I have, so far, is that the QuickLook library might give me an information object where I can retrieve this info from.
Since the QuickLook API seems to be rather in flux at the moment and most examples show how to use it in iOS, I simply cannot find a way to set up my "Preview" object using a URL and get the information from there.
I would like to avoid setting up my project as a QuickLook plugin, or setting up the whole preview pane / view scaffolding; at the moment I just want to get out what QuickLook loads before it starts displaying, but I can't comprehend what paradigm Apple wants me to implement here.
XCode 7.3.1.
It turns out I misinterpreted the contents of draggingInfo.draggingPasteboard().types as a hierarchical list containing only one type of info (URL in this case).
Had to subscribe to dragged event type kUTTypeMessage as String and retrieve the e-mail subject from the pasteboard with stringForType("public.url-name")
EDIT: Note that the current Mail.app will sometimes create a stack of mails when you drag an e-mail thread. Although the method above still works to get the subject of the stack, there is no URL in the dragging info then and since there's no list of Message-IDs available either, I had to resort to scraping the user's mbox directory:
// See if we can resolve e-mail message meta data
if let mboxPath = pboard.stringForType("com.apple.mail.PasteboardTypeMessageTransfer") {
if let automatorPlist = pboard.propertyListForType("com.apple.mail.PasteboardTypeAutomator") {
// Get the latest e-mail in the thread
if let maxID = (automatorPlist.allObjects.flatMap({ $0["id"]! }) as AnyObject).valueForKeyPath("#max.self") as? Int {
// Read its meta data in the background
let emailItem = draggingEmailItem
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Find the e-mail file
if let path = Util.findEmlById(searchPath: mboxPath, id: maxID) {
// Read its contents
emailItem.properties = Util.metaDataFromEml(path)
dispatch_async(dispatch_get_main_queue(), {
// Update UI
});
}
}
}
}
}
Util funcs:
/* Searches the given path for <id>.eml[x] and returns its URL if found
*/
static func findEmlById(searchPath searchPath: String, id: Int)-> NSURL? {
let enumerator = NSFileManager.defaultManager().enumeratorAtPath(searchPath)
while let element = enumerator?.nextObject() as? NSString {
switch (element.lastPathComponent, element.pathExtension) {
case (let lpc, "emlx") where lpc.hasPrefix("\(id)"):
return NSURL(fileURLWithPath: searchPath).URLByAppendingPathComponent(element as String)!
case (let lpc, "eml") where lpc.hasPrefix("\(id)"):
return NSURL(fileURLWithPath: searchPath).URLByAppendingPathComponent(element as String)!
default: ()
}
}
return nil
}
/* Reads an eml[x] file and parses it, looking for e-mail meta data
*/
static func metaDataFromEml(path: NSURL)-> Dictionary<String, AnyObject> {
// TODO Support more fields
var properties: Dictionary<String, AnyObject> = [:]
do {
let emlxContent = try String(contentsOfURL: path, encoding: NSUTF8StringEncoding)
// Parse message ID from "...\nMessage-ID: <...>"
let messageIdStrMatches = emlxContent.regexMatches("[\\n\\r].*Message-ID:\\s*<([^\n\r]*)>")
if !messageIdStrMatches.isEmpty {
properties["messageId"] = messageIdStrMatches[0] as String
}
}
catch {
print("ERROR: Failed to open emlx file")
}
return properties
}
Note: If your app is sandboxed you will need the com.apple.security.temporary-exception.files.home-relative-path.read-only entitlement set to an array with one string in it: /Library/

AppleWatch Messages URL works hard coded but not with variables

TLDR When I hard code phone numbers into a URL it opens in watch messages correctly, but when I use a variable string with the numbers typed in exactly the same way inside of it, it doesn't.
Example:
NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=Test")
Above code works but below code doesn't:
let hardCode = "8888888888,9999999999,3333333333"
NSURL(string: "sms:/open?addresses=\(hardCode)&body=Test")
FULL DETAILS:
I am making a URL from variables to open messages on the Apple Watch with pre-filled contents. I am getting the phone numbers from the contact book and storing them in an array. They are provided in this format:
(###) ###-#### but need to be ##########
I tested the code by hard-coding phone numbers into the URL and it works properly with all contacts and completed body:
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
But when I build the phone number values programmatically it does not work:
//holds phone numbers without special chars
var tempArray: [String] = []
//if I can access the unformatted numbers
if let recips = saveData["recips"] as? [String] {
//for each number provided
recips.forEach { (person: String) in
//remove all non-numerical digits
//person is now (###) ###-####
let newPerson = person.digitsOnly()
//newPerson is ##########
print(person)
print("->\(newPerson)")
//add formatted number to tempArray
tempArray.append(newPerson)
}
}
//combine all numbers with "," between as a string
let recipString = tempArray.joinWithSeparator(",")
//recipString contains ##########,##########,##########...
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
I then add the "recipString" variable to the NSURL in the below code:
let messageBody = "test"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(recipString)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
The FINAL URL print shows the correct string, but the messages app does not open properly, and shows quick reply menu instead of composed message window. It matches the functioning hard coded number version exactly, but behaves differently.
Totally lost, hope someone can help!
UPDATE 1
Here are the debug prints for both versions of the URL:
Manually declared (not created from recipString but actually declared in the URL string explicitly):
This version works
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
Variable created (using recipString):
This version doesn't
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
I have also tried applying url encoding to the "recipString" variable by using the below if let:
if let urlSafeRecip = recipString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(urlSafeRecip)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
}
UPDATE 2
I tested to see if the hardcode version of numbers matches the recipString exactly via this code:
let hardCode = "0000000000,1111111111,2222222222,3333333333,4444444444"
let isEqual = (hardCode == recipString)
if isEqual {
print("hardCode matches recipString")
}
else {
print("hardCode does not match recipString")
}
Debug prints:
hardCode matches recipString
UPDATE 3
I have confirmed that:
When a URL is made with hard coded numbers vs. numbers that I make from variables, checking == between them returns true.
In every test I can do between the two version of the url, it matches.
NOTES AFTER CORRECT ANSWER FOUND:
This type of URL formatting will ONLY work with multiple addresses in the URL. If you do not have multiple addresses you will need to do the following, which is undocumented but none-the-less works. I found this by bashing my face on the keyboard for hours, so if it helps you an upvote is deserved :)
follow the answer marked below, and then use this type of logic check before making the URL in the doItButton() function he mentioned:
func setupAndSendMsg(saveData: NSDictionary) {
if let urlSafeBody = createBody(saveData) {
let theNumbers = createNumbers(saveData).componentsSeparatedByString(",")
print(theNumbers.count-1)
if theNumbers.count-1 > 0 {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers(saveData))&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
} else {
if let url = NSURL(string: "sms:/open?address=\(createNumbers(saveData)),&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
}
My guess is that it is not the acctual openSystemUrl call that is the problem. I believe there must be something with the code that is building the number string programmatically.
The code bellow is a simplified version of all the code you have posted. I have confirmed that it is working on my Apple Watch. It opens the Messages app with pre-populated numbers & body text.
Take one more look at your code and see if there is something your missing. If you can't find anything, just delete the code and re-write it, probably will be faster then spotting the weird issue.
Once again the code bellow is confirmed working as expected, so you should be able to get it to work. (or just copy & paste my code) :)
class InterfaceController: WKInterfaceController {
#IBAction func doItButton() {
if let urlSafeBody = createBody() {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers())&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
private func createBody() -> String? {
let messageBody = "hello test message"
return messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
}
private func createNumbers() -> String {
let numbers = ["(111) 222-3333", "(444) 555-6666"]
var tempArray: [String] = []
numbers.forEach { (number: String) in
tempArray.append(number.digitsOnly())
}
return tempArray.joinWithSeparator(",")
}
}
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
With above said I would recommend against using undocumented Apple features for anything you plan on putting on the App Store for the reasons already mentioned in comments.