[SwiftUI] SwiftUI で実装する Drag&Drop (その1: String を Drag&Drop)

SwiftUI

Drag&Drop 実装について まとめていきます。最初のステップとして、String を ドラッグ&ドロップします。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.2.3
  • Xcode 12.4

Drag & Drop

以前は、macOS だけでしたが、複数アプリを並べられる iPad でも 使えるようになってきた Drag&Drop の実装について、まとめてます。

NSItemProvider

NSItemProvider が中心的な役割を持ちます。ドラッグ元からデータを受け取り、ドロップ先にデータを渡す役割をします。

Apple のドキュメントは、こちら

どうやって、受け渡しデータのタイプを設定するか/検知するか

ドラッグ元でのタイプ設定

NSItemProvider に複数のタイプを登録することができます。

画像処理アプリで自分が JPEG で保持しているならば、最初に登録するタイプは JPEG となるでしょう。

必要に応じて、PDF のタイプも登録してあげると、受け取る側で便利になるかもしれません。
# PDF への変換は別途 自分で行う必要があります。

登録タイプごとに、registerDataRepresentation メソッドをつかって、NSItemProvider に登録していくことになります。

Apple のドキュメントは、こちら

ドロップ先でのタイプ検知

NSItemProvider に、特定のタイプでデータを提供するか確認することができます。

希望するタイプから順番にチェックして、受け取れるもので受け取る形になります。

ドロップを受け入れ可能設定する時に、受け取りたいタイプを複数登録することができ、それに見合う NSItemProvider が渡されてきます。

# 受け取れるものがない時には、そもそもドロップできないということです。

例えば、SwiftUI での .onDrop では、closure に 指定したタイプをサポートする NSItemProvider が渡されてきます。

タイプ指定

上記で言及している タイプ とは、UTType のことです。ドキュメントのタイプ判定にも使用されます。

Apple のドキュメントは、こちら

SwiftUI[Swift] [iOS][MacOS] Document App を作る(その1:UTI と Document Type の定義)

String を ドラッグ&ドロップする

UTI にすでに、public.text 等のタイプが定義されています。

より具体的なタイプを指定することが推奨されていますので、"public.utf8-plain-text" を指定して、ドラッグアンドドロップすることにします。

以下のような2つの Text が横に並んでいる アプリを作成し、ドラッグ&ドロップできるようにしていきます。

ContentView

struct ContentView: View {
    @State private var dragText = "Drag from"
    @State private var dropText = "Drop to"
    
    var body: some View {
        HStack {
            List {
                Text(dragText)
            }
            List {
                Text(dropText)
            }
        }
    }
}
Drag&Drop
Drag&Drop

SwiftUI の Text をドラッグ可能にする

左側に表示されている Text をドラッグ可能にするコードは以下のようになります。.onDrag modifier を使用します。

onDrag

Text(dragText)
    .onDrag {
        // (1)
        let provider = NSItemProvider()
        // (2)
        provider.registerDataRepresentation(forTypeIdentifier: kUTTypeUTF8PlainText as String, visibility: .all) { completion -> Progress? in
            // (3)
            completion(dragText.data(using: .utf8), nil)
            return nil
        }
        // (4)
        return provider
    }
コード解説
  1. NSItemProvider を作成します
  2. kUTTypeUTF8PlainText というタイプでの Data を登録します(visibility は、.all にしています)
    厳密には、String ではなく UTType という型を渡す必要があります。
  3. 要求されたときに、String から .utf8 の Data を作成して渡す という closure を設定します
  4. provider を渡します

SwiftUI の Text をドロップ可能にする

次に、右側の Text へドロップできるようにします。.onDrop modifier を使用します。

onDrop

Text(dropText)
    // (1)
    .onDrop(of: [kUTTypeUTF8PlainText as String], isTargeted: $isTarget) { providers -> Bool in
        // (2)
        guard let provider = providers.first else { return false }
        // (3)
        provider.loadItem(forTypeIdentifier: kUTTypeUTF8PlainText as String, options: nil) { (data, error) in
            // (4)
            if let str = String(data: data as! Data, encoding: .utf8) {
                dropText += " \(str)"
            }
        }
        // (5)
        return true

    }
コード解説
  1. kUTTypeUTF8PlainText タイプをドロップ対象として受け取る指定をします
  2. [NSItemProvider] が渡されてくるので、最初の要素を使用します。空配列である時は、処理できないので false を返して終了します
  3. provider から タイプ を指定して要素を取得します
  4. 取得した data から、String を復元して、処理に使います。ここでは、表示テキストに受け取った String を追加しています
  5. 処理できたので true を返します

作成したコード

動作は、以下のようになります。

以下は、コード全体です。

ContentView

struct ContentView: View {
    @State private var dragText = "Drag from"
    @State private var dropText = "Drop to"
    
    @State private var isTarget = false
    var body: some View {
        HStack {
            List {
                Text(dragText)
                    .onDrag {
                        let provider = NSItemProvider()
                        provider.registerDataRepresentation(forTypeIdentifier: kUTTypeUTF8PlainText as String, visibility: .all) { (completion) -> Progress? in
                            completion(dragText.data(using: .utf8), nil)
                            return nil
                        }
                        return provider
                    }
            }
            List {
                Text(dropText)
                    .onDrop(of: [kUTTypeUTF8PlainText as String], isTargeted: $isTarget) { providers -> Bool in
                        guard let provider = providers.first else { return false }
                        provider.loadItem(forTypeIdentifier: kUTTypeUTF8PlainText as String, options: nil) { (data, error) in
                            if let str = String(data: data as! Data, encoding: .utf8) {
                                dropText += " \(str)"
                            }
                        }
                        return true

                    }
            }
        }
    }
}

まとめ:String を ドラッグ&ドロップする

String を ドラッグ&ドロップする
  • Drag される側は、onDrag modifier でドラッグ内容を設定する
  • Drop される側は、onDrop modifier でドラッグされた内容を取得する
  • データのタイプは、UTType で設定する

次回以降で、クラスを Drag & Drop で受け渡しする方法を説明します。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

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