ファイルのURLをD&Dで取得するViewの実装(macOS/SwiftUI)

## 概要
- 下記のようにD&DしたファイルのURLを取得するViewを作成する

![image](https://i.imgur.com/uWF7XGx.gif)

## 参考
- [SwiftUI macOSアプリでファイルを開く](https://genjiapp.com/blog/2021/09/06/swiftui-open-file.html)
- [Get file path from \.onDrop on macOS SwiftUI app](https://developer.apple.com/forums/thread/696503)
- [onDrop\(of:isTargeted:perform:\)](https://developer.apple.com/documentation/swiftui/view/ondrop(of:istargeted:perform:)-982eu)
- [loadObject\(ofClass:completionHandler:\)](https://developer.apple.com/documentation/foundation/nsitemprovider/2923363-loadobject)
- [NSItemProviderのloadObjectをasync/awaitに対応させてみると、うんともすんとも言わなくなって困った話](https://dev.classmethod.jp/articles/load-object-untomo-suntomo/)
- 画像をD&Dしたい場合は弊記事が参考になるかもしれません。
    - [画像をNSViewにドラッグして情報を取得する \(Swift\)](https://qiita.com/IKEH/items/1cdf51591be506c3f74b#%E8%BF%BD%E8%A8%982022-02-09)

## 実装

```swift
import SwiftUI

// MARK: - DropView

struct DropView: View {
    
    @State var fileURLs: [URL] = []
    @State private var isDragging = false
    
    var body: some View {
        ZStack {
            mainView()
            draggingFrame(display: isDragging)
                .zIndex(-1)
        }
        .onDrop(of: [.fileURL], isTargeted: $isDragging) { providers in
            let validProviders: [NSItemProvider] = providers.filter { $0.canLoadObject(ofClass: URL.self) }
            if validProviders.isEmpty {
                return false
            }
            
            fileURLs.removeAll()
            providers.forEach { provider in
                let _ = provider.loadObject(ofClass: URL.self) { url, error in
                    guard let url else { return }
                    self.fileURLs.append(url)
                }
            }

            return true
        }
    }
    
    // MARK: - ViewBuilder
    
    @ViewBuilder
    private func mainView() -> some View {
        if fileURLs.isEmpty {
            Text("D&D Here!")
                .font(.system(size: 64).bold())
        } else {
            ScrollView {
                ForEach(fileURLs, id: \.path) { fileURL in
                    HStack {
                        Text(fileURL.path)
                            .textSelection(.enabled)
                        Spacer()
                    }
                }
                .padding()
            }
            .padding()
        }
    }
    
    @ViewBuilder
    private func draggingFrame(display: Bool) -> some View {
        Rectangle()
            .stroke(
                (display ? Color(.selectedControlColor) : .gray.opacity(0.5)),
                style: StrokeStyle(lineWidth: 4))
            .padding()
    }

}

struct DropView_Previews: PreviewProvider {
    static var previews: some View {
        DropView()
            .previewDisplayName("")
        
        DropView(fileURLs: [
            URL(filePath: "/Users/ikeh/Downloads/Instance/Macrophotoofacutecatand_36358065.png"),
            URL(filePath: "/Users/ikeh/Downloads/Instance/Macrophotoofacutecatand_80234277.png"),
            URL(filePath: "/Users/ikeh/Downloads/Instance/Macrophotoofacutecateati_14042064.png"),
        ])
        .previewDisplayName("filesURL.count > 0")
        
    }
}
```