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).
Related
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)
I have downloaded a file using my iPad into the user Downloads folder, not in the resource bundle for the Playground [footnote].
Is there any way to read it using a Playground? I’ve tried a whole bunch of different things and nothing seems able to access the file.
Here’s an example of what doesn’t work for me:
// Playground to analyze my finances downloaded from my bank
import SwiftUI;
import Foundation;
// Read in transactions
var filename = "filename.txt"
do {
print("Letting dir")
let dir = try! FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: false);
print("Dir set, appending file")
var fileurl = dir.appendingPathComponent(filename)
print("Letting contents of file \( fileurl )");
let contents = try String(contentsOf: fileurl, encoding: .utf8)
// Process Transactions
print(contents)
// Display results
}
catch {
print("Error opening file: \( error )")
}
I think the problem is that it’s accessing the downloads folder of the resources bundle, instead of the system bundle, but I don’t know how to fix that or if it even can be done.
Thanks for any help. This has been unbelievably frustrating and it’s the very first step of my project.
David
[footnote] I can’t even figure out how to access the resource bundle - all the GUI lets me do is edit the Playground code. And I understand the resource paths frequently change, so that doesn’t seem like the right thing to do anyway.
I am not very familiar with Swift programming but I need to write a small tool in Swift which can unzip a file (and then launch a program). I need to unzip a file which is not contained in my app bundle. It is located in /Users/me/folder1/folder2/openjdk-11.0.2.zip
I tried the libraries "Zip", "ZipFoundation", and "SSZipArchive". From what I read so far, I think that the libraries which I tried need the zip file to be located in the app bundle but I am not sure.
With "Zip" I tried:
_ = try Zip.quickUnzipFile(URL(string: openjdkZipUrl!.relativePath)!)
With "ZipFoundation" I tried:
let fileManager = FileManager()
let archive = openjdkZipUrl
let destinationURL = openjdkFolderUrl
do {
try fileManager.unzipItem(at: archive.url, to: destinationURL)
} catch {
}
ZipFoundation told me "Value of type 'FileManager' has no member 'unzipItem'" but I imported it with import Foundation. I also have it (and the other libraries) in my Podfile.
With "SSZipArchive" I tried:
let success = SSZipArchive.unzipFile(atPath: openjdkZipUrl!.path, toDestination: openjdkFolderUrl!.path)
The used paths are
let openjdkZip = "file:///Users/" + user + "/folder1/folder2/openjdk-11.0.2.zip"
let openjdkZipUrl = URL(string: openjdkZip)
and
let openjdkFolder = "file:///Users/" + user + "/folder1/folder2/openjdk-11.0.2"
let openjdkFolderUrl = URL(string: openjdkFolder)
Is it really a problem that the zip file is not contained in my bundle? Can someone tell me what I did wrong?
Thanks in advance
#trojanfoe mentioned in the comments:
Mac apps are sandboxed by default which means they have no access to a user's files unless they ask for it by getting the user to open the file/folder. You should ensure it's turned off for your app, however you are testing if the file exists and if this is succeeding then it looks like sandboxing is not turned on?
I looked at the .entitlements file in my project and found out that "App Sandbox" was set to "YES". I set it to "NO" and it worked perfectly.
It seems that you can check for a file while in sandbox mode (as I did with my condition) but not access them when trying to unzip them.
Thanks again #trojanfoe :)
I searched for my problem with identifying location of the "Application support" on SO. However the various solutions I found are not working for me. Here's the code I'm using to locate a file I want to read/write from.
private var databaseUrl: URL?
{
let file = "MyApp/MyFile.txt"
if let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
{
let path = dir.appendingPathComponent(file)
return path
}
return nil
}
The Bundle Identifier of my app I have set to "MyApp" instead of "com.xxx.yyy.Myapp" which Xcode was using by default, and which I don't want. I was expecting to get a path like this...
/Users/griftopia/Library/Application Support/MyApp/MyFile.txt
This would be consistent with how I see other apps create files on my machine. However, what I am actually getting is this...
Users/griftopia/Library/Containers/MyApp-66665055012bb589084e4aeda209f212b5f695c7/Data/Library/Application Support/Myfile.txt)
Needless to say I want to say within the sandbox, but I thought Application Support is in the sandbox, but not expecting the path I got. Appreciate any insight
I see the ability to get the path to the ApplicationsSupport Directory using Swift.
Is there an interface to get the Log directory?
As far as I can see, there is no Apple-provided way to get this. This seems odd at first glance, but I believe Apple's rationale is that we are actually not supposed to need to directly access this directory, and instead should use their logging API which takes care of these details.
However, I'm using SwiftyBeaver to write logs to a file, and used the following code to generate the logs directory URL:
let bundleID = Bundle.main.bundleIdentifier!
let logsUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
.appendingPathComponent("Logs", isDirectory: true)
.appendingPathComponent(bundleID, isDirectory: true)