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.
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 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.
I have a UITableView on a ViewController which implement the delegate methods to handle the UITableView stuff - adding t o the table etc. All fine, but now I need another Object to handle scrollViewDidScroll from the table.
#imp ObjecteOne <UITableviewDelegate>
- ViewDidLoad{
[ObjectTwo setScrollView:_tableview];
}
#end
#imp ObjectTwo
- (void) setScrollView:(NSScrollView)view{
// Would you do:
[view addObserver??? #selector("scrollViewDidScroll") // something
or
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
// do stuff
}
#end
About all you can do is arrange for your ViewController to forward the scroll view delegate methods to the secondary object. One of the drawbacks of the delegate model is that there can only be one delegate at a time. (A better implementation by Apple might've been to have a tableDelegate separate the scrollView delegate)
I want to implement a UIScrollView subclass to present some custom formatted content. I just set a model object property of the scroll view and it handles all the required layout and rendering to display the content.
This works fine, but now I'd like to include zooming. According to the documentation, to support zooming you have to set a delegate and implement the viewForZoomingInScrollView: method. I guess I could set the delegate to the scroll view itself and implement that method in the subclass. But doing that I would lose the ability to have an external delegate (like an encapsulating UIViewController) that can be notified about scroll events.
Assuming the documentation is right and there is absolutely no (documented) way to implement zooming without a delegate, how could I still retain the possibility of having a regular, unrelated delegate?
Building upon H2CO3's suggestion of saving a hidden pointer to the real delegate and forwarding all incoming messages to it, I came up with the following solution.
Declare a private delegate variable to store a reference to the "real" delegate that is passed in to the setDelegate: method:
#interface BFWaveScrollView ()
#property (nonatomic, weak) id<UIScrollViewDelegate> ownDelegate;
#end
Set the delegate to self to be notified about scrolling events. Use super, so the original setDelegate: implementation is called, and not our modified one.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[super setDelegate:self];
}
return self;
}
Override setDelegate: to save a reference to the "real" delegate.
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
_ownDelegate = delegate;
}
When the UIScrollView tries to call a method of its delegate, it will first check to see if the delegate respondsToSelector:. We have to forward this to the real delegate if the selector is part of the UIScrollViewDelegate protocol (Don't forget to #import <objc/runtime.h>).
- (BOOL)selectorIsScrollViewDelegateMethod:(SEL)selector {
Protocol *protocol = objc_getProtocol("UIScrollViewDelegate");
struct objc_method_description description = protocol_getMethodDescription(
protocol, selector, NO, YES);
return (description.name != NULL);
}
- (BOOL)respondsToSelector:(SEL)selector {
if ([self selectorIsScrollViewDelegateMethod:selector]) {
return [_ownDelegate respondsToSelector:selector] ||
[super respondsToSelector:selector];
}
return [super respondsToSelector:selector];
}
Finally, forward all delegate methods to the real delegate that are not implemented in the subclass:
- (id)forwardingTargetForSelector:(SEL)selector {
if ([self selectorIsScrollViewDelegateMethod:selector]) {
return _ownDelegate;
}
return [super forwardingTargetForSelector:selector];
}
Don't forget to manually forward those delegate methods that are implemented by the subclass.
I'd abuse the fact that I'm being a subclass (on purpose :P). So you can hack it. Really bad, and I should feel bad for proposing this solution.
#interface MyHackishScrollView: UIScrollView {
id <UIScrollViewDelegate> ownDelegate;
}
#end
#implementation MyHackishScrollView
- (void)setDelegate:(id <UIScrollViewDelegate>)newDel
{
ownDelegate = newDel;
[super setDelegate:self];
}
- (UIView *)viewForScrollingInScrollView:(UIScrollView *)sv
{
return whateverYouWant;
}
// and then implement all the delegate methods
// something like this:
- (void)scrollViewDidScroll:(UIScrollView *)sv
{
[ownDelegate scrollViewDidScroll:self];
}
// etc.
#end
Maybe this is easier to read and understand a couple of weeks later :)
(sample code for intercepting locationManager:didUpdateLocations: in a subclass)
Other than that the same handling for setting self as delegate to the superclass and intercepting setDelegate in order to save the user's delegate to mDelegate.
EDIT:
-(BOOL)respondsToSelector:(SEL)selector {
if (sel_isEqual(selector, #selector(locationManager:didUpdateLocations:)))
return true;
return [mDelegate respondsToSelector:selector];
}
- (id)forwardingTargetForSelector:(SEL)selector {
if (sel_isEqual(selector, #selector(locationManager:didUpdateLocations:)))
return self;
return mDelegate;
}
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!