Browse Source

Disable DnD

Soomin Lee 7 years ago
parent
commit
d066b46919
2 changed files with 505 additions and 504 deletions
  1. 475 474
      Sources/VLCDragAndDropManager.swift
  2. 30 30
      Sources/VLCMediaSubcategory+VLCDragAndDrop.swift

+ 475 - 474
Sources/VLCDragAndDropManager.swift

@@ -33,478 +33,479 @@ protocol VLCDragAndDropManagerDelegate: NSObjectProtocol {
 }
 }
 
 
 @available(iOS 11.0, *)
 @available(iOS 11.0, *)
-class VLCDragAndDropManager<T>: NSObject, UICollectionViewDragDelegate, UITableViewDragDelegate, UICollectionViewDropDelegate, UITableViewDropDelegate, UIDropInteractionDelegate {
-
-    let utiTypeIdentifiers: [String] = VLCDragAndDropManager.supportedTypeIdentifiers()
-    var subcategory: VLCMediaSubcategoryModel<T>
-    /// Returns the supported type identifiers that VLC can process.
-    /// It fetches the identifiers in LSItemContentTypes from all the CFBundleDocumentTypes in the info.plist.
-    /// Video, Audio and Subtitle formats
-    ///
-    /// - Returns: Array of UTITypeIdentifiers
-    private class func supportedTypeIdentifiers() -> [String] {
-        var typeIdentifiers: [String] = []
-        if let documents = Bundle.main.infoDictionary?["CFBundleDocumentTypes"] as? [[String: Any]] {
-            for item in documents {
-                if let value = item["LSItemContentTypes"] as? [String] {
-                    typeIdentifiers.append(contentsOf: value)
-                }
-            }
-        }
-        return typeIdentifiers
-    }
-
-    @available(*, unavailable, message: "use init(category:)")
-    override init() {
-        fatalError()
-    }
-
-    init(subcategory: VLCMediaSubcategoryModel<T>) {
-        self.subcategory = subcategory
-        super.init()
-    }
-
-    // MARK: - TableView
-
-    func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
-        return canHandleDropSession(session: session)
-    }
-
-    func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
-        return dragItems(forIndexPath: indexPath)
-    }
-
-    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
-        return dragItems(forIndexPath: indexPath)
-    }
-
-    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
-        let operation = dropOperation(hasActiveDrag: tableView.hasActiveDrag, firstSessionItem: session.items.first, withDestinationIndexPath: destinationIndexPath)
-        return UITableViewDropProposal(operation: operation, intent: .insertIntoDestinationIndexPath)
-    }
-
-    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
-        let section = tableView.numberOfSections - 1
-        let row = tableView.numberOfRows(inSection: section)
-        let destinationPath = coordinator.destinationIndexPath ?? IndexPath(row: row, section: section)
-
-        for item in coordinator.items {
-            let itemProvider = item.dragItem.itemProvider
-            // we're not gonna handle moving of folders
-            if let sourceItem = item.dragItem.localObject, fileIsCollection(file: sourceItem as AnyObject) {
-                continue
-            }
-
-            if fileIsFolder(atIndexPath: destinationPath) { // handle dropping onto a folder
-                addDragItem(tableView: tableView, dragItem: item, toFolderAt: destinationPath)
-                continue
-            }
-
-            if item.sourceIndexPath != nil { // element within VLC
-                moveItem(tableView: tableView, item: item, toIndexPath: destinationPath)
-                continue
-            }
-            // Element dragging from another App
-            let placeholder = UITableViewDropPlaceholder(insertionIndexPath: destinationPath, reuseIdentifier: VLCPlaylistTableViewCell.cellIdentifier(), rowHeight: VLCPlaylistTableViewCell.heightOfCell())
-            let placeholderContext = coordinator.drop(item.dragItem, to: placeholder)
-            createFileWith(itemProvider: itemProvider) {
-                [weak self] file, error in
-
-                guard let strongSelf = self else { return }
-
-                if let file = file {
-                    placeholderContext.commitInsertion() {
-                        insertionIndexPath in
-                        strongSelf.subcategory.dragAndDropManagerInsertItem(manager: strongSelf, item: file, atIndexPath: insertionIndexPath)
-                    }
-                }
-                if let error = error as? DropError {
-                    strongSelf.handleError(error: error, itemProvider: item.dragItem.itemProvider)
-                    placeholderContext.deletePlaceholder()
-                }
-            }
-        }
-    }
-
-    private func inFolder() -> Bool {
-        return subcategory.dragAndDropManagerCurrentSelection(manager: self) as? MLLabel != nil
-    }
-
-    private func moveItem(tableView: UITableView, item: UITableViewDropItem, toIndexPath destinationPath: IndexPath) {
-        if let mlFile = item.dragItem.localObject as? MLFile, !mlFile.labels.isEmpty && !inFolder() {
-            tableView.performBatchUpdates({
-                tableView.insertRows(at: [destinationPath], with: .automatic)
-                subcategory.dragAndDropManagerInsertItem(manager: self, item: mlFile, atIndexPath: destinationPath)
-                subcategory.dragAndDropManagerRemoveFileFromFolder(manager: self, file: mlFile)
-            }, completion: nil)
-        }
-    }
-
-    private func addDragItem(tableView: UITableView, dragItem item: UITableViewDropItem, toFolderAt index: IndexPath) {
-        if let sourcepath = item.sourceIndexPath { // local file that just needs to be moved
-            tableView.performBatchUpdates({
-                if let file = subcategory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: sourcepath) as? MLFile {
-                    tableView.deleteRows(at: [sourcepath], with: .automatic)
-                    addFile(file: file, toFolderAt: index)
-                    subcategory.dragAndDropManagerDeleteItem(manager: self, atIndexPath: sourcepath)
-                }
-            }, completion: nil)
-            return
-        }
-        // file from other app
-        createFileWith(itemProvider: item.dragItem.itemProvider) {
-            [weak self] file, error in
-
-            if let strongSelf = self, let file = file {
-                strongSelf.addFile(file: file, toFolderAt: index)
-            }
-        }
-    }
-
-    // MARK: - Collectionview
-
-    func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
-        return canHandleDropSession(session: session)
-    }
-
-    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
-        return dragItems(forIndexPath: indexPath)
-    }
-
-    func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
-        return dragItems(forIndexPath: indexPath)
-    }
-
-    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
-        let operation = dropOperation(hasActiveDrag: collectionView.hasActiveDrag, firstSessionItem: session.items.first, withDestinationIndexPath: destinationIndexPath)
-        return UICollectionViewDropProposal(operation: operation, intent: .insertIntoDestinationIndexPath)
-    }
-
-    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
-        let section = collectionView.numberOfSections - 1
-        let row = collectionView.numberOfItems(inSection: section)
-        let destinationPath = coordinator.destinationIndexPath ?? IndexPath(row: row, section: section)
-
-        for item in coordinator.items {
-            if let sourceItem = item.dragItem.localObject, fileIsCollection(file: sourceItem as AnyObject) { // We're not handling moving of Collection
-                continue
-            }
-            if fileIsFolder(atIndexPath: destinationPath) { // handle dropping onto a folder
-                addDragItem(collectionView: collectionView, dragItem: item, toFolderAt: destinationPath)
-                continue
-            }
-            if item.sourceIndexPath != nil { // element within VLC
-                moveItem(collectionView: collectionView, item: item, toIndexPath: destinationPath)
-                continue
-            }
-            // Element from another App
-            let placeholder = UICollectionViewDropPlaceholder(insertionIndexPath: destinationPath, reuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier())
-            let placeholderContext = coordinator.drop(item.dragItem, to: placeholder)
-            createFileWith(itemProvider: item.dragItem.itemProvider) {
-                [weak self] file, error in
-
-                guard let strongSelf = self else { return }
-
-                if let file = file {
-                    placeholderContext.commitInsertion() {
-                        insertionIndexPath in
-                        strongSelf.subcategory.dragAndDropManagerInsertItem(manager: strongSelf, item: file, atIndexPath: insertionIndexPath)
-                    }
-                }
-                if let error = error as? DropError {
-                    strongSelf.handleError(error: error, itemProvider: item.dragItem.itemProvider)
-                    placeholderContext.deletePlaceholder()
-                }
-            }
-        }
-    }
-
-    private func moveItem(collectionView: UICollectionView, item: UICollectionViewDropItem, toIndexPath destinationPath: IndexPath) {
-        if let mlFile = item.dragItem.localObject as? MLFile, !mlFile.labels.isEmpty && !inFolder() {
-            collectionView.performBatchUpdates({
-                collectionView.insertItems(at: [destinationPath])
-                subcategory.dragAndDropManagerInsertItem(manager: self, item: mlFile, atIndexPath: destinationPath)
-                subcategory.dragAndDropManagerRemoveFileFromFolder(manager: self, file: mlFile)
-            }, completion: nil)
-        }
-    }
-
-    private func addDragItem(collectionView: UICollectionView, dragItem item: UICollectionViewDropItem, toFolderAt index: IndexPath) {
-        if let sourcepath = item.sourceIndexPath {
-            // local file that just needs to be moved
-            collectionView.performBatchUpdates({
-                if let file = subcategory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: sourcepath) as? MLFile {
-                    collectionView.deleteItems(at: [sourcepath])
-                    addFile(file: file, toFolderAt: index)
-                    subcategory.dragAndDropManagerDeleteItem(manager: self, atIndexPath: sourcepath)
-                }
-            }, completion: nil)
-        } else {
-            // file from other app
-            createFileWith(itemProvider: item.dragItem.itemProvider) {
-                [weak self] file, error in
-                if let strongSelf = self, let file = file {
-                    strongSelf.addFile(file: file, toFolderAt: index)
-                }
-            }
-        }
-    }
-
-    // MARK: - DropInteractionDelegate for EmptyView
-
-    func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
-        return canHandleDropSession(session: session)
-    }
-
-    func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
-        return UIDropProposal(operation: .copy)
-    }
-
-    func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
-        for item in session.items {
-            createFileWith(itemProvider: item.itemProvider) {
-                [weak self] _, error in
-                if let error = error as? DropError {
-                    self?.handleError(error: error, itemProvider: item.itemProvider)
-                }
-                // no need to handle the file case since the libraryVC updates itself after getting a file
-            }
-        }
-    }
-
-    // MARK: - Shared Methods
-
-    // Checks if the session has items conforming to typeidentifiers
-    private func canHandleDropSession(session: UIDropSession) -> Bool {
-        if session.localDragSession != nil {
-            return true
-        }
-        return session.hasItemsConforming(toTypeIdentifiers: utiTypeIdentifiers)
-    }
-
-    /// Returns a drop operation type
-    ///
-    /// - Parameters:
-    ///   - hasActiveDrag: State if the drag started within the app
-    ///   - item: UIDragItem from session
-    /// - Returns: UIDropOperation
-    private func dropOperation(hasActiveDrag: Bool, firstSessionItem item: AnyObject?, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UIDropOperation {
-        let inAlbum = subcategory.dragAndDropManagerCurrentSelection(manager: self) as? MLAlbum != nil
-        let inShow = subcategory.dragAndDropManagerCurrentSelection(manager: self) as? MLShow != nil
-        // you can move files into a folder or copy from anothr app into a folder
-        if fileIsFolder(atIndexPath: destinationIndexPath) {
-            // no dragging entire shows and albums into folders
-            if let dragItem = item, let mlFile = dragItem.localObject as? MLFile, mlFile.isAlbumTrack() || mlFile.isShowEpisode() {
-                return .forbidden
-            }
-            return hasActiveDrag ? .move : .copy
-        }
-        // you can't reorder
-        if inFolder() {
-            return hasActiveDrag ? .forbidden : .copy
-        }
-        // you can't reorder in or drag into an Album or Show
-        if inAlbum || inShow {
-            return .cancel
-        }
-        // we're dragging a file out of a folder
-        if let dragItem = item, let mlFile = dragItem.localObject as? MLFile, !mlFile.labels.isEmpty {
-            return .copy
-        }
-        // no reorder from another app into the top layer
-        return hasActiveDrag ? .forbidden : .copy
-    }
-
-    /// show an Alert when dropping failed
-    ///
-    /// - Parameters:
-    ///   - error: the type of error that happend
-    ///   - itemProvider: the itemProvider to retrieve the suggestedName
-    private func handleError(error: DropError, itemProvider: NSItemProvider) {
-        let message: String
-        let filename = itemProvider.suggestedName ?? NSLocalizedString("THIS_FILE", comment: "")
-        switch error.kind {
-        case .loadFileRepresentationFailed:
-            message = String(format: NSLocalizedString("NOT_SUPPORTED_FILETYPE", comment: ""), filename)
-        case .moveFileToDocuments:
-            message = String(format: NSLocalizedString("FILE_EXISTS", comment: ""), filename)
-        }
-        let alert = UIAlertController(title: NSLocalizedString("ERROR", comment: ""), message: message, preferredStyle: .alert)
-        alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
-        UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
-    }
-
-    private func fileIsFolder(atIndexPath indexPath: IndexPath?) -> Bool {
-        if let indexPath = indexPath {
-            let file = subcategory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath)
-            return file as? MLLabel != nil
-        }
-        return false
-    }
-
-    private func fileIsCollection(file: Any?) -> Bool {
-        let isFolder = file as? MLLabel != nil
-        let isAlbum = file as? MLAlbum != nil
-        let isShow = file as? MLShow != nil
-        return isFolder || isAlbum || isShow
-    }
-
-    private func fileIsCollection(atIndexPath indexPath: IndexPath?) -> Bool {
-        if let indexPath = indexPath {
-            if let file = subcategory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath) {
-                return fileIsCollection(file:file)
-            }
-        }
-        return false
-    }
-
-    // creating dragItems for the file at indexpath
-    private func dragItems(forIndexPath indexPath: IndexPath) -> [UIDragItem] {
-        if let file = subcategory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath) {
-            if fileIsCollection(atIndexPath: indexPath) {
-                return dragItemsforCollection(file: file)
-            }
-            return dragItem(fromFile: file)
-        }
-        assert(false, "we can't generate a dragfile if the delegate can't return a file ")
-        return []
-    }
-
-    /// Iterates over the items of a collection to create dragitems.
-    /// Since we're not storing collections as folders we have to provide single files
-    ///
-    /// - Parameter file: Can be of type MLAlbum, MLLabel or MLShow
-    /// - Returns: An array of UIDragItems
-    private func dragItemsforCollection(file: Any) -> [UIDragItem] {
-        var dragItems = [UIDragItem]()
-        var set = Set<AnyHashable>()
-        if let folder = file as? MLLabel {
-            set = folder.files
-        } else if let album = file as? MLAlbum {
-            for track in album.tracks {
-                if let mlfile = (track as? MLAlbumTrack)?.files.first {
-                    _ = set.insert(mlfile)
-                }
-            }
-        } else if let show = file as? MLShow {
-            for episode in show.episodes {
-                if let mlfile = (episode as? MLShowEpisode)?.files {
-                    set = set.union(mlfile)
-                }
-            }
-        } else {
-            assert(false, "can't get dragitems from a file that is not a collection")
-        }
-        for convertibleFile in set {
-            if let mlfile = convertibleFile as? MLFile, let item = dragItem(fromFile: mlfile).first {
-                dragItems.append(item)
-            }
-        }
-        return dragItems
-    }
-
-    //Provides an item for other applications
-    private func dragItem(fromFile file: Any) -> [UIDragItem] {
-        guard let file = mlFile(from: file as AnyObject), let path = file.url else {
-            assert(false, "can't create a dragitem if there is no file or the file has no url")
-            return []
-        }
-
-        let data = try? Data(contentsOf: path, options: .mappedIfSafe)
-        let itemProvider = NSItemProvider()
-        itemProvider.suggestedName = path.lastPathComponent
-        // maybe use UTTypeForFileURL
-        if let identifiers = try? path.resourceValues(forKeys: [.typeIdentifierKey]), let identifier = identifiers.typeIdentifier {
-            // here we can show progress
-            itemProvider.registerDataRepresentation(forTypeIdentifier: identifier, visibility: .all) { completion -> Progress? in
-                completion(data, nil)
-                return nil
-            }
-            let dragitem = UIDragItem(itemProvider: itemProvider)
-            dragitem.localObject = file
-            return [dragitem]
-        }
-        assert(false, "we can't provide a typeidentifier")
-        return []
-    }
-
-    private func mlFile(from file: AnyObject) -> MLFile? {
-        if let episode = file as? MLShowEpisode, let convertedfile = episode.files.first as? MLFile {
-            return convertedfile
-        }
-
-        if let track = file as? MLAlbumTrack, let convertedfile = track.files.first as? MLFile {
-            return convertedfile
-        }
-
-        if let convertedfile = file as? MLFile {
-            return convertedfile
-        }
-        return nil
-    }
-
-    private func addFile(file: MLFile, toFolderAt folderIndex: IndexPath) {
-        let label = subcategory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: folderIndex) as! MLLabel
-        DispatchQueue.main.async {
-            _ = label.files.insert(file)
-            file.labels = [label]
-            file.folderTrackNumber = NSNumber(integerLiteral: label.files.count - 1)
-        }
-    }
-
-    /// try to create a file from the dropped item
-    ///
-    /// - Parameters:
-    ///   - itemProvider: itemprovider which is used to load the files from
-    ///   - completion: callback with the successfully created file or error if it failed
-    private func createFileWith(itemProvider: NSItemProvider, completion: @escaping ((MLFile?, Error?) -> Void)) {
-        itemProvider.loadFileRepresentation(forTypeIdentifier: kUTTypeData as String) {
-            [weak self] (url, error) in
-            guard let strongSelf = self else { return }
-
-            guard let url = url else {
-                DispatchQueue.main.async {
-                    completion(nil, DropError(kind: .loadFileRepresentationFailed))
-                }
-                return
-            }
-            // returns nil for local session but this should also not be called for a local session
-            guard let destinationURL = strongSelf.moveFileToDocuments(fromURL: url) else {
-                DispatchQueue.main.async {
-                    completion(nil, DropError(kind: .moveFileToDocuments))
-                }
-                return
-            }
-            DispatchQueue.global(qos: .background).async {
-                let sharedlib = MLMediaLibrary.sharedMediaLibrary() as? MLMediaLibrary
-                sharedlib?.addFilePaths([destinationURL.path])
-
-                if let file = MLFile.file(for: destinationURL).first as? MLFile {
-                    DispatchQueue.main.async {
-                        // we dragged into a folder
-                        if let selection = strongSelf.subcategory.dragAndDropManagerCurrentSelection(manager: strongSelf) as? MLLabel {
-                            file.labels = [selection]
-                        }
-                        completion(file, nil)
-                    }
-                }
-            }
-        }
-    }
-
-    private func moveFileToDocuments(fromURL filepath: URL?) -> URL? {
-        let searchPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
-        let newDirectoryPath = searchPaths.first
-        guard let directoryPath = newDirectoryPath, let url = filepath else {
-            return nil
-        }
-        let destinationURL = URL(fileURLWithPath: "\(directoryPath)" + "/" + "\(url.lastPathComponent)")
-        do {
-            try FileManager.default.moveItem(at: url, to: destinationURL)
-        } catch let error {
-            print(error.localizedDescription)
-            return nil
-        }
-        return destinationURL
-    }
+class VLCDragAndDropManager<ModelType>: NSObject {
+//, UICollectionViewDragDelegate, UITableViewDragDelegate, UICollectionViewDropDelegate, UITableViewDropDelegate, UIDropInteractionDelegate {
+
+//    let utiTypeIdentifiers: [String] = VLCDragAndDropManager.supportedTypeIdentifiers()
+//    var cateory: ModelType
+//    /// Returns the supported type identifiers that VLC can process.
+//    /// It fetches the identifiers in LSItemContentTypes from all the CFBundleDocumentTypes in the info.plist.
+//    /// Video, Audio and Subtitle formats
+//    ///
+//    /// - Returns: Array of UTITypeIdentifiers
+//    private class func supportedTypeIdentifiers() -> [String] {
+//        var typeIdentifiers: [String] = []
+//        if let documents = Bundle.main.infoDictionary?["CFBundleDocumentTypes"] as? [[String: Any]] {
+//            for item in documents {
+//                if let value = item["LSItemContentTypes"] as? [String] {
+//                    typeIdentifiers.append(contentsOf: value)
+//                }
+//            }
+//        }
+//        return typeIdentifiers
+//    }
+//
+//    @available(*, unavailable, message: "use init(category:)")
+//    override init() {
+//        fatalError()
+//    }
+//
+//    init(cateory: ModelType) {
+//        self.cateory = cateory
+//        super.init()
+//    }
+//
+//    // MARK: - TableView
+//
+//    func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
+//        return canHandleDropSession(session: session)
+//    }
+//
+//    func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
+//        return dragItems(forIndexPath: indexPath)
+//    }
+//
+//    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
+//        return dragItems(forIndexPath: indexPath)
+//    }
+//
+//    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
+//        let operation = dropOperation(hasActiveDrag: tableView.hasActiveDrag, firstSessionItem: session.items.first, withDestinationIndexPath: destinationIndexPath)
+//        return UITableViewDropProposal(operation: operation, intent: .insertIntoDestinationIndexPath)
+//    }
+//
+//    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
+//        let section = tableView.numberOfSections - 1
+//        let row = tableView.numberOfRows(inSection: section)
+//        let destinationPath = coordinator.destinationIndexPath ?? IndexPath(row: row, section: section)
+//
+//        for item in coordinator.items {
+//            let itemProvider = item.dragItem.itemProvider
+//            // we're not gonna handle moving of folders
+//            if let sourceItem = item.dragItem.localObject, fileIsCollection(file: sourceItem as AnyObject) {
+//                continue
+//            }
+//
+//            if fileIsFolder(atIndexPath: destinationPath) { // handle dropping onto a folder
+//                addDragItem(tableView: tableView, dragItem: item, toFolderAt: destinationPath)
+//                continue
+//            }
+//
+//            if item.sourceIndexPath != nil { // element within VLC
+//                moveItem(tableView: tableView, item: item, toIndexPath: destinationPath)
+//                continue
+//            }
+//            // Element dragging from another App
+//            let placeholder = UITableViewDropPlaceholder(insertionIndexPath: destinationPath, reuseIdentifier: VLCPlaylistTableViewCell.cellIdentifier(), rowHeight: VLCPlaylistTableViewCell.heightOfCell())
+//            let placeholderContext = coordinator.drop(item.dragItem, to: placeholder)
+//            createFileWith(itemProvider: itemProvider) {
+//                [weak self] file, error in
+//
+//                guard let strongSelf = self else { return }
+//
+//                if let file = file {
+//                    placeholderContext.commitInsertion() {
+//                        insertionIndexPath in
+//                        strongSelf.cateory.dragAndDropManagerInsertItem(manager: strongSelf, item: file, atIndexPath: insertionIndexPath)
+//                    }
+//                }
+//                if let error = error as? DropError {
+//                    strongSelf.handleError(error: error, itemProvider: item.dragItem.itemProvider)
+//                    placeholderContext.deletePlaceholder()
+//                }
+//            }
+//        }
+//    }
+//
+//    private func inFolder() -> Bool {
+//        return cateory.dragAndDropManagerCurrentSelection(manager: self) as? MLLabel != nil
+//    }
+//
+//    private func moveItem(tableView: UITableView, item: UITableViewDropItem, toIndexPath destinationPath: IndexPath) {
+//        if let mlFile = item.dragItem.localObject as? MLFile, !mlFile.labels.isEmpty && !inFolder() {
+//            tableView.performBatchUpdates({
+//                tableView.insertRows(at: [destinationPath], with: .automatic)
+//                cateory.dragAndDropManagerInsertItem(manager: self, item: mlFile, atIndexPath: destinationPath)
+//                cateory.dragAndDropManagerRemoveFileFromFolder(manager: self, file: mlFile)
+//            }, completion: nil)
+//        }
+//    }
+//
+//    private func addDragItem(tableView: UITableView, dragItem item: UITableViewDropItem, toFolderAt index: IndexPath) {
+//        if let sourcepath = item.sourceIndexPath { // local file that just needs to be moved
+//            tableView.performBatchUpdates({
+//                if let file = cateory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: sourcepath) as? MLFile {
+//                    tableView.deleteRows(at: [sourcepath], with: .automatic)
+//                    addFile(file: file, toFolderAt: index)
+//                    cateory.dragAndDropManagerDeleteItem(manager: self, atIndexPath: sourcepath)
+//                }
+//            }, completion: nil)
+//            return
+//        }
+//        // file from other app
+//        createFileWith(itemProvider: item.dragItem.itemProvider) {
+//            [weak self] file, error in
+//
+//            if let strongSelf = self, let file = file {
+//                strongSelf.addFile(file: file, toFolderAt: index)
+//            }
+//        }
+//    }
+//
+//    // MARK: - Collectionview
+//
+//    func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
+//        return canHandleDropSession(session: session)
+//    }
+//
+//    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
+//        return dragItems(forIndexPath: indexPath)
+//    }
+//
+//    func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
+//        return dragItems(forIndexPath: indexPath)
+//    }
+//
+//    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
+//        let operation = dropOperation(hasActiveDrag: collectionView.hasActiveDrag, firstSessionItem: session.items.first, withDestinationIndexPath: destinationIndexPath)
+//        return UICollectionViewDropProposal(operation: operation, intent: .insertIntoDestinationIndexPath)
+//    }
+//
+//    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
+//        let section = collectionView.numberOfSections - 1
+//        let row = collectionView.numberOfItems(inSection: section)
+//        let destinationPath = coordinator.destinationIndexPath ?? IndexPath(row: row, section: section)
+//
+//        for item in coordinator.items {
+//            if let sourceItem = item.dragItem.localObject, fileIsCollection(file: sourceItem as AnyObject) { // We're not handling moving of Collection
+//                continue
+//            }
+//            if fileIsFolder(atIndexPath: destinationPath) { // handle dropping onto a folder
+//                addDragItem(collectionView: collectionView, dragItem: item, toFolderAt: destinationPath)
+//                continue
+//            }
+//            if item.sourceIndexPath != nil { // element within VLC
+//                moveItem(collectionView: collectionView, item: item, toIndexPath: destinationPath)
+//                continue
+//            }
+//            // Element from another App
+//            let placeholder = UICollectionViewDropPlaceholder(insertionIndexPath: destinationPath, reuseIdentifier: VLCPlaylistCollectionViewCell.cellIdentifier())
+//            let placeholderContext = coordinator.drop(item.dragItem, to: placeholder)
+//            createFileWith(itemProvider: item.dragItem.itemProvider) {
+//                [weak self] file, error in
+//
+//                guard let strongSelf = self else { return }
+//
+//                if let file = file {
+//                    placeholderContext.commitInsertion() {
+//                        insertionIndexPath in
+//                        strongSelf.cateory.dragAndDropManagerInsertItem(manager: strongSelf, item: file, atIndexPath: insertionIndexPath)
+//                    }
+//                }
+//                if let error = error as? DropError {
+//                    strongSelf.handleError(error: error, itemProvider: item.dragItem.itemProvider)
+//                    placeholderContext.deletePlaceholder()
+//                }
+//            }
+//        }
+//    }
+//
+//    private func moveItem(collectionView: UICollectionView, item: UICollectionViewDropItem, toIndexPath destinationPath: IndexPath) {
+//        if let mlFile = item.dragItem.localObject as? MLFile, !mlFile.labels.isEmpty && !inFolder() {
+//            collectionView.performBatchUpdates({
+//                collectionView.insertItems(at: [destinationPath])
+//                cateory.dragAndDropManagerInsertItem(manager: self, item: mlFile, atIndexPath: destinationPath)
+//                cateory.dragAndDropManagerRemoveFileFromFolder(manager: self, file: mlFile)
+//            }, completion: nil)
+//        }
+//    }
+//
+//    private func addDragItem(collectionView: UICollectionView, dragItem item: UICollectionViewDropItem, toFolderAt index: IndexPath) {
+//        if let sourcepath = item.sourceIndexPath {
+//            // local file that just needs to be moved
+//            collectionView.performBatchUpdates({
+//                if let file = cateory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: sourcepath) as? MLFile {
+//                    collectionView.deleteItems(at: [sourcepath])
+//                    addFile(file: file, toFolderAt: index)
+//                    cateory.dragAndDropManagerDeleteItem(manager: self, atIndexPath: sourcepath)
+//                }
+//            }, completion: nil)
+//        } else {
+//            // file from other app
+//            createFileWith(itemProvider: item.dragItem.itemProvider) {
+//                [weak self] file, error in
+//                if let strongSelf = self, let file = file {
+//                    strongSelf.addFile(file: file, toFolderAt: index)
+//                }
+//            }
+//        }
+//    }
+//
+//    // MARK: - DropInteractionDelegate for EmptyView
+//
+//    func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
+//        return canHandleDropSession(session: session)
+//    }
+//
+//    func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
+//        return UIDropProposal(operation: .copy)
+//    }
+//
+//    func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
+//        for item in session.items {
+//            createFileWith(itemProvider: item.itemProvider) {
+//                [weak self] _, error in
+//                if let error = error as? DropError {
+//                    self?.handleError(error: error, itemProvider: item.itemProvider)
+//                }
+//                // no need to handle the file case since the libraryVC updates itself after getting a file
+//            }
+//        }
+//    }
+//
+//    // MARK: - Shared Methods
+//
+//    // Checks if the session has items conforming to typeidentifiers
+//    private func canHandleDropSession(session: UIDropSession) -> Bool {
+//        if session.localDragSession != nil {
+//            return true
+//        }
+//        return session.hasItemsConforming(toTypeIdentifiers: utiTypeIdentifiers)
+//    }
+//
+//    /// Returns a drop operation type
+//    ///
+//    /// - Parameters:
+//    ///   - hasActiveDrag: State if the drag started within the app
+//    ///   - item: UIDragItem from session
+//    /// - Returns: UIDropOperation
+//    private func dropOperation(hasActiveDrag: Bool, firstSessionItem item: AnyObject?, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UIDropOperation {
+//        let inAlbum = cateory.dragAndDropManagerCurrentSelection(manager: self) as? MLAlbum != nil
+//        let inShow = cateory.dragAndDropManagerCurrentSelection(manager: self) as? MLShow != nil
+//        // you can move files into a folder or copy from anothr app into a folder
+//        if fileIsFolder(atIndexPath: destinationIndexPath) {
+//            // no dragging entire shows and albums into folders
+//            if let dragItem = item, let mlFile = dragItem.localObject as? MLFile, mlFile.isAlbumTrack() || mlFile.isShowEpisode() {
+//                return .forbidden
+//            }
+//            return hasActiveDrag ? .move : .copy
+//        }
+//        // you can't reorder
+//        if inFolder() {
+//            return hasActiveDrag ? .forbidden : .copy
+//        }
+//        // you can't reorder in or drag into an Album or Show
+//        if inAlbum || inShow {
+//            return .cancel
+//        }
+//        // we're dragging a file out of a folder
+//        if let dragItem = item, let mlFile = dragItem.localObject as? MLFile, !mlFile.labels.isEmpty {
+//            return .copy
+//        }
+//        // no reorder from another app into the top layer
+//        return hasActiveDrag ? .forbidden : .copy
+//    }
+//
+//    /// show an Alert when dropping failed
+//    ///
+//    /// - Parameters:
+//    ///   - error: the type of error that happend
+//    ///   - itemProvider: the itemProvider to retrieve the suggestedName
+//    private func handleError(error: DropError, itemProvider: NSItemProvider) {
+//        let message: String
+//        let filename = itemProvider.suggestedName ?? NSLocalizedString("THIS_FILE", comment: "")
+//        switch error.kind {
+//        case .loadFileRepresentationFailed:
+//            message = String(format: NSLocalizedString("NOT_SUPPORTED_FILETYPE", comment: ""), filename)
+//        case .moveFileToDocuments:
+//            message = String(format: NSLocalizedString("FILE_EXISTS", comment: ""), filename)
+//        }
+//        let alert = UIAlertController(title: NSLocalizedString("ERROR", comment: ""), message: message, preferredStyle: .alert)
+//        alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
+//        UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
+//    }
+//
+//    private func fileIsFolder(atIndexPath indexPath: IndexPath?) -> Bool {
+//        if let indexPath = indexPath {
+//            let file = cateory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath)
+//            return file as? MLLabel != nil
+//        }
+//        return false
+//    }
+//
+//    private func fileIsCollection(file: Any?) -> Bool {
+//        let isFolder = file as? MLLabel != nil
+//        let isAlbum = file as? MLAlbum != nil
+//        let isShow = file as? MLShow != nil
+//        return isFolder || isAlbum || isShow
+//    }
+//
+//    private func fileIsCollection(atIndexPath indexPath: IndexPath?) -> Bool {
+//        if let indexPath = indexPath {
+//            if let file = cateory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath) {
+//                return fileIsCollection(file:file)
+//            }
+//        }
+//        return false
+//    }
+//
+//    // creating dragItems for the file at indexpath
+//    private func dragItems(forIndexPath indexPath: IndexPath) -> [UIDragItem] {
+//        if let file = cateory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: indexPath) {
+//            if fileIsCollection(atIndexPath: indexPath) {
+//                return dragItemsforCollection(file: file)
+//            }
+//            return dragItem(fromFile: file)
+//        }
+//        assert(false, "we can't generate a dragfile if the delegate can't return a file ")
+//        return []
+//    }
+//
+//    /// Iterates over the items of a collection to create dragitems.
+//    /// Since we're not storing collections as folders we have to provide single files
+//    ///
+//    /// - Parameter file: Can be of type MLAlbum, MLLabel or MLShow
+//    /// - Returns: An array of UIDragItems
+//    private func dragItemsforCollection(file: Any) -> [UIDragItem] {
+//        var dragItems = [UIDragItem]()
+//        var set = Set<AnyHashable>()
+//        if let folder = file as? MLLabel {
+//            set = folder.files
+//        } else if let album = file as? MLAlbum {
+//            for track in album.tracks {
+//                if let mlfile = (track as? MLAlbumTrack)?.files.first {
+//                    _ = set.insert(mlfile)
+//                }
+//            }
+//        } else if let show = file as? MLShow {
+//            for episode in show.episodes {
+//                if let mlfile = (episode as? MLShowEpisode)?.files {
+//                    set = set.union(mlfile)
+//                }
+//            }
+//        } else {
+//            assert(false, "can't get dragitems from a file that is not a collection")
+//        }
+//        for convertibleFile in set {
+//            if let mlfile = convertibleFile as? MLFile, let item = dragItem(fromFile: mlfile).first {
+//                dragItems.append(item)
+//            }
+//        }
+//        return dragItems
+//    }
+//
+//    //Provides an item for other applications
+//    private func dragItem(fromFile file: Any) -> [UIDragItem] {
+//        guard let file = mlFile(from: file as AnyObject), let path = file.url else {
+//            assert(false, "can't create a dragitem if there is no file or the file has no url")
+//            return []
+//        }
+//
+//        let data = try? Data(contentsOf: path, options: .mappedIfSafe)
+//        let itemProvider = NSItemProvider()
+//        itemProvider.suggestedName = path.lastPathComponent
+//        // maybe use UTTypeForFileURL
+//        if let identifiers = try? path.resourceValues(forKeys: [.typeIdentifierKey]), let identifier = identifiers.typeIdentifier {
+//            // here we can show progress
+//            itemProvider.registerDataRepresentation(forTypeIdentifier: identifier, visibility: .all) { completion -> Progress? in
+//                completion(data, nil)
+//                return nil
+//            }
+//            let dragitem = UIDragItem(itemProvider: itemProvider)
+//            dragitem.localObject = file
+//            return [dragitem]
+//        }
+//        assert(false, "we can't provide a typeidentifier")
+//        return []
+//    }
+//
+//    private func mlFile(from file: AnyObject) -> MLFile? {
+//        if let episode = file as? MLShowEpisode, let convertedfile = episode.files.first as? MLFile {
+//            return convertedfile
+//        }
+//
+//        if let track = file as? MLAlbumTrack, let convertedfile = track.files.first as? MLFile {
+//            return convertedfile
+//        }
+//
+//        if let convertedfile = file as? MLFile {
+//            return convertedfile
+//        }
+//        return nil
+//    }
+//
+//    private func addFile(file: MLFile, toFolderAt folderIndex: IndexPath) {
+//        let label = cateory.dragAndDropManagerRequestsFile(manager: self, atIndexPath: folderIndex) as! MLLabel
+//        DispatchQueue.main.async {
+//            _ = label.files.insert(file)
+//            file.labels = [label]
+//            file.folderTrackNumber = NSNumber(integerLiteral: label.files.count - 1)
+//        }
+//    }
+//
+//    /// try to create a file from the dropped item
+//    ///
+//    /// - Parameters:
+//    ///   - itemProvider: itemprovider which is used to load the files from
+//    ///   - completion: callback with the successfully created file or error if it failed
+//    private func createFileWith(itemProvider: NSItemProvider, completion: @escaping ((MLFile?, Error?) -> Void)) {
+//        itemProvider.loadFileRepresentation(forTypeIdentifier: kUTTypeData as String) {
+//            [weak self] (url, error) in
+//            guard let strongSelf = self else { return }
+//
+//            guard let url = url else {
+//                DispatchQueue.main.async {
+//                    completion(nil, DropError(kind: .loadFileRepresentationFailed))
+//                }
+//                return
+//            }
+//            // returns nil for local session but this should also not be called for a local session
+//            guard let destinationURL = strongSelf.moveFileToDocuments(fromURL: url) else {
+//                DispatchQueue.main.async {
+//                    completion(nil, DropError(kind: .moveFileToDocuments))
+//                }
+//                return
+//            }
+//            DispatchQueue.global(qos: .background).async {
+//                let sharedlib = MLMediaLibrary.sharedMediaLibrary() as? MLMediaLibrary
+//                sharedlib?.addFilePaths([destinationURL.path])
+//
+//                if let file = MLFile.file(for: destinationURL).first as? MLFile {
+//                    DispatchQueue.main.async {
+//                        // we dragged into a folder
+//                        if let selection = strongSelf.cateory.dragAndDropManagerCurrentSelection(manager: strongSelf) as? MLLabel {
+//                            file.labels = [selection]
+//                        }
+//                        completion(file, nil)
+//                    }
+//                }
+//            }
+//        }
+//    }
+//
+//    private func moveFileToDocuments(fromURL filepath: URL?) -> URL? {
+//        let searchPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
+//        let newDirectoryPath = searchPaths.first
+//        guard let directoryPath = newDirectoryPath, let url = filepath else {
+//            return nil
+//        }
+//        let destinationURL = URL(fileURLWithPath: "\(directoryPath)" + "/" + "\(url.lastPathComponent)")
+//        do {
+//            try FileManager.default.moveItem(at: url, to: destinationURL)
+//        } catch let error {
+//            print(error.localizedDescription)
+//            return nil
+//        }
+//        return destinationURL
+//    }
 }
 }

+ 30 - 30
Sources/VLCMediaSubcategory+VLCDragAndDrop.swift

@@ -12,33 +12,33 @@
 
 
 import Foundation
 import Foundation
 
 
-@available(iOS 11.0, *)
-extension VLCMediaSubcategoryModel: VLCDragAndDropManagerDelegate {
-
-    func dragAndDropManagerRequestsFile(manager: NSObject, atIndexPath indexPath: IndexPath) -> Any? {
-        return files[indexPath.row]
-    }
-
-    func dragAndDropManagerInsertItem(manager: NSObject, item: NSManagedObject, atIndexPath indexPath: IndexPath) {
-        if item as? MLLabel != nil && indexPath.row < files.count {
-            files.remove(at: indexPath.row)
-        }
-        // TODO: handle insertion
-        //files.insert(item, at: indexPath.row)
-    }
-
-    func dragAndDropManagerDeleteItem(manager: NSObject, atIndexPath indexPath: IndexPath) {
-        files.remove(at: indexPath.row)
-    }
-
-    func dragAndDropManagerCurrentSelection(manager: NSObject) -> AnyObject? {
-
-        //TODO: Handle playlists and Collections
-        fatalError()
-    }
-
-    func dragAndDropManagerRemoveFileFromFolder(manager: NSObject, file: NSManagedObject) {
-        //TODO: handle removing from playlists and Collections
-        fatalError()
-    }
-}
+//@available(iOS 11.0, *)
+//extension MediaLibraryBaseModel: VLCDragAndDropManagerDelegate {
+//
+//    func dragAndDropManagerRequestsFile(manager: NSObject, atIndexPath indexPath: IndexPath) -> Any? {
+//        return files[indexPath.row]
+//    }
+//
+//    func dragAndDropManagerInsertItem(manager: NSObject, item: NSManagedObject, atIndexPath indexPath: IndexPath) {
+//        if item as? MLLabel != nil && indexPath.row < files.count {
+//            files.remove(at: indexPath.row)
+//        }
+//        // TODO: handle insertion
+//        //files.insert(item, at: indexPath.row)
+//    }
+//
+//    func dragAndDropManagerDeleteItem(manager: NSObject, atIndexPath indexPath: IndexPath) {
+//        files.remove(at: indexPath.row)
+//    }
+//
+//    func dragAndDropManagerCurrentSelection(manager: NSObject) -> AnyObject? {
+//
+//        //TODO: Handle playlists and Collections
+//        fatalError()
+//    }
+//
+//    func dragAndDropManagerRemoveFileFromFolder(manager: NSObject, file: NSManagedObject) {
+//        //TODO: handle removing from playlists and Collections
+//        fatalError()
+//    }
+//}