I have a swiftUI app that implements the native drag and drop apis. Everything works well but I have discovered an edge case that I don't understand how to fix. Essentially the entire view of the app (the size of the device screen) is a drop target. When dragging an object to be dropped, if the user moves the item to the edge of the screen the app will crash throwing the error:
'NSInternalInconsistencyException', reason: UIDragPreviewTarget requires that the container view is in a window, but it is not
I assume this has to do with the drop target and trying to move the drag object out of the app's window, but I don't know what to do to handle this edge case.
Any help would be greatly appreciated
EDIT:
I commented out the .onDrop() modifier and the crash still persists. I can only guess that the generated UIDragPreviewTarget is tracking its location internally but when it is pulled out of the Frame of the app it throws the error
After digging around a bunch more and creating a new simplified project to demonstrate this wasn't a bug in the Drag and Drop implementation in SwiftUI, I think I found the issue.
In my case I have a view that holds the data I need sitting above the view I want to be able to drag that data into. While in the drag process, I wanted the top view to dismiss itself so as to no longer obstruct the lower view. I was flipping this switch in one of two ways.
when starting the drag process on the original piece of data, I started a timer that would give you 1.5 seconds to cancel the drop before it would hide the view.
when entering the lower view's drop target, it would close the top view
Removing both of these actions eliminated the crash.
My assumption at this point is that hiding the original source of the data is tripping up the dragItem's sense of "home" and when you try and drag it off screen it has no where to return back too.
Related
(See Update 3 for more information. I fixed the original problem, now there's an exception I've never seen before)
so I've been trying to implement my own version of IOS11' Drag&Drop feature. I've implemented a custom gesture recognizer and my own drag and drop session manager, called DragAndDropSession.
The situation with my app is the following: I have a "fullscreen" vertically scrolling collectionView, that holds horizontally scrolling collectionViews in each cell (row). A bit like what netflix has, for instance.
While dragging an item, I want these horizontal collectionViews to dynamically make space for the dragged item (just like with IOS11' Drag&Drop). I do that by adding an invisible cell to the row's collectionView and then I use collectionView.beginInteractiveMovement(..) on that cell. This way, it looks like a gap is moving around when I constantly update it to the current touch position. (I haven't found a better way).
Each time the touch moves to a different row, I stop the first interactive movement, remove the empty cell, and add it to the new collectionView, where I again begin an interactive movement. The difficulty here is the managing aspect so that everything gets "cleaned up" and then set up again correctly.
As this is hard to explain, I created a demo project showcasing everything (including the crash I'm going to tell you about in a second):
https://github.com/d3mueller/DragAndDropTest
(I hope it's working. Let me know, if not)
A few things:
It's a work in progress. dropping an item isn't implemented, so don't try that :D. Things will happen, that should not happen. I only implemented dragging
I'm using IGListKit (https://github.com/Instagram/IGListKit) to manage my collectionViews. It's probably not relevant to my problem, though. (I tried to comment the important bits)
In my code, you'll often see ...SectionController. This is the "manager" of a cell in the collectionView (IGListKit). For instance, the rows in the vertically scrolling collectionView each have a section controller that contains the data for this row, and the collectionView for this row etc.
In the following, I'll try to explain my problem. Sadly, I can't really post actual code snippets here, because you need to know the context to understand what happens there. That's why I added the demo project.
Okay, now to my problem: It's crashing. Sometimes. It gives me this error message:
'NSInternalInconsistencyException', reason: 'attempt to begin reordering on collection view while reordering is already in progress'
I set a breakpoint to catch this exception, so I know that it crashes in DragAndDropSession.swift: Line 194, which is this:
rowCollectionView.beginInteractiveMovementForItem(at: rowIndexPath)
In this line, I start a new interactive movement for the collectionView row that the finger is currently hovering on. I just don't know why it gets to this line, when it already has begun an interactive movement. A few lines above (line 171) I cancel the interactive movement.
There is a specific case/situation I'm not covering/catching in my code. I just can't find it. I've spent hours on this.
How to reproduce this bug (Look at Update 2. I found a way):
(I only managed to reproduce it on my iPad, not in the simulator)
Long press any item, move it a bit and then use another finger (while still holding the dragged item) to quickly scroll up and down and left and right. You have to be really fast and chaotic. Then it sometimes crashes.
The cause:
First, it's likely to be the updateDrag() method in DragAndDropSession.swift.
The error says I'm trying to begin reordering while another reordering is already in progress. Thus, in some cases I begin it twice or I don't cancel the movement before beginning another. I just don't know why.
UPDATE:
Sometimes, a different error is being trown (at the same line, after doing the exact same thing):
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<_UIDragSnappingFeedbackGenerator: 0x1c0134dc0: prepared=1> is already being interacted with'
I have never seen this before. The only thing I found about this, is this bug report: https://openradar.appspot.com/42139082
Any ideas?
UPDATE 2:
I found a way to reproduce it 100% of the time (I updated the demo project to add more rows and remove the empty row):
Steps to reproduce (also see gif):
long press an item to begin dragging it.
While still dragging the item, use the second finger to scroll at least 2 rows down. While you do this, you musn't move the first finger. It has to be perfectly still. Also, it has to be one swipe gesture to scroll.
Before the scrolling stops, scroll up (again using only one swipe gesture) until you reach the row directly below the one you started out with. Stop there (using the second finger or wait until it stops by itself)
Move the first finger (that's dragging the item) which will create a gap in that row.
Now, whenever you move the first finger onto the row you started out with, it crashes.
UPDATE 3:
I've fixed the original problem with the attempt to begin reordering on collection view while reordering is already in progress. It took me quite some time, the problem was occurring when a row leaves the screen (that's why you needed to scroll down quite a bit before it crashes). When the row then came back, a possibly different cell would be dequeued, thus I couldn't cancel the interactive movement from the original one. I fixed this by saving the collectionView itself (contained by the row). This and a couple small fixes did the trick. However, now I'm getting the following error (as introduced in Update 1) all the time it crashes.
2018-07-27 18:33:06.866322+0200 DragAndDropTest[62655:8122083] * Assertion failure in -[_UIDragSnappingFeedbackGenerator userInteractionStarted], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3698.54.4/_UIDragFeedbackGenerator.m:175
2018-07-27 18:33:06.867191+0200 DragAndDropTest[62655:8122083] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<_UIDragSnappingFeedbackGenerator: 0x1c012c9e0: prepared=1> is already being interacted with'
I haven't found a reliable way to reproduce this yet, but it's fairly easy to trigger. Just move the dragging item around a bit and scroll etc.
The big problem now is that I have absolutely no idea what this is. I've never seen this before, I don't know how to debug it. Has anyone ever seen this?
Again, I updated the demo project.
I'd be extremely grateful if someone could take a look at this. It's probably something trivial I'm not seeing. Let me know if you need any more information
Thank you!
I had the same assertion failure using iOS 11 Collection View Drag and Drop API in combination with interactive move (beginInteractiveMove etc).
Since this scenario is very specific and edge-casey, your mileage may vary.
I ventured into the rabbit hole and figured out the following:
_UIDragSnappingFeedbackGenerator is a private subclass of UIFeedbackGenerator which is responsible for haptic feedback
UIFeedbackGenerator Apple Documentation:
Releasing the Generator
If you no longer need a prepared generator, remove all references to the generator object and let the system deallocate it. This lets the Taptic Engine return to its idle state.
This could mean that for some reason the interactive move / reordering session is not completely finished although you may have called cancelInteractiveMovement(). The feedback generator remains in a prepared state (→ reason: '<_UIDragSnappingFeedbackGenerator: 0x1c012c9e0: prepared=1> from the assertion failure log) and will not let go of this state until it is deallocated by the system.
What fixed it for me was overriding cancelInteractiveMovement in my UICollectionView subclass and calling both super.cancelInteractiveMovement and super.endInteractiveMovement:
override func cancelInteractiveMovement() {
super.cancelInteractiveMovement()
super.endInteractiveMovement() // ← will not perform the standard "end" animation
// the moving cell was already reset by cancelInteractiveMovement
}
This seems to clean up the unfinished UIFeedbackGenerator and does not crash anymore (so far).
Related links
http://www.openradar.me/42139082 (closed rdar)
http://www.openradar.me/42154922 (duplicate of above, still open)
Although it's been quite a while since this was originally asked, there's still little information on the <_UIDragSnappingFeedbackGenerator: 0x1c012c9e0: prepared=1> is already being interacted with' error out there and we were experiencing it quite regularly in our app even after implementing the override cancelInteractiveMovement() call in the answer from #Daniel.
When I dropped breakpoints on the cancelInteractiveMovement it was very rarely called.
I've implemented the following on the drag delegate for the collection views and so far have not seen any of the UIDragSnappingFeedbackGenerator crashes yet.
public func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
collectionView.endInteractiveMovement()
}
Still TBD if there will be downstream consequences for this.
I have quite a few labels and buttons on my view controller and I have an image and I have set it to the back in the layers part on the left. How ever when I make it bigger it seems to go to the front and block the images. I have tried multiple codes from other people who have the same problem, but it still doesn't work. I am using Xcode 8, Swift, iOS.
In interface builder you can change the order of what is in front or behind by changing the order of the views as they appear in the Document Outline...
The higher up the list they appear, the further towards the back they are.
From what I can tell there are some challenges people are having in XCode6 in terms of wiring up outlet collections. I just wanted to see if i'm doing this right or what I need to do correctly! I reviewed here Can't hook up an outlet collection in Xcode 6 using storyboard as well as other areas, but I wanted to be more basic about this first.
I have 7 images on a screen. I'd like to iterate through all of them in a loop or something. I read an outlet collection would be a sensible way to do this. To create an outlet collection, I am doing this:
While splitting the screen (storyboard on left, assistant editor on right) I control+click on one of my 7 images, and drag it into the view controller code on the right. This will prompt me to create an outlet, or outlet collection (I select outlet collection).
Now that this is done, a circle appears in the left margin next to this variable indicating that the image is associated with that variable. If I hover over the circle, the first image lights up.
What you're supposed to do (can someone confirm this please??) is you just hover your mouse over the circle, and a + symbol appears, and all you do is hold down the left mouse button, and drag over to another image (for example image#2), and it should automatically add that to the collection. The idea is to simply drag to each image so they are all added.
Is that right in step #3 above? Is this the normal way you would add items to a collection? Drag from the + to the image within your controller? For me, when I do this, the line appears, but it doesn't seem to respond to connecting the line to anything. I just hover it over the images, and it doesn't highlight the image or select it or anything, and when I let go, nothing happens.
I'm still somewhat new to this - what am I doing wrong?
Thanks so much for your patience/help!
OK I had the same issue and I think I have just found a workaround
1) Do steps one and 2 with the first image
2) Now repeat exactly what you did using the second image - drag and drop just below the first outlet collection (this would simply create a second outlet collection use exactly the same name )
3) Now Xcode won't like it as you have two outletCollections with the same name. Now comment out the first one
4) Now you only have one outlet collection. And it would be wired up to both your images. confirm it by hovering your mouse pointer over the little 'circle' next to the IBOutlet to see your images being highlighted.
This seems to work for me. So hope this helps :-)
There is an error with Xcode and Interface Builder that causes problems with outlet collections. You are doing it right, Xcode is problematic.
A workaround is to:
Go to your DerivedData folder (Xcode preferences -> locations)
Clean build folder from the Product menu while holding option (alt) button
Quit Xcode
Delete the folder starting with your app's name in the derived data folder
Go to your xcodeproj file, right click it, show package contents.
Delete the xcuserdata folder (warning: don't touch the others!)
Open Xcode again.
When you delete user data, window positions etc. will be reset to defaults and breakpoints will be deleted. Your project should work smoother (after a few seconds of re-indexing) and outlet collections should work properly.
I have had some success by just changing my workflow as follows (no need to restart Xcode).
First, create the outlets in code in the controller class file.
Second, drag from the objects (in the outline view of IB) to the controller, or back (for actions).
This has always worked without fail. It is slightly more effort, but actually negligible. Definitely an Xcode bug.
I have a relatively simple dialog/popup style UIViewController that isn't behaving correctly. I have many others just like it (though each with a unique button layout) that work just fine. For some reason that I cannot figure out, this controller is only accepting touches within (approximately) the green shaded area.
Note that none of these colors are the actual colors, just placeholders. Same goes for the text.
Tapping in the "Search..." text field does nothing. Tapping on the "Cancel" button does nothing. No UI reaction whatsoever.
The tableview will scroll just fine (there are over 100 rows) if it's touched inside (approximately) the green region. Touching the tableview below the green results in no response. Same thing for the "Cats" button. It reacts when touched in approximately the top half, but nothing in the bottom half.
I've banged my head against the wall for the requisite "several hours" and am getting nowhere.
It seems that part of your view controller's view is overlapped with some "harmful" view/views. Maybe this view is created programmatically and is not visible in IB. You should start with checking your views hierarchy. Put breakpoint in your view controller's code. Somewhere, where you are sure all views (normal and harmful) are already created and type in debug area
po [self.view recursiveDescription]
you should receive something like
Now try to find harmful views. Temporarily delete some "normal" views could be a good idea.
I found my solution. I have a method that launches a dialog controller given the dialog to launch and the parent (up until this point, always a full-screen view controller). The parent gets darkened and the dialog gets launched on the center of the screen.
This one wasn't working because I was launching it from an existing dialog; I was telling the dialog launcher that the new dialog's parent was the current dialog, not the whole screen. This means that the new dialog (which is bigger than the first one) launches at the correct size, but only accepts input inside the area of the "parent" dialog, which is the smaller one underneath it.
Fix: Launch the new dialog using the current dialog's parent (which is full screen) and it works fine. The green zone on my image is the approximate size of the dialog that was being used as the parent, which is why input was only being accepted in that area.
My app, on very rare occasions, freezes when I select text in a UIWebView. Here's how it happens:
Tap and hold to select text
Text gets selected
Whole app freezes, unresponsive to touch, but I can still see operatings running
May freeze for around 10s. After that the UIMenuController appears. If I try to scroll while it is freezing, the scrolling happens after the app is responsive again
If the text is still selected, it freezes again if I scroll. Happens again and again
If I managed to scroll the selected text out of the view, scrolling the rest of the webview is fine, until I scroll the selected text back
If I get out of the UIViewController containing the UIWebView, I can confirm that they are both deallocated. Go back into a new UIViewController with UIWebView and it happens again.
This continues to happen with new instances of UIWebView, and even when I close and resume the app. Only stops if I force a restart of the app.
Does anyone have any clue how I can debug this? I've never seen this in simulator. Only happens very rarely on device.
I suspect you either have set up your view hierarchy improperly, or have modified it on a thread not the main thread. So tell us exactly where the UIWebView resides (super views), and take a good look at code that modifies sub view arrays.
For instance, you don't add the web view to a UINavigationController's sub views directly, but to its view controllers array etc.
All else fails create a demo project that also displays the problem and upload it to DropBox where we can run it ourselves.