SWTableViewCell - No animation, but delegate methods are being called - uitableview

I have spent countless hours trying to get SWTableViewCell working, and I've run out of ideas. I'm trying to integrate it into a UITableViewController that contains a custom UITableViewCell (subclassed). For some reason, I can't get any of the animation working. I thought at first that MMDrawerController might have been causing the problem, but after completely removing it's usage, the swipe still doesn't produce animation. So that's not the culprit.
I've gone so far as to try a different cell swipe implementation (TLSwipeForOptionsCell), but I get the same results of no action. I've also tried MCSwipeTableViewCell, which does work in showing the swipe action, but unfortunately presents it's own problems since it doesn't support auto-layout.
For the SWTableViewCell, I can confirm by stepping through the code that
The class receives the gesture and steps through the logic of the code appropriately.
The delegate methods are getting fired appropriately, so the control should have done what it was supposed to do.
However, nothing happens in my table view cell. No animation, no glitch/flicker, no sign that anything has changed.
I've also followed the guidance for using table view editing, which did remove the default "delete" option (desired to remove that anyway), but it still doesn't work.
As you can see in the documentation on GitHub, integrating this should be super simple, but it just doesn't work for me.
Target is iOS 7.1 SDK.
For SWTableViewCell (the swipe implementation I'd prefer to use), I've just noticed that the selection of the cell is lost almost immediately when starting to drag. If I touch and hold on the cell, it is selected. I move just slightly, and selection is lost. However, with MCSwipeTableViewCell the selection is not lost.
Any ideas?

At the expense of looking like an idiot, I'm going to log what the problem was just in case someone else makes the same mistake.
In addition to the symptoms above, I was also having a problem where touching on the cell so that the selection state was triggered would result in a highlight that covered all of my controls--the cell looked empty/blank. That was also resolved with the solution below.
In Interface Builder, I had set the backgroundView Outlet to contentView. Don't do that. Bad stuff happens.
Hope someone else ends up benefiting from this.

Related

Handling multiple UICollectionView' interactiveMovements - Crash: UIDragSnappingFeedbackGenerator is already being interacted with

(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.

Highlight UIButton when not selecting the button itself

I have a custom class here that consists of a UIButton inside of a UIView.
The goal of this class is to allow users to have a greater area (being the UIView) to select the button than just the buttons frame itself.
Now when a user taps on the view I want the buttons highlighted image to show... But the problem is, it does not.
I've read many possible solutions to this issue For Example:
Calling: [btnObject sendActionsForControlEvents:UIControlEventTouchUpInside]
This however did not change the buttons highlight.
I also tried just settings the button.highlighted = YES;
But that did not work either.
I have the images for different states properly setup (Normal and Highlighted), I'm sure of that.
I also have the gestureRecognizer working properly as the functionality is great except for the lack of highlight.
Does anybody know if I'm missing any thing special that needs to be done in order to pull off this seemingly very simple task? Surely it's been done many times.
Thank you
You were on the right track. -[UIButton setHighlighted:] is just a flag. What you need to do is call setNeedsDisplay on that button right after you change the highlighted property.
I solved my problem a little while ago and I'm not sure if Kevin Low's answer would've worked too, it very well might have.
But for some reason, a UITapGesture doesn't work well with highlighting buttons as a view transitions (That might be because I didn't call setNeedsDisplay). The gesture that ended up working was the UILongPressGesture with a 0.0 sec for minimum duration.

TableView didSelectRowAtIndexPath not called

So I have added a UIUITableView to a UIViewController. I can't use a UITableViewController for reasons I don't need to explain since it will be unnecessary information. Anyway, I have set the delegate, and the data source to this viewController. I've added the delegate and datasource protocols as well. The cells are populated correctly, so the datasource is working fine. I can also scroll so it all works fine.
However, I can't get the didSelectRowAtIndexPath to trigger. It SHOULD trigger, but doesn't. I've read and a lot of issues with this can be correlated to a UIGestureRecognizer, but I haven't implemented one. I also use the standard UITableView, so not a custom made one.
If I long press the cells (3-4 sec) then it gets triggered as it's supposed to. This suggests that there is some issue with another view or something absorbing the tap gesture, which I have no control over. How would I solve this?
No, it's not didDeselectRowAtIndexPath.
Yes all delegates and datasources are correct, since I can get the delegates/sources to trigger.
Yes, Single Selection is set on the TableView in the inspector.
Yes, everything has user interaction enabled.
If I just copy the code over to a UITableViewController it will work just fine, but right now that is not an option, I'm afraid. Anyone got any ideas on how to solve this? Most people who've had this issue has either had the issues in the list above, or added a UIGesture on top of the UITableView - I haven't.
I want to start by saying that I appreciate all the answers here provided, it gave me a lot of things to try out so I learned a lot - thanks! None of your suggestions worked, but simply because I'm a complete idiot. I said in the post that I did NOT implement a UIGestureRecognizerwhich I didn't...in that class, but in its super class. So I DID in fact implement it, but in a class that this ViewController was a subclass off. The only reason I didn't remember it was because I haven't looked at that super class for weeks.
Someone did suggest it in the comments that I should check for it, and I did and I was already certain I didn't implement one, so I quickly dismissed it. But now, after about 4 hours of debugging and recreating the project, adding things one by one, I eventually realized that the only thing that differed at this point was the Super Class, and the first piece of code I see when I open the file up was a GestureRecognizer...
So keep this in mind in the future everyone - I know I will. Thanks again for the help!
Sincerely,
The complete idiot.
Make sure you don't add any controller in that cell, which covers the entire cell and also the user interaction of that controller is enabled.
Due to that controller's user interaction enabled the tap action is taken by that controller and when you long press that cell, that cell will receive your tap.
Example :
UIView *_view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _cellWidth, _cellHeight)];
[_view setUserInteractionEnabled:YES];
[cell.contentView addSubview:_view];
In the above case that view got your tap instated of the cell.

How to make SWTableViewCell swipe more sticky

I've implemented the SWTableViewCell code in order to allow side swipes to reveal more UI, such as buttons.
Which is working fine, except that the UIScrollview it subclasses is really just too touchy, flicking back and forth like a manic game of ping pong.
I went to make changes to the class, but realised UIScrollView didn't seem to give me the ability to say change the way the scrolling animations work in the way I wanted.
Leaving me thinking that I either need to create my own version of the a Swipe cell, with a pan gesture and a overlay view, instead of a scrollview or find someone who has already solved this problem.
Please check it out sample: https://github.com/runmad/RMSwipeTableViewCell
May be it will helpful to you, Sir
:)
Not sure if this will give you the effect you desire, but you can make it less "jumpy" by altering the decelerationRate. Try:
someScrollView.decelerationRate = UIScrollViewDecelerationRateFast;
If that's the right idea, you can jump-to-definition in Xcode, and checkout the float value, then try your own.

UIView with UIButtons not showing up prior to click or rotate

I've been banging my head with this issue for the last two days. Googled a lot but wasn't able to find the answer yet, so I decided to request some help here. Let's see if I get any luck.
I'm coding a reusable control that consists of an UIView with a variable number of customized UIButtons. I implemented initWithFrame:, initWithCoder: and drawRect: where the buttons (which are built prior to drawing) are actually added to the view. Everything is done programmatically since the UIButton content should be supplied when using the control, so there's no XIB for this UIView.
This UIView, let's call it CustomizableBarButton is then used in an UIViewController, let's call it MyTestViewController with a view on it, let's call it customizableBarButtonView.
MyTestViewController's GUI was set on IB where an UIView was tied to customizableBarButtonView (the class was matching accordingly).
MyTestViewController's is a pretty standard class except for the viewWillAppear: that initializes the buttons and passes them to the CustomizableBarButton along with some other options.
The issue is that everything works perfectly...except for the first time!
I mean, when I run the app on the simulator (I haven't tried it on the iPhone yet but I strongly believe that it's not an hardware issue) the MyTestViewController shows the customizableBarButtonView background but not the buttons. Now when you click on the place where a button should be all the buttons suddenly appear!
I'm puzzled since the CustomizablebarButton drawRect: runs before the strange "click n'appear" effect and the buttons are actually added to the subview.
Another hint that my help: if you don't click on the buttons (so you still got no buttons yet) but rotate the device they will also appear as if by magic!
It is probably something very simple but I'm missing it and I'm going nuts...
Can someone lend a hand on this, please?
Thanks in advance!
You said you're adding the buttons in drawRect:. Don't do that. You need to add the buttons in your init methods.

Resources