Skip to content

Instantly share code, notes, and snippets.

@juanarzola
Created January 31, 2026 09:30
Show Gist options
  • Select an option

  • Save juanarzola/1a6d691a3add0443c6dbed51ecd38fe4 to your computer and use it in GitHub Desktop.

Select an option

Save juanarzola/1a6d691a3add0443c6dbed51ecd38fe4 to your computer and use it in GitHub Desktop.
Like fileImporter() but you can call fileImporterSimultaneous().fileImporterSimultaneous() and both work
//
// 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
)
}
}
}
@juanarzola
Copy link
Author

would it break existing background modifiers?

no:

  1. a background applied to a clear background is still visible
  2. a clear background applied to another background just leaves that other background on top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment