I want to get information about how UITableView would be scrolled without actually schange scrollView.contentOffset. I cant use scrollEnabled property becouse its stop fireing scrollViewDidScroll event on table and I need it. Is there any way to do it?
The question is a little bit hard to understand but in any case scrolling on a UITableView will modify contentOffset. That's how the superclass, UIScrollView, works.
As for scrollViewDidScroll, it is only called automatically when the user scrolls the view, but you can override setContentOffset to trigger it manually when needed. Just to avoid a loop set a BOOL dragging = YES flag on scrollViewWillBeginDragging: and clear it on scrollViewDidEndDragging:willDecelerate:.
Related
I am doing paging effect in UICollectionView. My solution is shown below.
setContentOffset method will be called in scrollViewWillEndDragging and calculate the next or previous page contentOffset x value and set it with animation.
However, there is one issue which is that once the setContentOffset function has been called, if I touch the screen, then the scrollView will be stopped. Even if you release your finger, it won't continue, which means it stops at a wrong position.
Actually, I've tried to override the targetOffset in UICollectionViewFlowLayout but this issue still exists. Also, I tried to call touchesEnded but this is not even triggered at all. Furthermore, I tried isPagingEnabled and it won't cause this issue but my collectionView items are more complicated, which leads to a wrong targetContentOffset.
My current solution is set scrollView.isUserInteractionEnabled = false after setContentOffset and set it back to true when scrollViewDidEndScrollingAnimation called. This is okay but I am still wondering if there is any good way to do this?
I tried both Google Calendar and Outlook, they will reset you back to the position it should be.
I searched online and I cannot find any questions regarding this issue.
Could you help me? Thanks!
I tried a lot to figure it out and finally, I got something correct to share with you guys.
The solution is to set targetContentOffset in scrollviewWillEndDragging, then I can get what I want.
The truth behind this is that if you call setContentOffset, then scrollviewWillEndDragging won't be called in the second time endDragging (when you touch the screen after the first endDragging). However, if you simply set targetContentOffset = requiredContentOffset, then the second time endDragging will be called and at this time, the paging method will be called again to navigate UIScrollView to a correct position.
What I've learned from this is never call setContentOffset when you do the pagination effect. Some tutorials online for the pagination are totally wrong.
I'm trying to build something roughly similar to the drawer menu in Apple Maps on iOS.
In this Xcode project I'm attaching a UIPanGestureRecognizer on the VC's view, and as the panning happens, move vertically a UITableView with scrolling disabled.
The issue is every time after the pan ends, the didSelectRow method is called only after a second tap happens somewhere on the UITableView. Of course I'd like it to be called after the first tap.
The funny thing is that the bug does not happen if I enable the table's scrolling, and in the gesture recognizer's delegate have shouldRecognizeSimultaneouslyWith returning true.
Other funny thing is a very similar thing seems to happen in Apple Maps itself, if you try pulling the drawer up with the finger resting on a recent location entry from the list inside the drawer.
Thanks for your help!
I don’t understand well what are your saying.
But I think that the main problem is with “Chain Responder”. When you use PanGestureRecognizerand the UITableView property isScrollEnable = false in the Responder Chain the PanGestureRecognizer it is the first who is called, and the system wait to that fails or the event is not handled, then it is passed to the next in the Responder Chain, who is the UITableView. For that reason, it takes too long to get called the didSelectRow function
I suggest to you create a new UIView and insert in the ViewController in the storyboard o nib and put the UITableVIew outside of that UIView, then link the PanGestureRecognizer to that new UIView. In that way the Responder Chain don’t get in conflict with both, because the system can detect when the drag is in the new UIView and call only to the PanGestureRecognizer and when it is in the UITableView will call to the didSelectRow
Best Regards
Write if it does not resolve
I have a UICollectionView with custom cells- They have a UITextView that mostly covers the entire cell. This presents a problem when using didSelectItemAtIndexPath. The only way to trigger it is by tapping outside the UITextView. I want it to trigger wherever in the cell you tap, whether there is a text view or not. How can this be done?
didSelectItemAtIndexPath is called when none of the subView of collectionViewCell respond to that touch. As the textView respond to those touches, so it won't forward those touches to its superView, so collectionView won't get it.
override hitTest:withEvent method of your collectionViewCell or CollectionView subclass and always return self from them.so it explicitly makes collectionView as first responder.
I would suggest to use UIGestureRecognizer for each cell and when it taped to send it to UITextView or whatever , perhaps there maybe a better solutions , but I would use this 1 because of simplicity reasons.
Do you override touchesEnded: withEvent: ?
I had the same problem today and I found that I have some customised logic in touchesEnded in one of collectionview's container views, and I didn't call
[super touchesEnded: withEvent:]
when I'm done with my customised logic in touchesEnded.
After adding the super call, everything is fine.
Select UITextView, in that specific case UICollectionViewCell, and switch to attribute inspector. The uncheck User interaction enabled and it should work fine.
I ran into this problem when I had a scroll view taking up my entire collection view cell. While all the solutions above probably work fine, I came up with my own elegant work-around. I put a 'select' label under my scroll view. Since the label is not part of the scroll view, it passes the tap event on to the collection view. It also serves as a nice indicator that an action is required of the user.
Just do this
textview.isUserInteractionEnabled = false
I'm using CPPickerView in my app to accomplish a horizontal UIPickerView, and it works great, but with large data sources (dozens of items) it scrolls very slowly which makes navigation before (especially considering a normal UIPickerView can go very fast through them).
I don't mean performance-wise, by the way, I mean the view decelerates very quickly, making traversal difficult.
It's just a subclass of UIScrollView with pagingEnabled set to YES. What can I do?
I looked in the source, and it seems CPPickerView is using a scroll view. Scroll views have a decelerationRate property. Play with that and see which value makes for the best result.
Don't fill CPPickerView with all data.
For example fill with first 20 items and if it reaches to the end add another 20.
Creator of CPPickerView here - I've recently updated CPPickerView to add an allowSlowDeceleration property, which should do what you're looking for. Check out the latest code on Github, or Cocoapods version 1.2.0.
For the purposes of documentation, here's how the solution works. Like you mentioned CPPickerView just a scrollview with pagingEnabled set to YES, so the solution I found was to disable paging when the user scrolls with enough velocity.
UIScrollViewDelegate has an optional method scrollViewWillEndDragging:withVelocity:targetContentOffset:, which is called when the user's finger is lifted after swiping/scrolling on the scrollview, and it's still called even when paging is enabled. Based on that value you can tell if the user was trying to scroll quickly through items, or just move one or two items.
I played around with the CPPickerViews in the Demo project, and found that a velocity of about 2.9f seems to be about the normal "fast swipe" threshold. So if the velocity is greater than this threshold (which I defined as kCPPickerDecelerationThreshold in CPPickerView.m) and allowSlowDeceleration is set to YES, CPPickerView now sets pagingEnabled to NO before the deceleration starts. This allows the picker to "coast" and decelerate like a normal scrollview.
It then catches the end of the deceleration, OR the user touching to stop the scroll, by the call to the scrollViewDidEndDecelerating: delegate method. The current item is determined (based on the offset of the scrollview), and then if the scrollview's pagingEnabled property is set to NO a call to the private method scrollToIndex:animated: is made with animation set to YES. This scrolls the CPPickerView to the current item, which necessary as it's unlikely the coasting scroll ended right on a page boundary.
Finally, when the animated scroll completes, the scrollViewDidEndScrollingAnimation: delegate method is called, at which point pagingEnabled is set back to YES.
If you find that you're having trouble getting it to recognize a "fast" swipe, try playing with the kCPPickerDecelerationThreshold value. In hindsight that maybe should be a customizable property, so perhaps I'll roll that into the next update.
As mentioned above you can use the decelerationRate property, setting it to UIScrollViewDecelerationRateNormal might help.
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
If that still doesn't solve your problem you could also buffer your inputs into the scroll. See this:
Advanced scrollview techniques
I have a scrollview, which automatically advances every 3 seconds to the next "page" (page control also used). However, once the user touches the scrollview, I want to stop the auto-advancing.
I have subclassed the scrollview. I can detect touchesBegan in the subclass, but touchesMoved is not called, so I can't tell if the user has manually swiped the scrollview. I can't use scrollviewDidScroll, because that gets called when I set the contentOffset when the auto-advance timer fires.
So what are my other options for detecting touches? Why isn't touchesMoved called on the scrollview subclass?
Thank you for the suggestions. They helped me stumble upon this easy solution:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.scrollTimer invalidate];
}
You may want to look into the following delegate method:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
Per Apple:
The scroll view calls this method at the end of its implementations of the UIScrollView and setContentOffset:animated: and scrollRectToVisible:animated: methods, but only if animations are requested.
So that delegate method is only called when programatic scrolling occurs. You could set up your autoscroll call in that method, only calling it if some BOOL is false. Setting that BOOL to true in your touch event.
Or something completely different =]
It is a useful delegate method though.
~ Good luck
The scrollview probably nominates a subview to receive touch input — UIKit objects are very fussy about this sort of thing and generally can't even handle forwarded events.
What you probably want to do is to key-value observe either tracking or dragging (and it sounds like you want the latter). If the relevant property changes to true then you know the user has initiated scrolling.