问题
I'm implementing drag-n-drop on UICollectionView
using a new Apple's API introduced in iOS 11.
What I need is to get a frame of drag preview (see below, red rectangle)
I tried to get drag's location:
extension ViewController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView,
dropSessionDidUpdate session: UIDropSession,
withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
let location = session.location(in: collectionView)
}
but it represents my finger's location only. I need to get the entire frame or, at least, it's origin.
I also tried to store y
position of touch in my cell and then do something like dragLocation.y - touchLocationInCell.y
, but this approach is not accurate: looks like the preview gets some offset from the touch location.
回答1:
I have worked on similar functionality and can share some details. To be short, it seems that there is no option that allows position tracking of preview view.
From my perspective, I would not rely on the frame of the preview view. There are some cases when system could make preview view smaller or bigger than original view. For instance, it could happen when initial dragged view size is bigger than it's super view size. In this case system will make preview view smaller in order to bring more visibility about content that is being dragged. And this behavior is not predictable, system could apply such transformation based on some internal logic.
I think the way it works is because drag and drop engine allows to move items not only inside one specific app but between other apps. Since that, drag and drop engine should be a separate system process that handles all drag and drop operations including preview sizing and position.
During drag operation, system creates either original view snapshot or snapshot of the view that was returned from previewProvider of UIDragItem
.
Then it creates container view (instance of private UIPortalView
class with CAPortalLayer
underneath), installs snapshot into this container and displays container view. Since that moment preview view position is handled by system and there is no public API to access this view.
The only possible way to track drag position is to use location(in view: UIView)
function of UIDragDropSession
protocol.
I hope this helps.
回答2:
I am facing the same issue (trying to drag/drop a UICollectionView calendar event cell) and I've come to the conclusion (as did Alex D.) that the iOS 11 provided "preview" view cannot solve this problem. However, I did find a decent alternative solution, which is basically to create your own preview view from a UICollectionViewCell.snapshot
. Then you can drag that view around and you'll have access to its position, and once the user "drops" the view, create the new cell based on the custom preview view's position and remove the custom preview view.
You can see a working example of this here: https://github.com/zjfjack/JZCalendarWeekView/blob/master/JZCalendarWeekView/JZLongPressWeekView.swift#L385
回答3:
I was facing the same issue. I went through all the code Apple provides but could not find anything that would provide information on the preview frame. What I ended up doing is storing the initial position of the cell and then moving the origin by the displacement of the initial touch and the final touch. Something like this
func finalFrame(at location: CGPoint) -> CGRect {
var displacement: CGPoint = CGPoint(x: 0, y: 0)
displacement.x = location.x - initialTapLocation.x
displacement.y = location.y - initialTapLocation.y
var newFrame = initialCellFrame
newFrame.origin.x += displacement.x
newFrame.origin.y += displacement.y
return newFrame
}
Not the ideal solution, specifically for custom previews with scaling animation, but I hope this helps. The location you can get from session.location:in and I am storing the initial location in custom coordinator type, but you can use local object or context for that.
You can get the initial position and frame in the
collectionView:dragSessionWillBegin
OR
collectionView:itemsForBeginning:at
with
let location = session.location(in: collectionView) //store this
//if using the second method, it provides the indexpath, so no need to look for it
let indexPath = collectionView.indexPathForItem(at: location)
let cell = collectionView.cellForItem(at: indexPath)
cell.frame // store this also
来源:https://stackoverflow.com/questions/51020273/get-frame-of-drag-preview