Tag Archives: SwiftUI 2.0

SwiftUI 2.0: How to Import Files into iOS Apps

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 …

fileImporterModifier

fileImporter(isPresented:allowedContentTypes:allowsMultipleSelection:onCompletion:)The method allows the user to import one or more files.

Declaration
SwiftUI
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 isPresentedvariable bound with to true .
  • When the file to import is selected onCompletionisPresentedis automatically set back to false before the callback is called .
  • If the user cancels the import, isPresentedis automatically set back to false and the onCompletioncallback is not called.
  • onCompletionWhen the callback is invoked, resultteeth .successor .failurebe either.
  • .successIf, contains a list resultof URLs of files to import .
Note

allowedContentTypesCan 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:

SwiftUI
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:

XCode
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
Swift
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
SwiftUI
...
    @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” .audiousing 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!

fileExporterModifier

fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:)The method allows the user to export a document in memory to a file on disk.

fileMoverModifier

fileMover(isPresented:file:onCompletion:)The method allows the user to move an existing file to a new location.