How to override the handlePan selector in UICollectionView - ios

In my tvOS app, I am trying to listen to changes in scrolling of my UICollectionView. After research, I found that the collection view natively receives a few gesture recognizers among them a UIPanGestureRecognizer with the selector handlePan:
<UIScrollViewPanGestureRecognizer: 0x101a4c1a0; state = Possible; delaysTouchesEnded = NO; view = <UICollectionView 0x1020c5d00>; target= <(action=handlePan:, target=<UICollectionView 0x1020c5d00>)>>
in the log, or in code:
myCollectionView.panGestureRecognizer
I was wondering if there's a way to add my controller as the target of the gesture recognizer, or maybe override the handlePan method.
I tried implementing the UIGestureRecognizerDelegate but it does not give me an access to the handlePan method.
Maybe I should just add a custom UIPanGestureRecognizer of my own on the collection view?

UICollectionView is a subclass of UIScrollView so you can detect scroll changes on collectionview by adding scrollview delegates.
Objective-C
// called on finger up if the user dragged. decelerate is true if it will continue moving afterwards
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
}
// called when scroll view grinds to a halt
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
}
Swift
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
}

Related

Why the animation of KeyboardDismissMode is weird

I add this code to enable scroll to dismiss keyboard
tableView.keyboardDismissMode = .onDrag
But the keyboard always lose the animation when it was about to exit,
like at this position
Reference Image
iOS version is 14.4
You can dismiss your keyboard when you scroll with simply using UIScrollViewDelegate method scrollViewDidScroll like below:
extension yourClass: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.searchbar.resignFirstResponder() // Your textField or searchBar etc.
}
}

Disable UIScrollView inertia (scrollViewDidEndDecelerating) Swift

Setup: I have a Scroll View that works great horizontally as well as a UISwipeGestureRecognizer that triggers a segue to another view when I swipe down.
Problem: If I am scrolling horizontally and I begin to swipe down (vertical scrolling disabled) WHILE the scrolling is decelerating, the swipe down action (segue) does not execute. It only works once the scrolling deceleration is complete.
Is there a way to disable the scroll deceleration (aka inertia) so that my swipe down gesture can be detected instantly? Perhaps there's a workaround to force UISwipeGestureRecognizer to be detected first?
Solutions in Swift are highly appreciated!
UIScrollView has a pinchGestureRecognizer and a panGestureRecognizer.If you have a UISwipeGestureRecognizer,the gesture will most likely be recognized as a UIPanGestureRecognizer.
You can add a dependence to solve the problem:
scrollView.panGestureRecognizer.requireGestureRecognizerToFail(swipeGesture)
Swift 3:
To disable decelerating scroll you can try:
func scrollViewWillEndDragging (scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
targetContentOffset.memory = scrollView.contentOffset
}
or you change this value:
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal
// UIScrollViewDecelerationRateFast is the other param
About your gestureRecognizer interferences you can call this method to exclude touching from:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
// insert here views to exclude from gesture
if touch.view!.isDescendantOfView(self.excludeThisView) {
return false
}
return true
}
did you look here?
Deactivate UIScrollView decelerating
the answer
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
[scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
converted to swift is
func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
scrollView.setContentOffset(scrollView.contentOffset, animated: true)
}

Add a Pan Gesture Recognizer to an UITableView in Swift

How can I add a Pan Gesture Recognizer to an UITableView, without blocking my scrolling feature of my TableView?
This is my code:
#IBOutlet var ScanPanGestureRecognizer: UIPanGestureRecognizer!
#IBAction func ScanPanGestureRecognizer(sender: UIPanGestureRecognizer)
{
print("TEST")
}
override func viewDidLoad()
{
ScanTableView.addGestureRecognizer(ScanPanGestureRecognizer)
}
So the code works and I get a lot of prints with "Test" but I'm not able to move (scroll) my TableView any more. I have read some other questions / answers but I couldn't find my issue. I thought that the extension "addGestureRecognizer" only add a gesture and not overwrite the TableView pan gesture... Thanks!
I think whatever you want to do with a pan gesture recognizer on a UITableView you can do it in UITableView's delegate method scrollViewDidScroll: in that method the scrollView.contentOffset will tell you how much the tableView has scrolled

UIPageViewController detecting pan gestures

Is there a way to determine the panning location of a UIPageViewController while sliding left/right? I have been trying to accomplish this but its not working. I have a UIPageViewController added as a subview and i can slide it horizontally left/right to switch between pages however i need to determine the x,y coordinates of where I am panning on the screen.
I figured out how to do this. Basically a UIPageViewController uses UIScrollViews as its subviews. I created a loop and set all the subviews that are UIScrollViews and assigned their delegates to my ViewController.
/**
* Set the UIScrollViews that are part of the UIPageViewController to delegate to this class,
* that way we can know when the user is panning left/right
*/
-(void)initializeScrollViewDelegates
{
UIScrollView *pageScrollView;
for (UIView* view in self.pageViewController.view.subviews){
if([view isKindOfClass:[UIScrollView class]])
{
pageScrollView = (UIScrollView *)view;
pageScrollView.delegate = self;
}
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(#"Im scrolling, yay!");
}
My personal preference is not to rely too much on the internal structure of the PageViewController because it can be changed later which will break your code, unbeknownst to you.
My solution is to use a pan gesture recogniser. Inside viewDidLoad, add the following:
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handler))
gestureRecognizer.delegate = yourDelegate
view.addGestureRecognizer(gestureRecognizer)
Inside your yourDelegate's definition, you should implement the following method to allow your gesture recogniser to process the touches
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Now, you should be able to access the X/Y location of the user's touches:
func handler(_ sender: UIPanGestureRecognizer) {
let totalTranslation = sender.translation(in: view)
//...
}

UICollectionView: how to detect when scrolling has stopped

I'm using a UICollectionView to scroll through a set of thumbnails quickly. Once scrolling ends, I'd like to display a larger hi-res version of the current thumbnail.
How can I detect when the user has completed scrolling? I do implement didEndDisplayingCell, but that only tells me when a particular cell has scrolled off; it doesn't tell me when the scroll motion actually completes.
NS_CLASS_AVAILABLE_IOS(6_0) #interface UICollectionView : UIScrollView
UICollectionView is a subclass of UIScrollView. So if you have set the delegate and implemented UIScrollViewDelegate, you should be able to detect this the same way as UIScrollView.
For eg:-
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
As per documentation, the above method should tell when the scroll view has ended decelerating the scrolling movement.
Just to cover your bases you should implement both these UIScrollViewDelegate methods.
In some cases there may not be a deceleration (and scrollViewDidEndDecelerating would not be called), for e.g., the page is fully scrolled in place. In those case do your update right there in scrollViewDidEndDragging.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
[self updateStuff];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self updateStuff];
}
An important fact to note here.
This method gets called on User initiated scrolls (i.e a Pan gesture):
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
or in Swift:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
On the other hand, this one gets called on all manually (programatically) initiated scrolls (like scrollRectToVisible or scrollToItemAtIndexPath):
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
or in Swift:
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView)
Swift 3 version of Abey M and D6mi 's answers:
When scroll is caused by user action
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if (!decelerate) {
//cause by user
print("SCROLL scrollViewDidEndDragging")
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//caused by user
print("SCROLL scrollViewDidEndDecelerating")
}
When scroll is caused by code action (programmatically): (like "scrollRectToVisible" or "scrollToItemAtIndexPath")
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
//caused by code
print("SCROLL scrollViewDidEndScrollingAnimation")
}
Notes:
Put these functions in your UIScrollViewDelegate or UICollectionViewDelegate delegate.
if you don't have a separate delegate, make your current class extend a UIScrollViewDelegate op top of your class file
.
open class MyClass: NSObject , UICollectionViewDelegate
and somewhere in your viewWillAppear make the class its own delegate
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ...
self.myScrollView.delegate = self
// ...
}
Swift 3 version:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// Your code here
}
if you want to use the visible indexpath:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self scrollingFinish];
}
- (void)scrollingFinish {
if([self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader]){
NSIndexPath *firstVisibleIndexPath = [[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader] firstObject];
[self.collectionView scrollToItemAtIndexPath:firstVisibleIndexPath atScrollPosition:UICollectionViewScrollPositionTop animated:YES];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
}
Get your collection view index and Dont forget to import UISCrollViewDelegate in your class
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let xPoint = scrollView.contentOffset.x + scrollView.frame.width / 2
let yPoint = scrollView.frame.height / 2
let center = CGPoint(x: xPoint, y: yPoint)
if let ip = self.collectionView.indexPathForItem(at: center) {
pageIndex = ip.row
}
print(">>>>>>>>>\(pageIndex)")
}

Resources