Can't get a scoped resource URL from drag and drop

Hi,

My Mac app allows a customer to drag and drop a file package onto a SwiftUI view. I can't seem to find a way to successfully call .startAccessingSecurityScopedResource() with the file/dir that was dropped into the view.

I put together a simple test app. Here is the code:

struct ContentView: View {
    
    @State var isTargetedForDrop: Bool = false
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Rectangle()
                .stroke(Color.gray)
                .onDrop(of: [UTType.fileURL], isTargeted: $isTargetedForDrop) { providers in
                    guard let provider = providers.first(where: { $0.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) }) else {
                        return false
                    }
                    provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { item, error in
                        if let error = error {
                            print("Drop load error: \(error)")
                            return
                        }
                        if let url = item as? URL {
                            print("Dropped file URL: \(url)")
                        } else if let data = item as? Data,
                                  let url = URL(dataRepresentation: data, relativeTo: nil) {
                            print("Dropped file URL (from data): \(url)")
                            let access = url.startAccessingSecurityScopedResource()
                            if access {
                                print("Successfully accessed file at URL: \(url)")
                            } else {
                                print("Failed to access file at URL: \(url)")
                            }
                            url.stopAccessingSecurityScopedResource()
                        } else {
                            print("Unsupported dropped item: \(String(describing: item))")
                        }
                    }
                    return true
                }
        }
        .padding()
    }
}

When I drop a file package into this view I see, "Failed to access file at URL: <the_full_file_path>"

I'm running Xcode 26 on macOS 26.

Answered by DTS Engineer in 902047022

My Mac app allows a customer to drag and drop a file package onto a SwiftUI view. I can't seem to find a way to successfully call .startAccessingSecurityScopedResource() with the file/dir that was dropped into the view.

So, the problem here is caused by the interaction of two different behaviors:

  1. startAccessingSecurityScopedResource actually works by using security scope data that's specifically attached to the URL object itself. That means it will fail simply because that particular object doesn't have scope data attached, EVEN if your app actually already has access to the file.

  2. The "point" of NSItemProvider is to simply data access, including things like file access, by dealing with "all" possible edge cases "at once" instead of forcing your app to consider each case individually. It doesn't necessarily "expect" that your app will directly interact with the file, which is why it isn't directly handing you a URL object.

Note that, for #1, the immediate thing I noticed was this line:

} else if let data = item as? Data,
		  let url = URL(dataRepresentation: data, relativeTo: nil) {

Most transformations tend to strip security scope data off of URLs, which is why I recommend avoiding them whenever possible. Similarly, if the system didn't directly "give" you a URL, then there's a good chance it won't have scoped data attached.

In any case, you basically have two options here. The first and simplest is to simply "do" whatever you wanted to do with the file. As the code below shows, you actually have access to the file's data inside your provider block, so if your needs are straightforward you can just "do" whatever you want to do.

However, if you need to longer-term access, particularly if you want to preserve access across launches, then my recommendation would be that you create a new "file management" class that takes the URL you got inside the NSItemProvider and then:

  • Create a security scoped bookmark to the object.

  • Resolve the bookmark you just created to a URL.

  • Use that new URL for all access.

There are many reasons I suggest this. First off, if your app will be saving/restoring bookmarks, then it avoids creating a situation where your app is actually dealing with two slightly different "kinds" of URLs:

  1. URLs directly retrieved from the system.

  2. URLs resolved from bookmarks.

...which can potentially have slightly different behavior. That’s a testing headache no one needs and the simplest solution is use the create/resolve “trick” to ensure that your app actually does all of its “work” through #2. It also means that IF bookmark generation is going to fail, you'll find out "early" instead of "late", meaning you can tell the user there’s a problem before they actually start doing anything. The fact that you’re working out of a bookmark means that IF "something" ever goes "wrong", you can try resolving the bookmark again.

Finally, I REALLY want to emphasize this point:

...create a new "file management" class....

Most of our examples and sample code work by directly passing basic types like URL "around", instead of using more complicated architecture. That's because our code needs to be short and easy to explain, NOT because it's actually a good idea. Outside of EXTREMELY simple apps, "bare" file access like this tends to turn into a confusing mess of duplicate code and overlooked edge cases[1].

[1] Ironically, those are the same reasons NSItemProvider was created in the first place.

In any case, here's my modified version of your code that demonstrates what I described above:

//
//  ContentView.swift
//  FileDragAndDrop_SwiftUI
//
//  Created by Kevin Elliott on 8/18/26.
//

import SwiftUI
import Foundation
import UniformTypeIdentifiers

struct ContentView: View {
    
    @State var isTargetedForDrop: Bool = false
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Rectangle()
                .stroke(Color.gray)
                .onDrop(of: [UTType.fileURL], isTargeted: $isTargetedForDrop) { providers in
                    guard let provider = providers.first(where: { $0.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) }) else {
                        return false
                    }
                    provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { item, error in
                        if let error = error {
                            print("Drop load error: \(error)")
                            return
                        }
                        if let url = item as? URL {
                            print("Dropped file URL: \(url)")
                        } else if let data = item as? Data,
                                  let url = URL(dataRepresentation: data, relativeTo: nil) {
                            print("Dropped file URL (from data): \(url)")
                            let content = try? Data(contentsOf: url)
                            print("File contents: \(content)")

                            let bookmarkData = try! url.bookmarkData(options: [.withSecurityScope], includingResourceValuesForKeys: nil, relativeTo: nil)
                            var isStale = false
                        #if true
                            #warning("Test code proving that your app has LOST access to the file.")
                            url.stopAccessingSecurityScopedResource()
                            let contTry = try? Data(contentsOf: url)
                            print("File contents: \(contTry)")
                        #endif
                            
                            let newURL = try! URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)

                            let access = newURL.startAccessingSecurityScopedResource()
                            if access {
                                print("Successfully accessed file at URL: \(url)")
                                let content = try? Data(contentsOf: url)
                                print("File contents: \(content)")

                            } else {
                                print("Failed to access file at URL: \(url)")
                            }
                            newURL.stopAccessingSecurityScopedResource()
                        } else {
                            print("Unsupported dropped item: \(String(describing: item))")
                        }
                    }
                    return true
                }
        }
        .padding()
    }
}

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I should say, I have also tried this approach to get the URL from the NSItemProvider:

provider.loadObject(ofClass: URL.self) { url, error in

This gives me a URL, but again, this returns false:

url.startAccessingSecurityScopedResource()

I am having the same problem. I am able to load security scoped URLs through the file picker API's but any drag and drop operations put me to temporary files. I have tried both the load file in place APIs as well:

provider.loadInPlaceFileRepresentation(forTypeIdentifier: type.identifier)
provider.loadFileRepresentation(for: type, openInPlace: true)

For my use case, I want the user to be able to open the original file from my app without copying their data into the sandbox.

My Mac app allows a customer to drag and drop a file package onto a SwiftUI view. I can't seem to find a way to successfully call .startAccessingSecurityScopedResource() with the file/dir that was dropped into the view.

So, the problem here is caused by the interaction of two different behaviors:

  1. startAccessingSecurityScopedResource actually works by using security scope data that's specifically attached to the URL object itself. That means it will fail simply because that particular object doesn't have scope data attached, EVEN if your app actually already has access to the file.

  2. The "point" of NSItemProvider is to simply data access, including things like file access, by dealing with "all" possible edge cases "at once" instead of forcing your app to consider each case individually. It doesn't necessarily "expect" that your app will directly interact with the file, which is why it isn't directly handing you a URL object.

Note that, for #1, the immediate thing I noticed was this line:

} else if let data = item as? Data,
		  let url = URL(dataRepresentation: data, relativeTo: nil) {

Most transformations tend to strip security scope data off of URLs, which is why I recommend avoiding them whenever possible. Similarly, if the system didn't directly "give" you a URL, then there's a good chance it won't have scoped data attached.

In any case, you basically have two options here. The first and simplest is to simply "do" whatever you wanted to do with the file. As the code below shows, you actually have access to the file's data inside your provider block, so if your needs are straightforward you can just "do" whatever you want to do.

However, if you need to longer-term access, particularly if you want to preserve access across launches, then my recommendation would be that you create a new "file management" class that takes the URL you got inside the NSItemProvider and then:

  • Create a security scoped bookmark to the object.

  • Resolve the bookmark you just created to a URL.

  • Use that new URL for all access.

There are many reasons I suggest this. First off, if your app will be saving/restoring bookmarks, then it avoids creating a situation where your app is actually dealing with two slightly different "kinds" of URLs:

  1. URLs directly retrieved from the system.

  2. URLs resolved from bookmarks.

...which can potentially have slightly different behavior. That’s a testing headache no one needs and the simplest solution is use the create/resolve “trick” to ensure that your app actually does all of its “work” through #2. It also means that IF bookmark generation is going to fail, you'll find out "early" instead of "late", meaning you can tell the user there’s a problem before they actually start doing anything. The fact that you’re working out of a bookmark means that IF "something" ever goes "wrong", you can try resolving the bookmark again.

Finally, I REALLY want to emphasize this point:

...create a new "file management" class....

Most of our examples and sample code work by directly passing basic types like URL "around", instead of using more complicated architecture. That's because our code needs to be short and easy to explain, NOT because it's actually a good idea. Outside of EXTREMELY simple apps, "bare" file access like this tends to turn into a confusing mess of duplicate code and overlooked edge cases[1].

[1] Ironically, those are the same reasons NSItemProvider was created in the first place.

In any case, here's my modified version of your code that demonstrates what I described above:

//
//  ContentView.swift
//  FileDragAndDrop_SwiftUI
//
//  Created by Kevin Elliott on 8/18/26.
//

import SwiftUI
import Foundation
import UniformTypeIdentifiers

struct ContentView: View {
    
    @State var isTargetedForDrop: Bool = false
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Rectangle()
                .stroke(Color.gray)
                .onDrop(of: [UTType.fileURL], isTargeted: $isTargetedForDrop) { providers in
                    guard let provider = providers.first(where: { $0.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) }) else {
                        return false
                    }
                    provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { item, error in
                        if let error = error {
                            print("Drop load error: \(error)")
                            return
                        }
                        if let url = item as? URL {
                            print("Dropped file URL: \(url)")
                        } else if let data = item as? Data,
                                  let url = URL(dataRepresentation: data, relativeTo: nil) {
                            print("Dropped file URL (from data): \(url)")
                            let content = try? Data(contentsOf: url)
                            print("File contents: \(content)")

                            let bookmarkData = try! url.bookmarkData(options: [.withSecurityScope], includingResourceValuesForKeys: nil, relativeTo: nil)
                            var isStale = false
                        #if true
                            #warning("Test code proving that your app has LOST access to the file.")
                            url.stopAccessingSecurityScopedResource()
                            let contTry = try? Data(contentsOf: url)
                            print("File contents: \(contTry)")
                        #endif
                            
                            let newURL = try! URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)

                            let access = newURL.startAccessingSecurityScopedResource()
                            if access {
                                print("Successfully accessed file at URL: \(url)")
                                let content = try? Data(contentsOf: url)
                                print("File contents: \(content)")

                            } else {
                                print("Failed to access file at URL: \(url)")
                            }
                            newURL.stopAccessingSecurityScopedResource()
                        } else {
                            print("Unsupported dropped item: \(String(describing: item))")
                        }
                    }
                    return true
                }
        }
        .padding()
    }
}

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Can't get a scoped resource URL from drag and drop
 
 
Q