Task
I wanted to import an audio file into an iOS 14.0 or later app built using Swift UI 2.0 at work, but I had problems with security-scoped URL s and styling, so I wanted to share my experience …
File picker
With the release of iOS 14 Beta 6, Apple has provided a new view qualifier that allows users to import existing files from their iOS device or iCloud Drive . First, fileImporter()
let’s check out this new modifier …
fileImporter
Modifier
fileImporter(isPresented:allowedContentTypes:allowsMultipleSelection:onCompletion:)
The method allows the user to import one or more files.
Declaration
func fileImporter(
isPresented: Binding<Bool>,
allowedContentTypes: [UTType],
allowsMultipleSelection: Bool,
onCompletion: @escaping (Result<[URL], Error>) -> Void
) -> some View
parameter
- isPresented : A binding that supports whether to display the file picker interface.
- allowedContentTypes : A list of supported content types that can be imported .
- allowsMultipleSelection : A Boolean value that supports whether the user can select and import multiple files at the same time.
- onCompletion : A callback that will be called when the operation is successful.
Details
- To display the file picker , set the
isPresented
variable bound with to true . - When the file to import is selected
onCompletion
,isPresented
is automatically set back to false before the callback is called . - If the user cancels the import,
isPresented
is automatically set back to false and theonCompletion
callback is not called. onCompletion
When the callback is invoked,result
teeth.success
or.failure
be either..success
If, contains a listresult
of URLs of files to import .
Note
allowedContentTypes
Can be changed
as soon as the file importer is displayed, but has no immediate effect and is only applied the next time the file importer is displayed.
Sample code
Let’s test the implementation code by importing and playing one audio file:
import SwiftUI
import AVFoundation
...
@State private var isImporting = false
var body: some View {
Button(action: {
isImporting = true
}) {
Image(systemName: "waveform.circle.fill")
.font(.system(size: 40))
}
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.audio],
allowsMultipleSelection: false
) { result in
if case .success = result {
do {
let audioURL: URL = try result.get().first!
let audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
audioPlayer.delegate = ...
audioPlayer.prepareToPlay()
audioPlayer.play() // ← ERROR raised here
} catch {
let nsError = error as NSError
fatalError("File Import Error \(nsError), \(nsError.userInfo)")
}
} else {
print("File Import Failed")
}
}
}
...
what happened? !!
error
Below is the error I got in the XCode console when I ran the code for the actual implementation:
2021-04-23 10:00:01.123456+0900 MyApp[10691:1234567] Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service
2021-04-23 10:00:01.123456+0900 MyApp[10691:1234567] Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service
Playback initialization for "file:///private/var/mobile/Containers/Shared/AppGroup/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/File%20Provider%20Storage/Voice/TestFile.m4a" file has failed: Error Domain=NSOSStatusErrorDomain Code=-54 "(null)".
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file MyApp/AudioPlayer.swift, line 123
Meaning of error
Apps on iOS are sandboxed and access to files inside the sandbox is unlimited , but access to files outside the sandbox is restricted without proper permissions .
How do I get permissions to access files outside the app’s sandbox?
Security authority
Once you have a list of URLs for the files you want to import, you need to get permissions to access them.
startAccessingSecurityScopedResource()
Method
Declaration
func startAccessingSecurityScopedResource() -> Bool
Return value
This function returns true if the request to access the file was successful , false otherwise.
Details
Once you get the security scope URL, you can’t immediately use the file it points to.
To access the file, you need to call a startAccessingSecurityScopedResource()
method (or Core Foundation equivalent CFURLStartAccessingSecurityScopedResource(_:)
function) at the security scope URL, which adds the file location to your app’s sandbox and makes the file available to your app. ..
After successfully gaining access to a file, you should give up access to the file as soon as you finish using it.
As a result, calling a stopAccessingSecurityScopedResource()
method (or its equivalent Core Foundation equivalent CFURLStopAccessingSecurityScopedResource(_:)
) in a URL relinquishes access and immediately makes the file inaccessible.
Note
If you don’t give up access when file system resources are no longer needed, your app will leak kernel resources. If a lot of kernel resources are leaked, the app will not be able to add the file system location to the sandbox until it is restarted.
Sample code
...
@State var audioFiles: Array<MediaFileObject> = Array<MediaFileObject>()
@State private var isImporting = false
var body: some View {
Button(action: {
isImporting = true
}) {
Image(systemName: "waveform.circle.fill")
.font(.system(size: 40))
}
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.audio],
allowsMultipleSelection: false
) { result in
if case .success = result {
do {
let audioURL: URL = try result.get().first!
if audioURL.startAccessingSecurityScopedResource() {
audioFiles.append(AudioObject(id: UUID().uuidString, url: audioURL))
}
} catch {
let nsError = error as NSError
fatalError("File Import Error \(nsError), \(nsError.userInfo)")
}
} else {
print("File Import Failed")
}
}
}
...
File type
Shared system data formats can be used for resources to load, save, and open from other apps . Alternatively, you can define your own files and data formats as needed .
In my code, it represents all kinds of audio files, belong to the category “image, audio, and basic type of video” .audio
using the type;
- image : A basic type that represents image data.
- audio : A type that represents audio that does not contain video.
- audiovisualContent : A basic type that represents data that contains video content that may or may not contain audio.
- movie : A basic type that represents a media format that may contain both video and audio.
- video : A type that represents a video that does not contain audio.
styling
With SwiftUI 2.0 (expecting SwiftUI 3.0), styling isn’t really easy yet, but I’ve found some ways to change the look of the file picker:
UINavigationBar.appearance().tintColor: UIColor
: The color of the toolbar item in the header of the file picker.UITabBar.appearance().barTintColor: UIColor
: The background color of the tab bar at the bottom of the file picker.UITabBar.appearance().tintColor: UIColor
: The color of the currently selected tab bar item.UITabBar.appearance().unselectedItemTintColor: UIColor
: The color of the toolbar item that is not currently selected.
be careful ! Using UIKit settings in this way can affect the appearance of the navigation bar and tab bar in other parts of the application.
At the end
We hope this article helps you implement file imports in your app.
How to do SwiftUI 1.0?
I tried to implement a file picker that imports ( fileImporter()
none) audio files in another way, but I couldn’t!
fileExporter
Modifier
fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:)
The method allows the user to export a document in memory to a file on disk.
fileMover
Modifier
fileMover(isPresented:file:onCompletion:)
The method allows the user to move an existing file to a new location.
Read More:
- How to download files by post submission under Ajax
- How to Solve electron import page Error
- How to Solve Vue3 Import lodash error
- React: How to Solve Web3 import error
- Vue Import element-plus Error: Failed to resolve import “element-pluslibtheme-chalkindex.css“ from “src
- How to Solve Files Upload Error: http://net::ERR_SSL_PROTOCOL_ERROR
- [vite] Failed to parse source for import analysis because the content contains invalid JS syntax.
- Using ts-node to Execute .ts files Error [Solved]
- [Solved] Error:Plugin/Preset files are not allowed to export objects, only functions
- [Weex]Error in creating project NPM: unable to load file D:\program files\nodejs\node_ global\ weex.ps1 Because scripts are not allowed to run on this system.
- [Solved] node.js Upload Files Error: Multipart: boundary not found multer
- Solution to some map files in JS folder after Vue packaging (remove the map. JS file)
- [Solved] SyntaxError: Cannot use import statement outside a module
- [Solved] SyntaxError: Cannot use import statement outside a module
- [Solved] Uncaught SyntaxError: Cannot use import statement outside a module
- Vue-router import Dynamic Module Error [How to Solve]
- [Solved] VS Code Debug JavaScript Error: “crbug/1173575, non-JS module files deprecated”
- [Solved] vue3vite Error: Failed to resolve import “@vue/server-renderer from “src\App.vue“. Does the file exist
- [Solved] SyntaxError: Cannot use import statement outside a module
- Vue JS import font.css error [How to Solve]
I keep getting the error can’t find MediaFileObject in Scope