LoginSignup
5
5

More than 3 years have passed since last update.

UITableView のドラッグ・アンド・ドロップ (Drag & Drop)・サポートの追加

Last updated at Posted at 2020-05-28

今日は何を学びますか?

アプリケーションのたくさんのテーブルビューにドラッグ・アンド・ドロップ・サポートを追加します。

テーブルビュー間でアイテムをドラッグ・アンド・ドロップできるようになります。

Screen Shot 2020-05-27 at 2.26.54 PM.png

ストーリー

私はかわいい猫を3匹飼っていて、猫用のおもちゃも3つ持っています(実際にはもっとたくさん持っていますが、この例では3つだけということにしておきましょう)。どの猫がどのおもちゃを持っているか追跡したいと思い、簡単なアプリを作りました。

スタータープロジェクト

このチュートリアルに従っていただくために、UITableView が2つ以上あるアプリを持っている必要があります。この例では、1つのCollectionView の中に3つのテーブルビューがあります。

スタータープロジェクトはこちらからダウンロードしてください: https://github.com/mszmagic/DragAndDropNekoApp/tree/StartTemplate

リンク先で最初のバージョンをダウンロードしてください。

完成したプロジェクト

プロジェクトを直接 git clone したら完成したプログラムが手に入ります。

:sparkles:

ドラッグのサポート

ドラッグがサポートされると、システムユーザーが UITableViewCell を長押しし、それをドラッグして現在のテーブルの表示から外に出せるようになります。

tableView.dragInteractionEnabled = true
tableView.dragDelegate = self

デリゲート (Delegate) インプリメンテーションの追加

/*
 Drag delegate
 */
extension catCollectionViewCell: UITableViewDragDelegate {

    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {

    }

}

ここでは UIDragItem を提供する必要があります。UIDragItem には UITableViewCell 間で移動するデータが必要です。この例では、移動したいデータはオモチャの名前になります。

まずオモチャの名前を入手し:

let toyName = toys[indexPath.row]

次にこれを Data オブジェクト内にエンコード

guard let toyNameData = toyName.data(using: .utf8) else { return [] }

アイテムプロバイダーを生成し、toyNameData をこのプロバイダーにセット。

let provider = NSItemProvider()
provider.registerDataRepresentation(forTypeIdentifier: kUTTypePlainText as String, visibility: .all) { completion in
    completion(toyNameData, nil)
    return nil
}

ここで、その結果を戻す

let item = UIDragItem(itemProvider: provider)
return [item]

ただし、最初のネコの名前が必要になるので、これを session.localContext に保存

session.localContext = catName

これで1つのテーブル表示のセルをドラッグしてテーブル表示から外に出せるようになったはずです。そして、そのテーブル表示のセルを別のテーブル表示にドロップできるはずです。 

でも、ここまでは送り手側の作業を完了しただけです。まだ、これから受け手側のコーディング作業が少し残っています。

ドロップ (Drop) サポートを追加する

ドロップサポートを使うと、別のオブジェクトを現在のビューにドロップできます。ドロップサポートを追加するには、まずテーブルビューにデリゲートを割り当てます。

tableView.dropDelegate = self

次に、delegate を実装します:

extension catCollectionViewCell: UITableViewDropDelegate {

    func tableView(_ tableView: UITableView, performDropWith     coordinator: UITableViewDropCoordinator) {

    }

}

coordinator.session から2つの関数が必要になります。 (https://developer.apple.com/documentation/uikit/uidropsession)

hasItemsConforming(to:)

この関数を使うと、ビューにドロップしたデータのタイプを確認することができます。この記事の1つ前の節で次のコード行を使ったことを思い出してください:

provider.registerDataRepresentation(forTypeIdentifier: kUTTypePlainText as String, visibility: .all) { completion in

データのタイプは kUTTypePlainText as String です。次のコードを実行して確認できます:

if coordinator.session.hasItemsConforming(toTypeIdentifiers: [kUTTypePlainText as String]) { }

loadObjects(ofClass:completion:)

この関数を使うと、coordinator 内にあるデータオブジェクトを抽出できます。

coordinator.session.loadObjects(ofClass: NSString.self) { (fetchedItems) in
    //TODO
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
    //データのタイプは `kUTTypePlainText as String` です。次のコードを実行して確認できます
    if coordinator.session.hasItemsConforming(toTypeIdentifiers: [kUTTypePlainText as String]) {
        //この関数を使うと、`coordinator` 内にあるデータオブジェクトを抽出できます。
        coordinator.session.loadObjects(ofClass: NSString.self) { (fetchedItems) in
            guard let toyName = fetchedItems.first as? String else { return }
            //TODO
        }
    }
}

そして、おもちゃの名前が分かれば、そのおもちゃを1匹目の猫から削除して、2匹目の猫に追加できます。

ここでは、おもちゃの名前、1匹目の猫の名前、2匹目の猫の名前が必要です:

おもちゃの名前:

名前の情報は変数toyNameから取得できます。

1匹目の猫の名前

次のコード行を書いたことを思い出してください:

session.localContext = catName

猫の名前は session.localContext に保存してあるので、このコードを使って取得できます:

if let originalCatName = coordinator.session.localDragSession?.localContext as? String {
    //1匹目の猫の名前
}
2匹目の猫の名前

self.catName を呼び出すことで取得できます。

ドラッグ機能の追加に関するコードはここで見ることができます:https://github.com/mszmagic/DragAndDropNekoApp/blob/master/NekoApp/Views/catCollectionViewCell.swift#L50...L72

ドロップ機能の追加に関するコードはここで見ることができます:https://github.com/mszmagic/DragAndDropNekoApp/blob/master/NekoApp/Views/catCollectionViewCell.swift#L74...L93

データを更新する

これで、次のプログラム値が得られました。
- おもちゃの名前
- 最初の猫の名前
- 2番目の猫の名前

次のことが必要です。
- 最初の猫からおもちゃを外す
- 2番目の猫におもちゃを追加する
- テーブルビューをリロードする

これら3つのタスクは ViewController で完了するため、プログラム・デリゲート (Delegate) を使用してこの情報を伝達できます。

protocol dragAndDropActionDelegate: AnyObject {
    func moveToy(toyName: String, fromCat: String, toCat: String)
}

そして、そこでアクションを実行できるよう、このデリゲート (delegate) を ViewController 内にインプリメントします。

extension ViewController: dragAndDropActionDelegate {

    func moveToy(toyName: String, fromCat: String, toCat: String) {
        //最初の猫からおもちゃを外す
        var fromCatToys = catToys[fromCat] ?? []
        fromCatToys.removeAll { (toyNameInArray) -> Bool in
            return toyNameInArray == toyName
        }
        catToys[fromCat] = fromCatToys
        //2番目の猫におもちゃを追加する
        var toCatToys = catToys[toCat] ?? []
        toCatToys.append(toyName)
        catToys[toCat] = toCatToys
        //テーブルビューをリロードする
        collectionView.reloadData()
    }

}

テーブル (catCollectionViewCell) 表示に変数を登録。

//catCollectionViewCell.swift
weak var delegate: dragAndDropActionDelegate?

delegate 変数をセット:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    ...
    cell.delegate = self
    ...
}

そして、ドロップのアクションが実行されたときにデリゲートファンクションをコール。

self.delegate?.moveToy(toyName: toyName, fromCat: originalCatName, toCat: self.catName)

データ配列のアップデートに関連するコードは以下の通り https://github.com/mszmagic/DragAndDropNekoApp/commit/97fb908ce09b031b07c6bbc835fe43a59a9780c2

完成したプロジェクト: https://github.com/mszmagic/DragAndDropNekoApp


:relaxed: Twitter @MszPro

:sunny: 私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5