CLIENT APP ERROR - Neither the view or container of the UITargetedPreview is currently in a window.

Hi everyone,


I am requesting help with a UIKit warning I am trying to fix:


CLIENT APP ERROR - Neither the view or container of the UITargetedPreview is currently in a window. This is in violation of UIDragInteraction API contract and can cause a severe visual glich.


I have a Drag Source of a UITableView which implements UITableViewDragDelegate. The table view is within a Split View Master View, the destination is within the Split View Controller. The drag and drop interaction works perfectly. The proble is the scary error from XCode!


The UIDragItem is generated by:


extension Store: NSItemProviderWriting {
    
    var dragItem: UIDragItem {
        let dragItem = UIDragItem(itemProvider: NSItemProvider(object: self))
        dragItem.localObject = self
        dragItem.previewProvider = {
            let image = UIImageView(image: self.imageFront)
            image.frame = CGRect(x: 0, y: 0, width: 40, height: 40) // magic number for store preview size
            return UIDragPreview(view: image)
        }
        return dragItem
    }
}


I have tried to break on all the UIDragDelegate methods but the warning appears as the preview is generated, which seems to be later in the process.


I will post my drag methods to see if I am missing anything.


Thanks in advance.


// MARK: - Table view drag delegate

extension StoresTableViewController: UITableViewDragDelegate {
    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        return [stores[indexPath.row].dragItem]
    }

    func tableView(_ tableView: UITableView, dragSessionWillBegin session: UIDragSession) {
        guard let draggedStore = session.items.first?.localObject as? Store else { return }
        if draggedStore.maxSimilarStores > session.items.count {
            tableView.performBatchUpdates({
                let index = stores.firstIndex(of: draggedStore)!
                stores.remove(at: index)
                stores.insert(draggedStore.copy() as! Store, at: index)
                tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .fade)
            }, completion: nil)
        }
    }

    func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
        guard let draggedStore = session.items.first?.localObject as? Store else { return [] }
        let tableStore = stores[indexPath.row]
        if draggedStore.name == tableStore.name && draggedStore.maxSimilarStores > session.items.count {
            return [tableStore.dragItem]
        } else {
            return []
        }
    }

    func tableView(_ tableView: UITableView, dragPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
        guard let cell = tableView.cellForRow(at: indexPath) as? StoreTableViewCell else { return nil }
        let parameters = UIDragPreviewParameters()
        parameters.visiblePath = UIBezierPath(rect: cell.storeImageView.frame)
        return parameters
    }
}

Simon


EDIT:


I have added a debug project to reproduce:


https://github.com/atapp/iTOLD_DEBUG


Thanks

Accepted Reply

My solution to this after 48 hours of fiddling was to abandon the

UITableViewDragDelegate
and implement a drag interaction from a custom
UITableViewCell
. I am not sure if this is the correct method but it has cleared the error.


The clue for me was in the error itself, complaining of the lack of view or container for the

UITargetedPreview
. From what I can see
UITargetedPreview
is not part of the
UITableViewDelegate
system, I'm assuming because the
UITableViewDelegate
system is purely designed for drawing and dropping inside the
UITableView
itself.


My use is different, I am trying to drag from a

UITableView
to anywhere.


The new

UITableView
removes all
UIDragInteractionDelegate
methods and adds the required custom cell initializers:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     let cell = tableView.dequeueReusableCell(withIdentifier: "StoreCell", for: indexPath) as! StoreTableViewCell 
     let dragInteraction = UIDragInteraction(delegate: cell) cell.addInteraction(dragInteraction) 
     cell.store = stores[indexPath.row] {...} 
     return cell 
}

The custom

UITableViewCell
is:
class StoreTableViewCell: UITableViewCell {  
     var store: Store?  
     override func awakeFromNib() {
           super.awakeFromNib() // Initialization code 
     }  
     {...} 
}  

extension StoreTableViewCell: UIDragInteractionDelegate {
    func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
        if let dragItem = store?.dragItem {
            return [dragItem]
        }
        return []
    }
    
    func dragInteraction(_ interaction: UIDragInteraction, previewForLifting item: UIDragItem, session: UIDragSession) -> UITargetedDragPreview? {
        if let store = store, let frontImage = store.imageFront {
            let imageView = UIImageView(image: frontImage)
            imageView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
            let target = UIPreviewTarget(container: storeImageView, center: session.location(in: storeImageView))
            let parameters = UIPreviewParameters()
            parameters.backgroundColor = .clear
            let preview = UITargetedDragPreview(view: imageView, parameters: parameters, target: target)
            return preview
        }
        return nil
    }
}

Note the

UIPreviewTarget
being the image in the custom
UITableViewCell
(although I expect this can be any view) and I set the preview centre from the
UIDragSession
centre.

Replies

My solution to this after 48 hours of fiddling was to abandon the

UITableViewDragDelegate
and implement a drag interaction from a custom
UITableViewCell
. I am not sure if this is the correct method but it has cleared the error.


The clue for me was in the error itself, complaining of the lack of view or container for the

UITargetedPreview
. From what I can see
UITargetedPreview
is not part of the
UITableViewDelegate
system, I'm assuming because the
UITableViewDelegate
system is purely designed for drawing and dropping inside the
UITableView
itself.


My use is different, I am trying to drag from a

UITableView
to anywhere.


The new

UITableView
removes all
UIDragInteractionDelegate
methods and adds the required custom cell initializers:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     let cell = tableView.dequeueReusableCell(withIdentifier: "StoreCell", for: indexPath) as! StoreTableViewCell 
     let dragInteraction = UIDragInteraction(delegate: cell) cell.addInteraction(dragInteraction) 
     cell.store = stores[indexPath.row] {...} 
     return cell 
}

The custom

UITableViewCell
is:
class StoreTableViewCell: UITableViewCell {  
     var store: Store?  
     override func awakeFromNib() {
           super.awakeFromNib() // Initialization code 
     }  
     {...} 
}  

extension StoreTableViewCell: UIDragInteractionDelegate {
    func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
        if let dragItem = store?.dragItem {
            return [dragItem]
        }
        return []
    }
    
    func dragInteraction(_ interaction: UIDragInteraction, previewForLifting item: UIDragItem, session: UIDragSession) -> UITargetedDragPreview? {
        if let store = store, let frontImage = store.imageFront {
            let imageView = UIImageView(image: frontImage)
            imageView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
            let target = UIPreviewTarget(container: storeImageView, center: session.location(in: storeImageView))
            let parameters = UIPreviewParameters()
            parameters.backgroundColor = .clear
            let preview = UITargetedDragPreview(view: imageView, parameters: parameters, target: target)
            return preview
        }
        return nil
    }
}

Note the

UIPreviewTarget
being the image in the custom
UITableViewCell
(although I expect this can be any view) and I set the preview centre from the
UIDragSession
centre.