並び替えの出来るUICollectionViewは前回の方法で実現できますが、iOS11で追加されたドラッグ・アンド・ドロップの機能を使った方法もあります。
このドラッグ・アンド・ドロップ機能は、本来はiPad上で2つのアプリを開いた状態で、アプリをまたいだセルのドラッグ・アンド・ドロップで、データを受け渡すために導入されたものです。iPhoneでは、複数のアプリを開くことは出来ませんので、同じアプリ内でのドラッグ・アンド・ドロップになります。
今回は、このドラッグ・アンド・ドロップをUICollectionViewの中に適用することでセルの並び替えを実装します。
♪ ♪ ♪
まず、前回「UICollectionViewの並び替え(よくある編)」で作成した「とりあえずUICollectionViewを表示」のプロジェクトを用意します。ここにドラッグ・アンド・ドロップの処理を追加していきます。追加するのはUICollectionViewDragDelegateとUICollectionViewDropDelegateです。
ViewController+UICollectionViewDragDelegate.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
extension ViewController: UICollectionViewDragDelegate { | |
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { | |
let n = "\(numbers[indexPath.item])" | |
let itemProvider = NSItemProvider(object: n as NSString) | |
let dragItem = UIDragItem(itemProvider: itemProvider) | |
return [dragItem] | |
} | |
} |
indexPathでセルの位置がわかるので、ドラッグされるアイテムとしてUIDragItemの配列にして返します。
ViewController+UICollectionViewDropDelegate.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
extension ViewController: UICollectionViewDropDelegate { | |
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool { | |
return session.hasItemsConforming(toTypeIdentifiers: NSString.readableTypeIdentifiersForItemProvider) | |
} | |
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { | |
if session.localDragSession != nil { | |
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) | |
} else { | |
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) | |
} | |
} | |
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { | |
let destinationIndexPath: IndexPath //移動先 | |
if let indexPath = coordinator.destinationIndexPath, indexPath.row < collectionView.numberOfItems(inSection: 0) { | |
destinationIndexPath = indexPath | |
} else { | |
let section = collectionView.numberOfSections - 1 | |
let item = collectionView.numberOfItems(inSection: section) - 1 | |
//余白にドロップしたときは、末尾に移動 | |
destinationIndexPath = IndexPath(item: item, section: section) | |
} | |
switch coordinator.proposal.operation { | |
case .move: | |
let items = coordinator.items | |
if items.contains(where: { $0.sourceIndexPath != nil }) { | |
if items.count == 1, let item = items.first { | |
reorder(collectionView, item: item, to: destinationIndexPath, with: coordinator) //セルの並び替え | |
} | |
} | |
default: | |
return | |
} | |
} | |
// MARK: - PRIVATE METHODS | |
/// セルの並び替え | |
/// | |
/// - Parameters: | |
/// - sourceIndexPath: 移動元の位置 | |
/// - destinationIndexPath: 移動先の位置 | |
private func reorder(_ collectionView: UICollectionView, item: UICollectionViewDropItem, to destinationIndexPath: IndexPath, with coordinator: UICollectionViewDropCoordinator) { | |
guard let sourceIndexPath = item.sourceIndexPath else { | |
return | |
} | |
collectionView.performBatchUpdates({ | |
//配列の更新 | |
let n = numbers.remove(at: sourceIndexPath.item) | |
numbers.insert(n, at: destinationIndexPath.item) | |
//セルの移動 | |
collectionView.deleteItems(at: [sourceIndexPath]) | |
collectionView.insertItems(at: [destinationIndexPath]) | |
}) | |
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath) | |
} | |
} |
ViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class ViewController: UIViewController { | |
@IBOutlet weak var collectionView: UICollectionView! | |
var numbers: [Int] = [] | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
for n in 0..<100 { | |
numbers.append(n) | |
} | |
collectionView.dragDelegate = self | |
collectionView.dropDelegate = self | |
collectionView.dragInteractionEnabled = true //ドラッグ可能に | |
} | |
} |
iOS10までしか対応されていないiPhone5などがまだ現役だったりすることを考えると、ちょっと採用しづらいかもしれませんが、こっちにはこっちの利点があると思うので、この方法も選択肢のひとつとしておくのも良いのではないでしょうか。
iOS11のドラッグ・アンド・ドロップ機能は、この本が詳しいです。