iosswiftuidrag-and-droptransferable

SwiftUI dropDestination(for: URL.self) working on macOS but not iOS


I have the following view that works perfectly fine on macOS:

public struct DropView: View {
    
    @Bindable var store: StoreOf<Feature>
    @State var isDropTargeted = false
    
    public init(
        store: StoreOf<Feature>
    ) {
        self.store = store
    }
    
    public var body: some View {
        Group {
            if isDropTargeted && store.dropsAccepted {
                ZStack {
                    Color.accentColor.opacity(0.2)
                    VStack {
                        Image(systemName: "arrow.up.document")
                            .symbolRenderingMode(.hierarchical)
                            .resizable()
                            .scaledToFit()
                            .frame(width: 64, height: 64)
                        Text("Add file")
                            .foregroundStyle(.secondary)
                    }
                }
            } else {
                Color.clear
            }
        }
        .dropDestination(for: URL.self) { urls, _ in
            store.send(.filesDropped(urls))
            return store.dropsAccepted
        } isTargeted: { targeted in
            isDropTargeted = targeted
        }
    }
}

It does not work on iOS or iPadOS though, neither in the simulator nor on a real device. (I made sure that dropsAccepted is true.)

I've tried different things, none of which succeeded:

When the color is blue instead of clear the dragged file representation gets a "forbidden" icon when it is dragged over the view. Nothing else changes. Dropping isn't possible and isDropTargeted isn't set to true either.

Does anyone have an idea about what could be going on here?


Edit: Not sure if this is related but I just added .draggable(url) to another view in the same app. On the Mac I can drag an image out. On iOS nothing happens.


Solution

  • Seems like Sweeper was on the right track.

    The Files app on iOS seems not to to provide URLs.

    In order to get them I needed to create a Transferable item with a FileRepresentation:

    struct TransferItem: Transferable, Equatable, Sendable {
        
        public var url: URL
        
        static var transferRepresentation: some TransferRepresentation {
            FileRepresentation(contentType: .item) { item in
                SentTransferredFile(item.url)
            } importing: { received in
                @Dependency(\.fileClient) var fileClient
                let temporaryFolder = fileClient.temporaryReplacementDirectory(received.file)
                let temporaryURL = temporaryFolder.appendingPathComponent(received.file.lastPathComponent)
                let url = try fileClient.copyItemToUniqueURL(at: received.file, to: temporaryURL)
                return Self(url)
            }
        }
    }
    

    And then use it like this:

    .dropDestination(for: TransferItem.self) { items, _ in
        let urls = items.map(\.url)
            store.send(.filesDropped(urls))
            return store.dropsAccepted
        } isTargeted: { targeted in
            isDropTargeted = targeted
        }