Mac Sandbox issues with opening embedded RTF files - swift

My Mac programs usually ship with some internal Rich Text files containing legal details. I use the NSWorkspace openFile call to open the files within TextEdit.
The code looks something like this:
guard let aPath = Bundle.main.path(forResource: “Legal.rtf”, ofType: nil) else { return }
NSWorkspace.shared.openFile(aPath, withApplication: nil)
This has always worked, until recently when this code returns “The application can’t be opened. -50”. Is that a Sandbox issue? Accessing files within your bundle should be allowed. We do it for images and such.
What do I have set wrong?
Thank you!

Thank you for your comments.
I should have mentioned at first that parameter string I had included both filename and the file type (extension). So I would have to split them, something that was easy to do with NSString, but is not immediately available for Swift String. A bit of conversion would have given me the two strings.
However, that OpenFile has been replaced with the newer open(_:)
let name = "Legal.rtf"
guard let aURL = Bundle.main.url(forResource: name, withExtension: "") else { return }
NSWorkspace.shared.open(aURL)
This call does NOT mind if you pass it string with both parts.

Related

Is there a way to work with FileManager and NSBundle to get all the applications that support a given type of file in the /Applications directory?

I'm creating a Commandline tool that will get an input from a user to search for a specific application that supports a certain file extension. For example, if I enter mp4, it will probably show me QuickTime. I'm looking for the specific FileManager manipulation to achieve this in Swift.
I think you are looking for NSWorkspace.urlForApplication(toOpen:). It finds the application that would be opened if you had double clicked on a file, and returns its URL. Since it requires a file to work, you need to first create a temporary empty file somewhere, with the desired extension, then call this method.
let tempFileURL = FileManager.default.temporaryDirectory.appendingPathComponent("foo.mp4")
FileManager.default.createFile(atPath: tempFileURL.path, contents: nil, attributes: nil)
// this gives me the URL for QuickTime Player:
// file:///System/Applications/QuickTime%20Player.app/
let url = NSWorkspace.shared.urlForApplication(toOpen: tempFileURL)

Use Images in a swift framework

I need a picture to appeare in a framework. The only way i found needed that i know the name of the app it is in. Is there another way to get assets into your framework?
(For getting this posted:
my background search didnt help)
Almost 5 years ago I posted this answer. It contains two pieces of code to pull out an asset from a Framework's bundle. The key piece of code is this:
public func returnFile(_ resource:String, _ fileName:String, _ fileType:String) -> String {
let identifier = "com.companyname.appname" // replace with framework bundle identifier
let fileBundle = Bundle.init(identifier: identifier)
let filePath = (fileBundle?.path(forResource: resource, ofType: "bundle"))! + "/" + fileName + "." + fileType
do {
return try String(contentsOfFile: filePath)
}
catch let error as NSError {
return error.description
}
So what if your framework, which needs to know two things (app bundle and light/dark mode) tweaked this code? Move identifier out to be accessible to the app and not local to this function. Then create either a new variable (I think this is the best way) or a new function to work with the correct set of assets based on light or dark mode.
Now your apps can import your framework, and set things up in it's consumers appropriately. (I haven't tried this, but in theory I think it should work.)

Accessing File saved in today extension from project

I am trying to access a file I saved from my today extension.In my today extenson I did this to save the file:
func startRecording() {
let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a")
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
recordButton.setTitle("Stop", for: .normal)
} catch {
finishRecording(success: false)
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
I then tried to get the data for my AVAudio Player in the main part of the project using this code:
let path = Bundle.main.path(forResource: "recording.m4a", ofType:nil)!
let url = URL(fileURLWithPath: path)
However, it gave the error: unexpectedly found nil while unwrapping an Optional value.
Thanks for the help.
Your extension saves the file to its document directory and your app code is looking for the file in the app bundle. The app bundle only contains the resources that are distributed with the app. You'll need to delve into the file system.
However, there's another problem. The extension and containing app don't share a documents directory. They each have their own container for writing data local to themselves. If you want to share data between them, it's a little more work. In summary:
Create an app group identifier for the app and the extension to share.
Use FileManager.containerURL(forSecurityApplicationGroupIdentifier:) to get the file URL for the shared container directory.
From the container URL, append the file name.
In the extension, you'll set up the AVAudioRecorder as usual and start recording.
In the main app, you'll want to use the NSFileCoordinator API to ensure that only one process is writing to the file at a time. Hopefully, the AVAudioRecorder uses the NSFileCoordinator API internally, although I didn't immediately find confirmation of this.
For more details about shared containers, see this blog post.
I just tried the same - record audio from a Today Extension. The code looks sooo familiar, so I'm taking a wild guess: you want to capture voice and send the file to the Google Speech API, correct?
Nonetheless, I think we're hitting restrictions of extensions: judging by https://developer.apple.com/library/content/qa/qa1872/_index.html extensions cannot record audio. The article has been writting for iOS 8, but I don't believe Apple ever lifted the restriction. Please correct me if I'm wrong, since I keep running into problems doing what OP does myself.
btw, check the result of audioRecorder.record(). It might be false and indicate that the capture never started (that's my error currently).

NSData(contentsOfURL: url) always returning nil

I have this code in a new playground
import Foundation
let blogsURL: NSURL = NSURL(fileURLWithPath: "/Users/Francis/Documents/Xcode_projects/KM registratie/blogs.json")
let data = NSData(contentsOfURL: blogsURL)
On the second line the playground tells me that it (correctly) initialised the URL referring to file:///Users/Francis/Documents/Xcode_projects/KM%20registratie/blogs.json
and on the third line the playground tells me that data is nil
I already googled around but no question seems to be the exact same problem. I found this "NSData contentsOfURL constructor returns nil", but neither restarting Xcode nor restarting my entire computer fixes the problem.
playgrounds are sandboxed and it seems that there isn't an easy way to reach outside their "box". XML parsing in swift the title of this question is a bit misleading, the answer on it does answer this question
It's answered here:
XML parsing in swift
The problem is that your URL isn't pointing where you think it is. You're trying to open a URL in a subdirectory in your sandbox, and it doesn't exist.
You'll need to open the package contents of your playground (right click on the playground and select "Show Package Contents", add a folder called "Resources", and copy your file directly there.
Then you can get the file from the main bundle:
let url: NSURL! = NSBundle.mainBundle().URLForResource("blogs", withExtension: "json")

NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg") returning null

NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg")
The above code is returning null.
In order to check if the file exists or not, I used below code:
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(savepath) {
println("exist")
}
The above code returns that file exists in directory.
So I don't understand why the first code is returning null
Your problem is that NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg") returns an optional NSURL. You need to use if let to unwrap it and extract your file path from the returned url as follow:
if let resourceUrl = NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg") {
if NSFileManager.defaultManager().fileExistsAtPath(resourceUrl.path!) {
print("file found")
}
}
Swift 3.x
if let resourceUrl = Bundle.main.url(forResource: "bach1", withExtension: "jpg") {
if FileManager.default.fileExists(atPath: resourceUrl.path) {
print("file found")
}
}
I found that you have to actually go into Build Phases -> Copy Bundle Resources and manually add your item sometimes to the bundle. In my case it was a video file that didn't get added to the bundle correctly on its own.
NSBundle.URLForResource only returns files that are bundled in your application at build/compile time, like when you drag a file into your Xcode project and add it to your app target. You can typically use this when your app comes preloaded with resources you need, like images, movies, databases, and other files.
It seems like savepath is a path your application wrote to while running (maybe into your app's Documents directory?). Because of this, the file is outside of the bundle and you will need to store that path/URL somewhere (or build it up again) if you need to reference the file in the future.
I had this issue too. My fix was:
Selecting the file in project navigator
Open the 'File Inspector' (on the right hand side pane)
Checking the app target in the Target Membership