I have searched and found multiple posts that the recommended way to determine when a user stopped scrolling, and when a UIScrollView stopped moving is the following:
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Perform desired outcome here.
NSLog(#"scrollViewDidEndDragging");
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// Perform desired outcome here.
NSLog(#"scrollViewDidEndDecelerating");
}
I have tried multiple ways, I can not get it to work.
Is there something obvious I am missing? Something with the delegate?
Make sure that you have all the following code inside your ViewController.
Under your ViewController.m add
#interface ScrollViewTestViewController ()<UIScrollViewDelegate>
Inside ViewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView1.delegate=self;
self.scrollView2.delegate=self;
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate (BOOL)decelerate {
// Perform desired outcome here.
NSLog(#"scrollViewDidEndDragging");
if(scrollView==self.scrollView1)
//Do Something
else if(scrollView==self.scrollView2)
// Do Another thing
}
Add a Screen Shot and a content size to simulate the scrolling behaviour.
Related
I'm trying to extend the current Picker component for ios in react native:
https://github.com/facebook/react-native/blob/master/React/Views/RCTPicker.m
https://github.com/facebook/react-native/blob/master/React/Views/RCTPickerManager.m
I would like to add the possibility to listen for startDragging and endDragging, startDecelerating and endDecelerating events.
I've found there that we can actually know if the user is currently dragging the Picker by checking its subviews dragging and decelerating flags. That's good but an event/delegate like pattern would suit the React native bridging model better.
Could you please suggest me how to listen to the Picker subviews dragging and decelerating values changes?
--- EDIT ---
Here is what I've tried so far:
I edited the Picker Manager file and added this:
// ...
#implementation RCTPickerManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
//return [RCTPicker new];
RCTPicker* picker = [RCTPicker new];
[self setDelegateForScrollViews:picker];
return picker;
}
// ...
RCT_EXPORT_VIEW_PROPERTY(onScrollChange, RCTBubblingEventBlock)
// ...
-(void)setDelegateForScrollViews:(UIView*)view
{
if([view isKindOfClass:[UIScrollView class]]){
UIScrollView* scroll_view = (UIScrollView*) view;
RCTLogInfo(#"DEBUG setting a delegate on SV");
scroll_view.delegate = self;
}
else {
for(UIView *sub_view in [view subviews]){
[self setDelegateForScrollViews:sub_view];
}
}
}
// UIScrollViewDelegate methods
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
RCTLogInfo(#"DEBUG scrollViewWillBeginDragging");
((RCTPicker*)self.view).onScrollChange(#{ #"state": [NSNumber numberWithBool:YES] });
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
RCTLogInfo(#"DEBUG scrollViewDidEndDragging");
((RCTPicker*)self.view).onScrollChange(#{ #"state": [NSNumber numberWithBool:NO] });
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
RCTLogInfo(#"DEBUG scrollViewDidEndDecelerating");
// TODO
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
RCTLogInfo(#"DEBUG scrollViewDidEndScrollingAnimation");
}
#end
But the UIScrollView Delegate methods are never raised when I scroll the picker :(...
Anyone any idea?
Try subclassing the picker view and implementing the scrollview delegate methods. Don't forget to call super. I don't see why you're wanting to do this though. These functions are hidden by design. What are you trying to achieve by this? Even if you have a legit reason, you should probably reconsider what you are trying to do. If you really need to know when a user is scrolling to select something, use a UITableView and you can get its scrollview delegate events.
I having recently implemented a solution that allows me to know when a scroll view has finished scrolling. This is so that when I scroll my tableview I only call a specific method once the tableview has stopped moving completely.
I followed the answer provided here :https://stackoverflow.com/a/8010066/2126233 but am including code below for ease of reading:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isScrolling = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
//[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; // pull to refresh
if(!decelerate) {
self.isScrolling = NO;
[self callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.isScrolling = NO;
[self callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn];
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
self.isScrolling = NO;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
self.isScrolling = NO;
}
This solution works perfectly. However I require this same logic in 4 different view controllers and I don't like having duplicate code.
I am unsure how I can implement the code above in one class and then use it in the other 4 classes.
I have had a few ideas:
I have a base class that all other view controllers inherit from. I was thinking I could subclass the base class and then the 4 view controls that require this code are subclassed from this new class. This new class provides the implementation for the scroll delegate methods. But how do I call the method and passing in the tableView and dataArray.
Subclassing UITableView and in the subclass implementing these 4 methods. This means I could pass in the tableView ok. But the data source is array is a little more problematic.
Does anyone have any suggestions on an elegant way this issue can be solved. Many thanks
I think you are on right track
I have a base class that all other view controllers inherit from. I was thinking I could subclass the base class and then the 4 view controls that require this code are subclassed from this new class. This new class provides the implementation for the scroll delegate methods.
Now your question is
how do I call the method and passing in the tableView and dataArray
For this you can take help of delegate methods
In your base class make a protocol
In yourBaseClass.h
#protocol MyDelegate <NSObject>
#required
-(void)callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn;
#end
#interface yourBaseClass:UITableViewController
#property (nonatomic, weak) id<MyDelegate> myDelegate;
#end
In yourBaseClass.m
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isScrolling = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
//[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; // pull to refresh
if(!decelerate) {
self.isScrolling = NO;
if(self.myDelgate && [self.myDelegate respondsToSelector(callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn)])
{
[self.myDelegate callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn];
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.isScrolling = NO;
if(self.myDelgate && [self.myDelegate respondsToSelector(callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn)])
{
[self.myDelegate callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn];
}
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
self.isScrolling = NO;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
self.isScrolling = NO;
}
Now you have to just implement the myDelegate method callMethodThatRequiresTableViewAndArrayOfDataToBePassedIn in your subclasses.
And yes also set the
self.myDelegate=self;
in your subclass viewDidLoad method.
With this approach you need not to pass any tableView or dataArray.
Hope this helps.
When there're two scrollViews in the UIViewController, how can I call UIScrollView's delegate method, which makes it only works on one of scrollViews and never affects the another?
Since your question is not that clear, if you only want to manage one of the UIScrollViewDelegate, don't set the scrollView2.delegate of the unwanted one.
If you want to different ways of manage of the delegates, here's what you can do, with the example of scrollViewDidScroll: method.
Usually, delegates method always give as a parameter the "delegated" object.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == yourScrollView1)
{
//Do something
}
else //if (scrollView == yourScrollView2)
{
//Do something
}
}
All delegate methods in iOS always pass the reference of the calling object, like:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
So use the passed reference for making decision:
if([self.scrollView1 isEqual:scrollView])
{
//Do something here only for scrollview1.
}
Say you have two scrollviews. scrollA and scrollB. Set tags for both of them
scrollA.tag=1;
scrollB.tag=2;
and set delegate to both, in the delegate calls handle it like
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if(scrollView.tag=1){
//do something
}else if(scrollView.tag=2){
}
}
If you don't want to use tags you can easily do like
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if(scrollView==scrollA){
//do something
}else if(scrollView==scrollB){
//do something
}
}
Cheers.
I have a UICollectionView that is used to simulate the new calendar in iOS 7. This collection view is inside a controller that has a selectedDate property. Whenever the selectedDate property is set the collection view should scroll to the date in the collection view.
The calendar controller's viewWillAppear also ensure the selected date is visible because this controller is cached and reused.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.calendarView scrollToDate:[self selectedDate] animated:NO];
}
The problem is that the VERY first time the calendar controller is shown the scroll does not work. The contentOffset of the collection view is not updated.
My current workaround is to schedule the scroll to occur on the next run loop using
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void)
{
// Scroll to the date.
});
It looks like when the UICollectionView is not in a window you cannot scroll. Scheduling the scroll to happen on the next run loop ensure that the view has been added to the window and can be properly scrolled.
Has anyone else experienced this issue and what their workarounds?
you can always force auto-layout to layout.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.view.layoutIfNeeded()
self.collectionView.scrollToItemAtIndexPath......
}
If you are using auto layout, the issue may be that the constraints haven't set the frames yet. Try calling the scrollToDate: method in viewDidLayoutSubviews (without dispatch_after).
#interface CustomViewController ()
#property (nonatomic) BOOL isFirstTimeViewDidLayoutSubviews; // variable name could be re-factored
#property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
#end
#implementation CustomViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.isFirstTimeViewDidLayoutSubviews = YES;
}
- (void)viewDidLayoutSubviews
{
// only after layoutSubviews executes for subviews, do constraints and frames agree (WWDC 2012 video "Best Practices for Mastering Auto Layout")
if (self.isFirstTimeViewDidLayoutSubviews) {
// execute geometry-related code...
// good place to set scroll view's content offset, if its subviews are added dynamically (in code)
self.isFirstTimeViewDidLayoutSubviews = NO;
}
bilobatum's answer is correct!
I'm writing this because I don't have reputation to comment... :/
I tried bilobatum's answer in my project, and it worked perfectly!
My code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
if (currentOffset.y != 999) {
[collectionView setContentOffset:currentOffset animated:NO];
}
}
currentOsset is a CGPoint initialized with x = 0 and y = 999 values (CGPoint currentOffset = {0,999};)
In the viewWillDisappear method I save the collectionView's contentOffset in the currentOffset.
This way if I navigate to the controller that has the collectionView and I navigated to there before, I will always have the last position.
The code that will work for you:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self.calendarView scrollToDate:[self selectedDate] animated:NO];
}
Thank you bilobatum for the answer!
Using -viewDidLayoutSubviews created an infinite loop that made the solution too complicated.
Instead I just added a small delay to let the constraints be created before the scrolling:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([self.scheduleDate isThisWeek]) [self.calendarLayout performSelector:#selector(scrollToCurrentTime) withObject:nil afterDelay:1];
}
I would like to know if a cell is being dragged (via the grip on the right side when a UITableView is in editing mode). I would like to change the background while it is in that state.
There is no callback unfortunately, but since the default behaviour is to adjust the 'alpha' value of the UITableViewCell you can check for that in '- (void)layoutSubview' of your UITableViewCell subclass.
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.alpha < 0.99) {
// we are most likely being dragged
} else {
// restore for not being dragged
}
}
Note that this is a bit of a hack. I noticed that this doesn't work when the table view's separatorStyle is set to 'UITableViewCellSeparatorStyleNone'.
I don't know if this is possible in the framework. But if it's not, you can try to override touchedMoved method in an UITableViewCell subclass?
You might then be able to also retrieve the state of your tableView (editing or not) in your UItableViewCell's custom subclass.
It is possible, yes!
In ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate>
{
IBOutlet UITableView * myTable;
IBOutlet UIScrollView * myScroll;
}
In ViewController.m
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myScroll = myTable;
myScroll.delegate = self;
}
#pragma Mark UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"%f",scrollView.contentOffset.y);
}
That is it.
It's possible yes! But you have to keep track on the states yourself in the view controller. Define a member
BOOL tableDrag = NO;
In the method
scrollViewWillBeginDragging
you set tableDrag = YES; And in the method
scrollViewDidEndDragging
you set tableDraw = NO;
Now in the scrollViewDidScroll delegate method you can set the background.
If you have several scrollers then tag the scroll view so that you know which one is dragging.
Hope it helps!