Created
January 31, 2026 09:30
-
-
Save juanarzola/1a6d691a3add0443c6dbed51ecd38fe4 to your computer and use it in GitHub Desktop.
Like fileImporter() but you can call fileImporterSimultaneous().fileImporterSimultaneous() and both work
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // FileImporterSimultaneous.swift | |
| // Learn | |
| // | |
| // Created by Juan Arzola on 1/31/26. | |
| // Copyright © 2026 Juan Arzola. All rights reserved. | |
| // | |
| import SwiftUI | |
| import UniformTypeIdentifiers | |
| @available(iOS 14.0, macOS 11.0, *) | |
| @available(tvOS, unavailable) | |
| @available(watchOS, unavailable) | |
| extension View { | |
| /// Wrapper on SwiftUI `fileImporter` that can be applied to views with `fileImporterSimultaneous` already applied. | |
| /// | |
| /// Presents a system interface for allowing the user to import an existing | |
| /// file. | |
| /// | |
| /// In order for the interface to appear, `isPresented` must be `true`. When | |
| /// the operation is finished, `isPresented` will be set to `false` before | |
| /// `onCompletion` is called. If the user cancels the operation, | |
| /// `isPresented` will be set to `false` and `onCompletion` will not be | |
| /// called. | |
| /// | |
| /// - Note: This dialog provides security-scoped URLs. | |
| /// Call the ``startAccessingSecurityScopedResource`` method to access or bookmark | |
| /// the URLs, and the ``stopAccessingSecurityScopedResource`` method | |
| /// to release the access. | |
| /// | |
| /// For example, an application can have a button that allows the user to choose the default directory | |
| /// with document templates loaded on every launch. Such a button might look like this: | |
| /// | |
| /// struct PickTemplatesDirectoryButton: View { | |
| /// @State private var showFileImporter = false | |
| /// var onTemplatesDirectoryPicked: (URL) -> Void | |
| /// | |
| /// var body: some View { | |
| /// Button { | |
| /// showFileImporter = true | |
| /// } label: { | |
| /// Label("Choose templates directory", systemImage: "folder.circle") | |
| /// } | |
| /// .fileImporterSimultaneous( | |
| /// isPresented: $showFileImporter, | |
| /// allowedContentTypes: [.directory] | |
| /// ) { result in | |
| /// switch result { | |
| /// case .success(let directory): | |
| /// // gain access to the directory | |
| /// let gotAccess = directory.startAccessingSecurityScopedResource() | |
| /// if !gotAccess { return } | |
| /// // access the directory URL | |
| /// // (read templates in the directory, make a bookmark, etc.) | |
| /// onTemplatesDirectoryPicked(directory) | |
| /// // release access | |
| /// directory.stopAccessingSecurityScopedResource() | |
| /// case .failure(let error): | |
| /// // handle error | |
| /// print(error) | |
| /// } | |
| /// } | |
| /// } | |
| /// } | |
| /// | |
| /// - Note: Changing `allowedContentTypes` while the file importer is | |
| /// presented will have no immediate effect, however will apply the next | |
| /// time it is presented. | |
| /// | |
| /// - Parameters: | |
| /// - isPresented: A binding to whether the interface should be shown. | |
| /// - allowedContentTypes: The list of supported content types which can | |
| /// be imported. | |
| /// - onCompletion: A callback that will be invoked when the operation has | |
| /// succeeded or failed. To access the received URLs, call `startAccessingSecurityScopedResource`. | |
| /// When the access is no longer required, call `stopAccessingSecurityScopedResource`. | |
| /// - result: A `Result` indicating whether the operation succeeded or | |
| /// failed. | |
| nonisolated public func fileImporterSimultaneous(isPresented: Binding<Bool>, allowedContentTypes: [UTType], onCompletion: @escaping (_ result: Result<URL, any Error>) -> Void) -> some View { | |
| background { | |
| Color.clear | |
| .fileImporter( | |
| isPresented: isPresented, | |
| allowedContentTypes: allowedContentTypes, | |
| onCompletion: onCompletion, | |
| ) | |
| } | |
| } | |
| /// Wrapper on SwiftUI `fileImporter` that can be applied to views with `fileImporterSimultaneous` already applied. | |
| /// | |
| /// Presents a system interface for allowing the user to import multiple | |
| /// files. | |
| /// | |
| /// In order for the interface to appear, `isPresented` must be `true`. When | |
| /// the operation is finished, `isPresented` will be set to `false` before | |
| /// `onCompletion` is called. If the user cancels the operation, | |
| /// `isPresented` will be set to `false` and `onCompletion` will not be | |
| /// called. | |
| /// | |
| /// - Note: This dialog provides security-scoped URLs. | |
| /// Call the ``startAccessingSecurityScopedResource`` method to access or bookmark | |
| /// the URLs, and the ``stopAccessingSecurityScopedResource`` method | |
| /// to release the access. | |
| /// | |
| /// For example, a button that allows the user to choose multiple PDF files for the application | |
| /// to combine them later, might look like this: | |
| /// | |
| /// struct PickPDFsButton: View { | |
| /// @State private var showFileImporter = false | |
| /// var handlePickedPDF: (URL) -> Void | |
| /// | |
| /// var body: some View { | |
| /// Button { | |
| /// showFileImporter = true | |
| /// } label: { | |
| /// Label("Choose PDFs to combine", systemImage: "doc.circle") | |
| /// } | |
| /// .fileImporterSimultaneous( | |
| /// isPresented: $showFileImporter, | |
| /// allowedContentTypes: [.pdf], | |
| /// allowsMultipleSelection: true | |
| /// ) { result in | |
| /// switch result { | |
| /// case .success(let files): | |
| /// files.forEach { file in | |
| /// // gain access to the directory | |
| /// let gotAccess = file.startAccessingSecurityScopedResource() | |
| /// if !gotAccess { return } | |
| /// // access the directory URL | |
| /// // (read templates in the directory, make a bookmark, etc.) | |
| /// handlePickedPDF(file) | |
| /// // release access | |
| /// file.stopAccessingSecurityScopedResource() | |
| /// } | |
| /// case .failure(let error): | |
| /// // handle error | |
| /// print(error) | |
| /// } | |
| /// } | |
| /// } | |
| /// } | |
| /// | |
| /// - Note: Changing `allowedContentTypes` or `allowsMultipleSelection` | |
| /// while the file importer is presented will have no immediate effect, | |
| /// however will apply the next time it is presented. | |
| /// | |
| /// - Parameters: | |
| /// - isPresented: A binding to whether the interface should be shown. | |
| /// - allowedContentTypes: The list of supported content types which can | |
| /// be imported. | |
| /// - allowsMultipleSelection: Whether the importer allows the user to | |
| /// select more than one file to import. | |
| /// - onCompletion: A callback that will be invoked when the operation has | |
| /// succeeded or failed. To access the received URLs, call `startAccessingSecurityScopedResource`. | |
| /// When the access is no longer required, call `stopAccessingSecurityScopedResource`. | |
| /// - result: A `Result` indicating whether the operation succeeded or | |
| /// failed. | |
| nonisolated public func fileImporterSimultaneous(isPresented: Binding<Bool>, allowedContentTypes: [UTType], allowsMultipleSelection: Bool, onCompletion: @escaping (_ result: Result<[URL], any Error>) -> Void) -> some View { | |
| background { | |
| Color.clear | |
| .fileImporter( | |
| isPresented: isPresented, | |
| allowedContentTypes: allowedContentTypes, | |
| allowsMultipleSelection: allowsMultipleSelection, | |
| onCompletion: onCompletion, | |
| ) | |
| } | |
| } | |
| } | |
| @available(iOS 17.0, macOS 14.0, *) | |
| @available(tvOS, unavailable) | |
| @available(watchOS, unavailable) | |
| extension View { | |
| /// Wrapper on SwiftUI `fileImporter` that can be applied to views with `fileImporterSimultaneous` already applied. | |
| /// Presents a system dialog for allowing the user to import multiple | |
| /// files. | |
| /// | |
| /// In order for the dialog to appear, `isPresented` must be `true`. When | |
| /// the operation is finished, `isPresented` will be set to `false` before | |
| /// `onCompletion` is called. If the user cancels the operation, | |
| /// `isPresented` will be set to `false` and `onCompletion` will not be | |
| /// called. | |
| /// | |
| /// - Note: This dialog provides security-scoped URLs. | |
| /// Call the ``startAccessingSecurityScopedResource`` method to access or bookmark | |
| /// the URLs, and the ``stopAccessingSecurityScopedResource`` method | |
| /// to release the access. | |
| /// | |
| /// For example, a button that allows the user to choose multiple PDF files for the application | |
| /// to combine them later, might look like this: | |
| /// | |
| /// struct PickPDFsButton: View { | |
| /// @State private var showFileImporter = false | |
| /// var handlePickedPDF: (URL) -> Void | |
| /// | |
| /// var body: some View { | |
| /// Button { | |
| /// showFileImporter = true | |
| /// } label: { | |
| /// Label("Choose PDFs to combine", systemImage: "doc.circle") | |
| /// } | |
| /// .fileImporterSimultaneous( | |
| /// isPresented: $showFileImporter, | |
| /// allowedContentTypes: [.pdf], | |
| /// allowsMultipleSelection: true | |
| /// ) { result in | |
| /// switch result { | |
| /// case .success(let files): | |
| /// files.forEach { file in | |
| /// // gain access to the directory | |
| /// let gotAccess = file.startAccessingSecurityScopedResource() | |
| /// if !gotAccess { return } | |
| /// // access the directory URL | |
| /// // (read templates in the directory, make a bookmark, etc.) | |
| /// handlePickedPDF(file) | |
| /// // release access | |
| /// file.stopAccessingSecurityScopedResource() | |
| /// } | |
| /// case .failure(let error): | |
| /// // handle error | |
| /// print(error) | |
| /// } | |
| /// } | |
| /// } | |
| /// } | |
| /// | |
| /// - Note: Changing `allowedContentTypes` or `allowsMultipleSelection` | |
| /// while the file importer is presented will have no immediate effect, | |
| /// however will apply the next time it is presented. | |
| /// | |
| /// - Parameters: | |
| /// - isPresented: A binding to whether the dialog should be shown. | |
| /// - allowedContentTypes: The list of supported content types which can | |
| /// be imported. | |
| /// - allowsMultipleSelection: Whether the importer allows the user to | |
| /// select more than one file to import. | |
| /// - onCompletion: A callback that will be invoked when the operation has | |
| /// succeeded or failed. The `result` indicates whether the operation | |
| /// succeeded or failed. To access the received URLs, call `startAccessingSecurityScopedResource`. | |
| /// When the access is no longer required, call `stopAccessingSecurityScopedResource`. | |
| /// - onCancellation: A callback that will be invoked | |
| /// if the user cancels the operation. | |
| nonisolated public func fileImporterSimultaneous(isPresented: Binding<Bool>, allowedContentTypes: [UTType], allowsMultipleSelection: Bool, onCompletion: @escaping (_ result: Result<[URL], any Error>) -> Void, onCancellation: @escaping () -> Void) -> some View { | |
| background { | |
| Color.clear | |
| .fileImporter( | |
| isPresented: isPresented, | |
| allowedContentTypes: allowedContentTypes, | |
| allowsMultipleSelection: allowsMultipleSelection, | |
| onCompletion: onCompletion, | |
| onCancellation: onCancellation | |
| ) | |
| } | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
would it break existing background modifiers?
no: