[SwiftUI] SwiftUIのビュー上へのドロップの実装

MacOSアプリを作るための詳細化技術検討メモ

ゴール:SwiftUIのビュー上にドロップされたファイルの情報を取得する

Imageを表示しているビュー上に、別のイメージファイルをドロップして、Imageを入れ替える方法を説明します。

まずは、Imageを表示

後で、別ファイルを読み込んで表示することを考えて、ファイルを読み込んで表示するようなImageを設定します。

コード

struct ContentView: View {
    @State var nsImage:NSImage = NSImage(contentsOf: Bundle.main.url(forResource: "View", withExtension: "jpg")! )!
    var body: some View {
        Image(nsImage: self.nsImage)
            .resizable()
            .scaledToFit()
    }
}

バンドル内に持っている"View.jpg"を読み込んでImageに表示します。

あとで、このImage上にドロップすることを想定してます。

Dropターゲット

SwiftUIでは、ドロップに対応するためのmodifierとして、onDropがあります。

コード

.onDrop(of: [kUTTypeFileURL as String], isTargeted: $isDropTargeted, perform: self.processDroppedFile(provideres:))

第1引数はドロップできるようにするファイルタイプを指定し、2つ目は、このビューにドロップしたかのフラグです(使い方わかりません)。
3つめがドロップされたときに呼ばれる関数です。
関数のプロトタイプは、([NSItemProvider]) -> Bool)。

NSItemProviderから、ドロップされた情報を取得することができます。
ファイルをドロップされたときは、以下のような取得になります。

コード

func processDroppedFile(provideres:[NSItemProvider]) -> Bool {
    guard let provider = provideres.first else { return false }
    provider.loadItem(forTypeIdentifier: (kUTTypeFileURL as String), options: nil) { (urlData, error) in
        DispatchQueue.main.async {
            if let urlData = urlData as? Data {
                let imageURL = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
                if let localImage = NSImage(contentsOf: imageURL) {
                    self.nsImage = localImage
                }
            }
        }
    }
    return true
}

Appleのドキュメントに記載されていますが、UIに影響を与える操作については、DispatchQueue.main.asyncの中で実行するように明記されています。
今回は、設定した値をトリガーとしてSwiftUIが再構築するはずなので、DispatchQueue.main.asyncの中で設定しています。

ドロップされるファイルは、写真等の画像ファイルで、そのファイルからNSImageを生成できることを想定していますが、別タイプのファイルであれば、そのように処理することが必要となります。

Dropしているところ、ファイルをドラッグしていくと、”+”マークが確認できます。
DropOnSwiftUIView

最終的なコード

struct ContentView: View {
    @State private var isDropTargeted = false
    @State var nsImage:NSImage = NSImage(contentsOf: Bundle.main.url(forResource: "View", withExtension: "jpg")! )!
    var body: some View {
        Image(nsImage: self.nsImage)
            .resizable()
            .scaledToFit()
            .onDrop(of: [kUTTypeFileURL as String], isTargeted: $isDropTargeted, perform: self.processDroppedFile(provideres:))
    }
    func processDroppedFile(provideres:[NSItemProvider]) -> Bool {
        guard let provider = provideres.first else { return false }
        provider.loadItem(forTypeIdentifier: (kUTTypeFileURL as String), options: nil) { (urlData, error) in
            DispatchQueue.main.async {
                if let urlData = urlData as? Data {
                    let imageURL = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
                    if let localImage = NSImage(contentsOf: imageURL) {
                        self.nsImage = localImage
                    }
                }
            }
        }
        return true
    }
}

Drop対応まとめ

onDropの使い方というよりは、NSItemProviderの使い方が難しいかもしれません。

ただ、AppKitでのDrag&Drop対応よりコードは格段に短くなってます。

1 COMMENT

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です