출처: Adopting Drag and Drop in a Table View
위 문서의 샘플 코드를 정리한 내용입니다.
테이블 뷰에서 드래그 앤 드롭이 가능하게 하는 방법을 알아봅니다.
이를 위해서는 DragDelegate
, DropDelegate
를 채택하여야 하고, 추가적인 기능을 위해서는 델리게이트 메서드를 구현해야 합니다.
이 앱은 외부에서 드래그 된 텍스트를 추가할 수 있고, Row를 드래그하여 Row의 순서를 바꿀 수도 있습니다.
드래그 앤 드롭 API를 사용하지 않고, tableView(_:canMoveRowAt:)
및 tableView(_:moveRowAt:to:)
메서드를 사용합니다.
드래그 및 드롭을 하기 위해서는 각각의 Delegate를 채택 및 구현해야 합니다.
override func viewDidLoad() {
super.viewDidLoad()
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self
navigationItem.rightBarButtonItem = editButtonItem
}
커스텀 뷰와 다르게 TableView는 interactions
프로퍼티가 존재하지 않아, 따로 상호작용을 추가할 수 없습니다.
대신 해당 델리게이트들을 사용합니다.
TableView에서 드래깅이 시작될 때 데이터를 제공할 때는
tableView(_:itemsForBeginning:at:) -> [UIDragItem]
메서드를 구현하여야 합니다.
아이폰을 사용하다 보면 어떠한 앱에서 드래그를 한 후,
다른 앱에 드롭하여 해당 컨텐츠를 추가할 수 있는 경우가 있습니다.
이는 NSItemProvider
을 통해 구현할 수 있습니다.
이 앱에서는 드래그를 시작할 때 NSItemProvider
을 사용하여 해당 기능을 구현하였습니다.
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return model.dragItems(for: indexPath)
}
func dragItems(for indexPath: IndexPath) -> [UIDragItem] {
let placeName = placeNames[indexPath.row]
let data = placeName.data(using: .utf8)
let itemProvider = NSItemProvider()
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypePlainText as String, visibility: .all) { completion in
completion(data, nil)
return nil
}
return [
UIDragItem(itemProvider: itemProvider)
]
}
드롭 세션을 사용하기 위해서는 세 가지의 델리게이트 메서드를 구현해야 합니다.
tableView(_:canHandle:) -> Bool
앱은 상태에 따라, 또는 특정 요구 사항 등에 따라 드롭을 거절해야 하는 상황이 발생할 수 있습니다.
해당 앱은 데이터가 NSString
의 인스턴스일 때만 드롭할 수 있도록 해 놓은 모습입니다.
func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
return model.canHandle(session)
}
func canHandle(_ session: UIDropSession) -> Bool {
return session.canLoadObjects(ofClass: NSString.self)
}
tableView(_:dropSessionDidUpdate:withDestinationIndexPath:) -> UITableViewDropProposal
개발자는 시스템에게 데이터를 어떻게 제공해야 할 지(일반적으로 복사 등)를 알려주어야 합니다.
이는 해당 메서드에서 Drop Proposal을 반환하여 알려줄 수 있습니다.
아래 코드에서는 다음 조건으로 분기하여 Drop Proposal을 결정합니다.
cancel
반환한 개의 드래그 아이템만을 관리
editing
상태라면 move
반환앱 내부에서 드래그가 발생된 경우
copy
반환앱 외부에서 드래그가 들어온 경우
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
var dropProposal = UITableViewDropProposal(operation: .cancel)
// Accept only one drag item.
guard session.items.count == 1 else { return dropProposal }
// The .move drag operation is available only for dragging within this app and while in edit mode.
if tableView.hasActiveDrag {
if tableView.isEditing {
dropProposal = UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
} else {
// Drag is coming from outside the app.
dropProposal = UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
return dropProposal
}
tableView(_:performDropWith:)
사용자가 드롭을 완료할 때 (드래그 후 스크린에서 손가락을 뗄 때), 테이블 뷰는 드래그 아이템에 대한 데이터를 요청할 수 있습니다.
이 앱에서는 해당 메서드에서 아이템이 삽입될 위치와,
삽입 시 처리해야 할 DataSource의 배열 아이템 수정 및
TableView의 indexPath 수정 등을 진행합니다.
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
// Get last index path of table view.
let section = tableView.numberOfSections - 1
let row = tableView.numberOfRows(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
coordinator.session.loadObjects(ofClass: NSString.self) { items in
// Consume drag items.
let stringItems = items as! [String]
var indexPaths = [IndexPath]()
for (index, item) in stringItems.enumerated() {
let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
self.model.addItem(item, at: indexPath.row)
indexPaths.append(indexPath)
}
tableView.insertRows(at: indexPaths, with: .automatic)
}
}
tableView(_:itemsForBeginning:at:) -> [UIDragItem]
에서 드래그가 시작되고, 드래그 할 아이템을 반환tableView(_:canHandle:) -> Bool
메서드에서 앱이 해당 아이템을 드롭할 수 있는지 판단tableView(_:dropSessionDidUpdate:withDestinationIndexPath:) -> UITableViewDropProposal
에서 앱이 드롭될 데이터를 어떻게 처리해야 할 지 결정tableView(_:performDropWith:)
에서 드롭이 완료되었을 때 헤당 데이터를 가지고 특정 행동을 실행