swiftmacosswiftuidrag-and-drop

SwiftUI - Drag and Drop .MOV Files?


This been really getting me confused on how to use Drag and Drop for MOV Files on macOS. Below is the code that I have up and running. But I think the part that confuses the most is how to make a .Mov File into a "Transferable" struct that I can use. Any Help would be great!

struct ContentView: View {
@State private var media: [Data] = []
@State private var isResourcesExpanded: Bool = true
@State private var isConvertedFilesExpanded: Bool = true

var body: some View {
    NavigationSplitView {
        List {
            convertedFiles
        }
        .listStyle(.sidebar)
        
    } detail: {
        dragAndDrop
    }
}

private var convertedFiles: some View {
    Section(
        isExpanded: $isConvertedFilesExpanded,
        content: {
            VStack(alignment: .leading) {
                Text("Test")
                Text("test2")
            }
        },
        header: {
            HStack() {
                Image(systemName: "folder.fill")
                    .foregroundStyle(.teal)
                Text("Converted Files")
            }
        })
}

private var dragAndDrop: some View {
    VStack(spacing: 16) {
        Text("Drag .MOV Files")
            .font(.largeTitle)
        
        Image(systemName: "film")
            .font(.system(size: 60))
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .draggable([Data])
}

}


Solution

  • You should consider using .dropDestination modifier. The .draggable API marks a view as the source of a drag operation.

    //
    //  ContentView.swift
    //  drag&drop
    //
    //  Created by 0x67 on 2025-08-30.
    //
    
    import SwiftUI
    import UniformTypeIdentifiers
    
    struct ContentView: View {
        
        @State private var media: URL?
        @State private var isResourcesExpanded: Bool = true
        @State private var isConvertedFilesExpanded: Bool = true
        
        var body: some View {
            NavigationSplitView {
                List {
                    convertedFiles
                }
                .listStyle(.sidebar)
                
            } detail: {
                dragAndDrop
            }
        }
        
        private var convertedFiles: some View {
            Section(
                isExpanded: $isConvertedFilesExpanded,
                content: {
                    VStack(alignment: .leading) {
                        Text("Test")
                        Text("test2")
                    }
                },
                header: {
                    HStack() {
                        Image(systemName: "folder.fill")
                            .foregroundStyle(.teal)
                        Text("Converted Files")
                    }
                })
        }
        
        private var dragAndDrop: some View {
            VStack(spacing: 16) {
                Text("Drag .MOV Files")
                    .font(.largeTitle)
                
                Image(systemName: "film")
                    .font(.system(size: 60))
                
                if let media {
                    Text("Selected: \(media.lastPathComponent)")
                        .font(.footnote)
                        .foregroundStyle(.secondary)
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .dropDestination(for: URL.self) { items, location in
                guard let url = items.first else { return false} /*< we can handle multiple items, for simplicity, we expect only one */
                let pathExtension = url.pathExtension.lowercased()
                guard pathExtension == "mov" else { return false }
                do {
                    media = try copyIntoDocuments(url)
                    return true
                } catch {
                    print("Copy failed: \(error)")
                    return false
                }
            }
            
        }
    }
    
    // MARK: - File copy helper
    private func copyIntoDocuments(_ sourceURL: URL) throws -> URL {
        let fm = FileManager.default
        let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
        let dest = docs.appendingPathComponent(sourceURL.lastPathComponent)
    
        if fm.fileExists(atPath: dest.path) {
            try fm.removeItem(at: dest)
        }
    
        if sourceURL.startAccessingSecurityScopedResource() {
            defer { sourceURL.stopAccessingSecurityScopedResource() }
            try fm.copyItem(at: sourceURL, to: dest)
        } else {
            try fm.copyItem(at: sourceURL, to: dest)
        }
        return dest
    }
    
    #Preview {
        ContentView()
    }