Sponsor Link
環境&対象
- 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 のドキュメントは、こちら。
[Swift] [iOS][MacOS] Document App を作る(その1:UTI と Document Type の定義)
String を ドラッグ&ドロップする
UTI にすでに、public.text 等のタイプが定義されています。
より具体的なタイプを指定することが推奨されていますので、”public.utf8-plain-text” を指定して、ドラッグアンドドロップすることにします。
以下のような2つの Text が横に並んでいる アプリを作成し、ドラッグ&ドロップできるようにしていきます。
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)
}
}
}
}

SwiftUI の Text をドラッグ可能にする
左側に表示されている Text をドラッグ可能にするコードは以下のようになります。.onDrag modifier を使用します。
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
}
- NSItemProvider を作成します
- kUTTypeUTF8PlainText というタイプでの Data を登録します(visibility は、.all にしています)
厳密には、String ではなく UTType という型を渡す必要があります。 - 要求されたときに、String から .utf8 の Data を作成して渡す という closure を設定します
- provider を渡します
SwiftUI の Text をドロップ可能にする
次に、右側の Text へドロップできるようにします。.onDrop modifier を使用します。
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
}
- kUTTypeUTF8PlainText タイプをドロップ対象として受け取る指定をします
- [NSItemProvider] が渡されてくるので、最初の要素を使用します。空配列である時は、処理できないので false を返して終了します
- provider から タイプ を指定して要素を取得します
- 取得した data から、String を復元して、処理に使います。ここでは、表示テキストに受け取った String を追加しています
- 処理できたので true を返します
作成したコード
動作は、以下のようになります。
以下は、コード全体です。
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 を ドラッグ&ドロップする
- Drag される側は、onDrag modifier でドラッグ内容を設定する
- Drop される側は、onDrop modifier でドラッグされた内容を取得する
- データのタイプは、UTType で設定する
次回以降で、クラスを Drag & Drop で受け渡しする方法を説明します。
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link