Skip to content

Commit

Permalink
Inter-App drag and drop, Toolbar actions for drag and drop (#318)
Browse files Browse the repository at this point in the history
* #250 implements drag & drop files from outside into the app

* - implemented dragging outside the app to other apps
- implemented drop to UIToolbarButtonItem

* added delegate method to support dragging multiple items outside

* - completed drag and drop on toolbar button items
- fixed multiple items drop

* Fixed items for ActionContext from Multiselection

* - removed dead code
- fixed code review issues

* - locally available files could not used for drag and drop (internally)
- prevent dropping folders
  • Loading branch information
hosy authored Mar 27, 2019
1 parent db0990f commit 5a16019
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 66 deletions.
4 changes: 4 additions & 0 deletions ownCloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
23EC775D2137FB6B0032D4E6 /* WebViewDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EC775C2137FB6B0032D4E6 /* WebViewDisplayViewController.swift */; };
23F6238120B587EF004FDE8B /* SortMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F6238020B587EF004FDE8B /* SortMethod.swift */; };
23FA23E620BFD3D8009A6D73 /* SortBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FA23E520BFD3D8009A6D73 /* SortBar.swift */; };
39104E10223991C8002FC02F /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39104E0A223991C8002FC02F /* UIButton+Extension.swift */; };
39607CBC2225D480007B386D /* UITableViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39607CBB2225D480007B386D /* UITableViewController+Extension.swift */; };
3971B48F221B23FE006FB441 /* ThemeableColoredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3971B48E221B23FE006FB441 /* ThemeableColoredView.swift */; };
39878B7421FB1DE800DBF693 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39878B7321FB1DE800DBF693 /* UINavigationController+Extension.swift */; };
Expand Down Expand Up @@ -473,6 +474,7 @@
23EC775C2137FB6B0032D4E6 /* WebViewDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewDisplayViewController.swift; sourceTree = "<group>"; };
23F6238020B587EF004FDE8B /* SortMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortMethod.swift; sourceTree = "<group>"; };
23FA23E520BFD3D8009A6D73 /* SortBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBar.swift; sourceTree = "<group>"; };
39104E0A223991C8002FC02F /* UIButton+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = "<group>"; };
39607CBB2225D480007B386D /* UITableViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewController+Extension.swift"; sourceTree = "<group>"; };
3971B48E221B23FE006FB441 /* ThemeableColoredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeableColoredView.swift; sourceTree = "<group>"; };
39878B7321FB1DE800DBF693 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -861,6 +863,7 @@
239F1314205A69240029F186 /* UIKit Extensions */ = {
isa = PBXGroup;
children = (
39104E0A223991C8002FC02F /* UIButton+Extension.swift */,
239F1318205A693A0029F186 /* UIColor+Extension.swift */,
2347446920761BB700859C93 /* String+Extension.swift */,
DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */,
Expand Down Expand Up @@ -1932,6 +1935,7 @@
DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */,
4C6B78122226B86300C5F3DB /* PhotoAlbumTableViewCell.swift in Sources */,
39607CBC2225D480007B386D /* UITableViewController+Extension.swift in Sources */,
39104E10223991C8002FC02F /* UIButton+Extension.swift in Sources */,
DC422450207CB2500006A2A6 /* NSObject+ThemeApplication.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
243 changes: 177 additions & 66 deletions ownCloud/Client/ClientQueryViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Photos
typealias ClientActionVieDidAppearHandler = () -> Void
typealias ClientActionCompletionHandler = (_ actionPerformed: Bool) -> Void

class ClientQueryViewController: UITableViewController, Themeable {
class ClientQueryViewController: UITableViewController, Themeable, UIDropInteractionDelegate {
weak var core : OCCore?
var query : OCQuery

Expand All @@ -49,7 +49,6 @@ class ClientQueryViewController: UITableViewController, Themeable {
var queryRefreshControl: UIRefreshControl?

let flexibleSpaceBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

var deleteMultipleBarButtonItem: UIBarButtonItem?
var moveMultipleBarButtonItem: UIBarButtonItem?

Expand Down Expand Up @@ -158,12 +157,10 @@ class ClientQueryViewController: UITableViewController, Themeable {
self.navigationItem.rightBarButtonItems = [selectBarButton!, uploadBarButton!]

// Create bar button items for the toolbar
deleteMultipleBarButtonItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(actOnMultipleItems))
deleteMultipleBarButtonItem?.actionIdentifier = DeleteAction.identifier
deleteMultipleBarButtonItem = UIBarButtonItem(image: UIImage(named:"trash"), target: self as AnyObject, action: #selector(actOnMultipleItems), dropTarget: self, actionIdentifier: DeleteAction.identifier!)
deleteMultipleBarButtonItem?.isEnabled = false

moveMultipleBarButtonItem = UIBarButtonItem(barButtonSystemItem: .organize, target: self, action: #selector(actOnMultipleItems))
moveMultipleBarButtonItem?.actionIdentifier = MoveAction.identifier
moveMultipleBarButtonItem = UIBarButtonItem(image: UIImage(named:"folder"), target: self as AnyObject, action: #selector(actOnMultipleItems), dropTarget: self, actionIdentifier: MoveAction.identifier!)
moveMultipleBarButtonItem?.isEnabled = false

self.addThemableBackgroundView()
Expand Down Expand Up @@ -346,6 +343,11 @@ class ClientQueryViewController: UITableViewController, Themeable {
}

func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
for item in session.items {
if item.localObject == nil, item.itemProvider.hasItemConformingToTypeIdentifier("public.folder") {
return false
}
}
return true
}

Expand All @@ -369,23 +371,6 @@ class ClientQueryViewController: UITableViewController, Themeable {
return configuration
}

func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
let item: OCItem = itemAtIndexPath(indexPath)

guard item.type != .collection else {
return []
}

guard let data = item.serializedData() else {
return []
}

let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypeData as String)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}

func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {

if session.localDragSession != nil {
Expand All @@ -399,10 +384,73 @@ class ClientQueryViewController: UITableViewController, Themeable {
return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
}
} else {
return UITableViewDropProposal(operation: .forbidden)
return UITableViewDropProposal(operation: .copy)
}
}

func updateToolbarItemsForDropping(_ items: [OCItem]) {
guard let tabBarController = self.tabBarController as? ClientRootViewController else { return }
guard let toolbarItems = tabBarController.toolbar?.items else { return }

if let core = self.core {
// Remove duplicates
let uniqueItems = Array(Set(items))
// Get possible associated actions
let actionsLocation = OCExtensionLocation(ofType: .action, identifier: .toolbar)
let actionContext = ActionContext(viewController: self, core: core, items: uniqueItems, location: actionsLocation)
self.actions = Action.sortedApplicableActions(for: actionContext)

// Enable / disable tool-bar items depending on action availability
for item in toolbarItems {
if self.actions?.contains(where: {type(of:$0).identifier == item.actionIdentifier}) ?? false {
item.isEnabled = true
} else {
item.isEnabled = false
}
}
}

}

func tableView(_: UITableView, dragSessionDidEnd: UIDragSession) {
removeToolbar()
self.actions = nil
}

// MARK: - UIBarButtonItem Drop Delegate

func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
return true
}

func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
return UIDropProposal(operation: .copy)
}

func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
guard let button = interaction.view as? UIButton, let identifier = button.actionIdentifier else { return }

if let action = self.actions?.first(where: {type(of:$0).identifier == identifier}) {
// Configure progress handler
action.progressHandler = { [weak self] progress in
self?.progressSummarizer?.startTracking(progress: progress)
}

action.completionHandler = { [weak self] _ in
}

// Execute the action
action.willRun()
action.run()
}
}

func dragInteraction(_ interaction: UIDragInteraction,
session: UIDragSession,
didEndWith operation: UIDropOperation) {
removeToolbar()
}

// MARK: - Message
var messageView : UIView?
var messageContainerView : UIView?
Expand Down Expand Up @@ -669,8 +717,7 @@ class ClientQueryViewController: UITableViewController, Themeable {
removeToolbar()
}

@objc func actOnMultipleItems(_ sender: UIBarButtonItem) {

@objc func actOnMultipleItems(_ sender: UIButton) {
// Find associated action
if let action = self.actions?.first(where: {type(of:$0).identifier == sender.actionIdentifier}) {
// Configure progress handler
Expand Down Expand Up @@ -916,47 +963,54 @@ extension ClientQueryViewController: UITableViewDropDelegate {
guard let core = self.core else { return }

for item in coordinator.items {

var destinationItem: OCItem

guard let item = item.dragItem.localObject as? OCItem, let itemName = item.name else {
return
}

if coordinator.proposal.intent == .insertIntoDestinationIndexPath {

guard let destinationIP = coordinator.destinationIndexPath else {
if item.dragItem.localObject != nil {
var destinationItem: OCItem

guard let item = item.dragItem.localObject as? OCItem, let itemName = item.name else {
return
}

guard items.count >= destinationIP.row else {
return

if coordinator.proposal.intent == .insertIntoDestinationIndexPath {

guard let destinationIndexPath = coordinator.destinationIndexPath else {
return
}

guard items.count >= destinationIndexPath.row else {
return
}

let rootItem = items[destinationIndexPath.row]

guard rootItem.type == .collection else {
return
}

destinationItem = rootItem

} else {

guard let rootItem = self.query.rootItem, item.parentFileID != rootItem.fileID else {
return
}

destinationItem = rootItem

}

let rootItem = items[destinationIP.row]

guard rootItem.type == .collection else {
return

if let progress = core.move(item, to: destinationItem, withName: itemName, options: nil, resultHandler: { (error, _, _, _) in
if error != nil {
Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))")
}
}) {
self.progressSummarizer?.startTracking(progress: progress)
}

destinationItem = rootItem

} else {

guard let rootItem = self.query.rootItem, item.parentFileID != rootItem.fileID else {
return
guard let UTI = item.dragItem.itemProvider.registeredTypeIdentifiers.last else { return }
item.dragItem.itemProvider.loadFileRepresentation(forTypeIdentifier: UTI) { (url, _ error) in
guard let url = url else { return }
self.upload(itemURL: url, name: url.lastPathComponent)
}

destinationItem = rootItem

}

if let progress = core.move(item, to: destinationItem, withName: itemName, options: nil, resultHandler: { (error, _, _, _) in
if error != nil {
Log.log("Error \(String(describing: error)) moving \(String(describing: item.path))")
}
}) {
self.progressSummarizer?.startTracking(progress: progress)
}
}
}
Expand All @@ -965,18 +1019,75 @@ extension ClientQueryViewController: UITableViewDropDelegate {
extension ClientQueryViewController: UITableViewDragDelegate {

func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
self.populateToolbar(with: [moveMultipleBarButtonItem!, flexibleSpaceBarButton, deleteMultipleBarButtonItem!])

var selectedItems = [OCItem]()
// Add Items from Multiselection too
if let selectedIndexPaths = self.tableView.indexPathsForSelectedRows {
if selectedIndexPaths.count > 0 {
for indexPath in selectedIndexPaths {
selectedItems.append(itemAtIndexPath(indexPath))
}
}
}
for dragItem in session.items {
guard let item = dragItem.localObject as? OCItem else { continue }
selectedItems.append(item)
}

let item: OCItem = itemAtIndexPath(indexPath)
selectedItems.append(item)
updateToolbarItemsForDropping(selectedItems)

guard let data = item.serializedData() else {
return []
guard let dragItem = itemForDragging(item: item) else { return [] }
return [dragItem]
}

func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
var selectedItems = [OCItem]()
for dragItem in session.items {
guard let item = dragItem.localObject as? OCItem else { continue }
selectedItems.append(item)
}

let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypeData as String)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
let item: OCItem = itemAtIndexPath(indexPath)
selectedItems.append(item)
updateToolbarItemsForDropping(selectedItems)

guard let dragItem = itemForDragging(item: item) else { return [] }
return [dragItem]
}

func itemForDragging(item : OCItem) -> UIDragItem? {
if let core = self.core {
switch item.type {
case .collection:
guard let data = item.serializedData() else { return nil }
let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypeData as String)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return dragItem
case .file:
guard let rawUti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, item.mimeType as! CFString, nil)?.takeRetainedValue() else { return nil }

if let fileData = NSData(contentsOf: core.localURL(for: item)) {
let itemProvider = NSItemProvider(item: fileData, typeIdentifier: rawUti as! String)
itemProvider.suggestedName = item.name
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return dragItem
} else {
guard let data = item.serializedData() else { return nil }
let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypeData as String)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return dragItem
}
}
}

return nil
}
}

// MARK: - UIDocumentPickerDelegate
Expand Down
16 changes: 16 additions & 0 deletions ownCloud/Resources/Assets.xcassets/folder.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "folder.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template",
"preserves-vector-representation" : true
}
}
Binary file not shown.
16 changes: 16 additions & 0 deletions ownCloud/Resources/Assets.xcassets/trash.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "trash-25.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template",
"preserves-vector-representation" : true
}
}
Binary file not shown.
Loading

0 comments on commit 5a16019

Please sign in to comment.