UITableView method - delegate, observer or notificagtion center - ios

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)

Related

Duplicate code in UIScrollViewDelegate methods

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.

Implementing UIScrollView Methods when Subclassing UITextView

I'm having problems controlling the scrolling within a UITextView that I'm using, so I've opted to create my own subclass.
I've got a very basic question about providing implementations for some of the UIScrollView superclass methods.
Here's my skeleton code for the UITextView subclass:
#interface PastedTextView : UITextView
#end
#implementation PastedTextView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"scrollRectToVisible");
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
NSLog(#"setContentOffset");
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"zoomToRect");
}
#end
When will those UIScrollView methods be called? Only from my own client code? Or will they be called by the framework?
Update:
The reason I've asked this is because I'm having the following problem: I'm programatically adding text to the UITextView (from the pasteboard). When I do so, if the textview has scrolled such that the top of the content is no longer in view, the text view scrolls back to the top after the new text has been appended.
I'm not explicitly triggering this scroll, so it's happening within the framework.
I haven't found anything in Apple's documentation that describes this behaviour. So, I've been trying to locate the source of the scrolling so that I can avoid it...
When this scroll happens, none of the above methods are called. Incidentally, neither is UITextViews scrollRangeToVisible method (I've tried adding that method to the subclass implementation). I can't figure out why that implicit scroll back to the top is happening and I want to prevent it...
If you override these UIScrollView methods as shown, any caller (be it your code, or the system's) will hit your implementation instead of the builtin UIScrollView one. If you want to take advantage of the system implementation, you can always call super.
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[super zoomToRect:rect animated:animated];
NSLog(#"zoomToRect");
}

Subclass a zooming UIScrollView without delegate methods

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;
}

How to determine if a UITableViewCell is being dragged?

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!

How to know when a child view of a UIViewController is removed?

I'm using the TapJoy SDK for a game application on iOS. The SDK has a way to display a view on top of the application: http://knowledge.tapjoy.com/integration-8-x/ios/pb/featured-app
I can give the function a UIVIewController argument, so I can manage the show/hide by myself.
I have created the following UIViewVontroller:
#interface MyViewController : UIViewController
- (void) viewDidLoad;
- (void) viewDidUnload;
- (void) viewWillLoad;
- (void) viewWillUnload;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;
#end
#implementation MyViewController
- (void) viewDidLoad
{
self.view = GRAPHIC_SYSTEM::GetGlView();
NSLog(#"viewDidLoad");
}
- (void) viewDidUnload
{
NSLog(#"viewDidUnload");
}
- (void) viewWillLoad
{
NSLog(#"viewWillLoad");
}
- (void) viewWillUnload
{
NSLog(#"viewWillUnload");
}
- (void)viewWillAppear: (bool)animated
{
NSLog(#"viewWillAppear");
}
- (void)viewDidAppear:(BOOL)animated
{
NSLog(#"viewDidAppear");
}
- (void)viewWillDisappear:(BOOL)animated
{
NSLog(#"viewWillDisappear");
}
- (void)viewDidDisappear:(BOOL)animated
{
NSLog(#"viewDidDisappear");
}
#end
When I'm notified by TapJoy that a feature app is available, I show it using my view controller:
[TapjoyConnect showFeaturedAppFullScreenAdWithViewController: [[MyViewController alloc] init]];
The TapJoy view is successfully displayed on top of my game.
There are 2 problems:
Only the viewDidLoad log is printed in the console. None of the other log messages are printed
I would like to know when the user has closed the TapJoy view, so I can add some processing at that time, but none of the other functions of the view controller are called.
I've seen here on SO that some users recommend to use the Notifications. Unfortunately, as I don't have access to the source code of the TapJoy SDK, I need to find another way.
Do you have any ideas?
Thanks in advance
Mike
Well I could fix the issue by creating a custom UIView, which I set as the UIViewController view.
Next, I have then overriden the willRemoveSubview function of this custom view.
And with the viewDidLoad function of the UIViewController, I know when the view is displayed, AND when the TapJoy view is removed, so I can remove my custom view too.

Resources