Multiple delegates per one object? - iphone

I have a UIScrollView that I need to subclass and within the subclass I need to attach the UIScrollViewDelegate so I can implement the viewForZoomingInScrollView method.
Then I have a UIViewController where I need to instantiate an object of this UIScrollView subclass that I created, and I would also like to make the UIViewController a UIScrollViewDelegate for this object so I can implement scrollViewDidZoom in this UIViewController class.
How is it possible to make one object have two delegates? (I know I could easily just have one delegate and just implement both methods there, but for design purposes I'd like to do it the way that I'm mentioning).

Sometimes it makes sense to attach several delegates to a scroll view. In that case you can build a simple delegation splitter:
// Public interface
#interface CCDelegateSplitter : NSObject
- (void) addDelegate: (id) delegate;
- (void) addDelegates: (NSArray*) delegates;
#end
// Private interface
#interface CCDelegateSplitter ()
#property(strong) NSMutableSet *delegates;
#end
#implementation CCDelegateSplitter
- (id) init
{
self = [super init];
_delegates = [NSMutableSet set];
return self;
}
- (void) addDelegate: (id) delegate
{
[_delegates addObject:delegate];
}
- (void) addDelegates: (NSArray*) delegates
{
[_delegates addObjectsFromArray:delegates];
}
- (void) forwardInvocation: (NSInvocation*) invocation
{
for (id delegate in _delegates) {
[invocation invokeWithTarget:delegate];
}
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
NSMethodSignature *our = [super methodSignatureForSelector:selector];
NSMethodSignature *delegated = [(NSObject *)[_delegates anyObject] methodSignatureForSelector:selector];
return our ? our : delegated;
}
- (BOOL) respondsToSelector: (SEL) selector
{
return [[_delegates anyObject] respondsToSelector:selector];
}
#end
Then simply set an instance of this splitter as a delegate of the scroll view and attach any number of delegates to the splitter. All of them will receive the delegation events. Some caveats apply, for example all the delegates are assumed to be of the same type, otherwise you’ll have trouble with the naive respondsToSelector implementation. This is not a big problem, it’s easy to change the implementation to only send delegation events to those who support them.

You don't want an object with 2 delegates. You want to keep your customScrollView keep the responsibility of its own UIScrollViewDelegate functions.
To make your parentVC respond to the delegate methods of UIScrollView as well you will have to make a custom delegate inside your customScrollView.
At the moment a UIScrollViewDelegate function gets called you will also call one of your delegate functions from your custom delegate. This way your parentVC will respond at the moment you want it to.
It will look somewhat like this.
CustomScrollView.h
#protocol CustomDelegate <NSObject>
//custom delegate methods
-(void)myCustomDelegateMethod;
#end
#interface CustomScrollView : UIScrollView <UIScrollViewDelegate>
{
id<CustomDelegate> delegate
//the rest of the stuff
CustomScrollView.m
-(void) viewForZoomingInScrollView
{
[self.delegate myCustomDelegateMethod];
//rest of viewForZoomingInScrollView code
ParentVC.h
#interface CustomScrollView : UIViewController <CustomDelegate>
{
//stuff
ParentVC.m
-(void)makeCustomScrollView
{
CustomScrollView *csv = [[CustomScrollView alloc] init];
csv.delegate = self;
//other stuff
}
-(void)myCustomDelegateMethod
{
//respond to viewForZoomingInScrollView
}
I hope this fully covers your problem.
Good luck.

Short answer: you don't. Delegates are typically a weak one-to-one relationship:
#property (nonatomic, weak /*or assign*/) id<MyViewDelegate> delegate;
Sometimes you will see a "listener" design pattern, which is the one-to-many form of delegates:
- (void) addListener:(id<MyViewListener>)listener;
- (void) removeListener:(id<MyViewListener>)listener;
In your case, there doesn't appear to be a nice public override point in UIScrollView that allows subclasses to specify the viewForZoomingInScrollView. I would avoid making the UIScrollView its own delegate, if possible. You could make the UIViewController the UIScrollViewDelegate and have it provide the viewForZooming. Or you could make an intermediate view subclass which uses UIScrollView, provides the viewForZooming, and forwards the other delegate methods up.

I don't think you can have two UIScrollViewDelegate delegates directly connected to the same object.
What you can do is having the two delegates chain-connected. I.e., you connect one delegate to the other, then have the former forward messages to the latter when it cannot handle them itself directly.
In any case, I think I am missing a bit to fully suggest a solution, namely the reason why you do need a second delegate and cannot do always through one single delegate. In other words, what I think is that there might be alternative designs that would avoid needing two delegates.

Here's another potential problem with what you're trying to do...
Let's say you have two instances of a UIScrollView and one delegate object. In the delegate object, you override scrollViewDidScroll(UIScrollView *): method of the UIScrollViewDelegate protocol.
Inside the method, you want to access the value of the contentOffset property of both scroll views because, perhaps, you have two adjacent collections views, and you're trying to get the index path of the item at the center of the collection view to get the values of properties associated with those two items (think UIDatePicker).
In that case, how do you different between scroll views? The scrollView property only refers to one scroll view; but, even if it referred to both, how do you get the value of their respective contentOffset properties?
Now, you might say, "I can create an IBOutlet for both, and use their assigned references instead of the scrollView property in the delegate method, such as self.collectionViewFirst.contentOffset and self.collectionViewSecond.contentOffset, and ignore the scrollView property of the delegate method.
The problem is this: that property isn't stored. It's only available when the delegate method is called. Why? Because there's only one delegate object, and only one contentOffset property. By scrolling another scroll view, the value of the contentOffset property would change, and not reflect the content offset of any other scroll view except the last one scrolled.
It's bad practice to do what you're trying to do, even if the case (or a case like it) as I described doesn't apply to your situation. Remember: writing code is about sharing code. Incorrect code sends a message to others that diminishes your reputation.

Related

Message Passing with UIView as delegate

I have written a third party framework that displays a UIView on top of the existing view of the calling app, i.e., the app calls the framework/SDK and the SDK shows a UIView on top of the view that is currently on screen.
This is done by the use of delegates. The app sets the current view on screen as the delegate to the framework. The framework then uses this delegates and adds its own UIView as a subview using the code:
[[self delegate] addSubview:myVc.view];
where myVc is the ViewController in the framework.
Now I need to pass a method back to the calling app saying the view was shown on screen. Since, the delegate is a UIView, how do I pass a message to the calling class?
The reason why I have asked for a UIView delegate is because my UIView takes only a part of screen and I need the other part of the screen to show the remaining part of the app and be active. When I used ViewController as a delegate, it resulted in the other part of the screen being black instead of transparent.
So my question is how do I pass a message to the calling app, which calls the SDK and sets its view as a delegate. Thanks
You can post a notification that the caller listens for or you can accept a block as a parameter from the caller and execute that when the view is shown.
If I understood your question correctly, then you simply need to define protocol :
.h
#protocol YourFrameWorkViewDelegate <NSObject>
- (void)yourFrameWorkViewDidAppear;
#end
#interface YouFrameWorkView : UIView
#property (weak, nonatomic) id <YourFrameWorkViewDelegate> delegate;
#end
.m
#implementation YouFrameWorkView
- (void)didMoveToWindow
{
if ([self.delegate respondsToSelector:#selector(yourFrameWorkViewDidAppear)]) {
[self.delegate yourFrameWorkViewDidAppear];
}
}
#end
or you could call delegate from your VC like
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([self.view.delegate respondsToSelector:#selector(yourFrameWorkViewDidAppear)]) {
[self.view.delegate yourFrameWorkViewDidAppear];
}
Of course any app that wants to use you frame work needs to implement that protocol. This would probably be rootVCs view would have to be subclass and implement your protocol. This is standard practice. Hope it helped.
Follow up after your comment:
Masure that view implements your protocol
.h
#interface ViewThatUsesYourFrameWork : UIView <YourFrameWorkViewDelegate>
#end
.m
#implementation ViewThatUsesYourFrameWork
- (void)yourFrameWorkViewDidAppear
{
}
#end
and also confirm that delegate is indeed set.
before you call respondsToSelector
if(!self.delegate)NSLog(#"Delegate is not set")

ARC: Will setting self as a class member's delegate stop self from ever being freed?

Here's a pseudo class to demonstrate:
myView : UIView
- (void) init {
UIScrollView * scroller = [[UIScrollView alloc] init];
scroller.delegate = self;
[myView addSubview:scroller];
return self;
}
Under ARC, do I need to do anything else for memory to be freed correctly when all other references to myView have been removed? Will the reference between the two objects keep them sticking around forever without any intervention?
Does this change depending on whether or not scroller is a class property, or just a local variable declared in the function?
Just trying to find out why I've got multiple instances of some classes sticking around that shouldn't be there - semi related question, is there an easy way to find out why an object stays in memory (eg see all references to this object)?
iOS classes (like UIScrollView and UIWebView) already handle this correctly.
However, if you have your own delegate protocols and delegate properties, you need to make sure they are set to assign and not retain. To do this, wherever you declare a delegate (or whatever kind of protocol) you need to add the __unsafe_unretained tag thing:
#protocol FooBarDelegate {
//...
}
#interface Foo : Bar {
__unsafe_unretained id <FooBarDelegate> delegate;
}
#property (nonatomic, assign) id <FooBarDelegate> delegate;
Does this change depending on whether or not scroller is a class
property, or just a local variable declared in the function?
It doesn't. However keep in mind that adding something as a subview, the parent view will retain it automatically, regardless if you have a property or not.

how to trigger an action when gesture was initiated from a different object\class

my code UIViewController is instantiating an object\class, which is a UIView that has a frame which I draw a filled circle etc. I added a gesture to that second class frame that when a tap occur i want to close\release that second class and trigger some method written in the first object (in the UIViewController).
Now this second class recognize the tap ,but i'm not sure how to dealloc the class from within itself and tell the first class to do something (call a method)?
Hope this is clear, any idea how to go about that?
Thanks
The easiest way, in my opinion, is to create a delegate for the second class. For example, we'll just assume there is FirstClass and SecondClass, each their own file.
In FirstClass, when you set up its instance of SecondClass (instantiating the UIView), you would have something like the following:
SecondClass *class2 = [[SecondClass alloc] init....];
// set up the delegate, which basically creates a link between SecondClass and FirstClass;
class2.delegate = self;
In SecondClass.h, you would need to set up something like this:
#property (nonatomic, strong) id delegate;
and in SecondClass.m, you would have a synthesized value for the delegate as well as your gesture handling method:
// add the FirstClass header file;
#import "FirstClass.h"
// synthesize the value;
#synthesize delegate;
// later on into the implementation file;
- (void)gestureHandlingMethod:(UIGestureRecognizer *)gesture {
// do whatever you need for the gesture in SecondClass;
// send a message to FirstClass (now defined as delegate of SecondClass);
[self.delegate handleGesture:gesture forClass:self];
}
Finally, you need to add this gesture handling method to FirstClass so that it does what you need. So, in FirstClass.h, add -(void)handleGesture:(UIGestureRecognizer *)gesture forClass:(id)secondClass;. Then finish up by adding this method to FirstClass.m, like the following:
- (void)handleGesture:(UIGestureRecognizer *)gesture forClass:(id)secondClass {
// do what you want here from within the FirstClass;
[self doSomethingWithGestureIfYouWant:gesture];
// then deallocate SecondClass if that's what you want to do;
[secondClass dealloc]; // you may need to specify [(SecondClass *)secondClass dealloc];
}
That should do it. It's just a nice little way of linking different files together. Hopefully this helps you out.

Problems when overriding scrollViewDidScroll, but not all the other methods of UIScrollViewDelegate

Okay, so I did all the research for this issue but none of the existing solutions seem to address my problem, so here it is:
I have a custom class that extends UIScrollView (and contains a UIView)
I'd like to override the scrollViewDidScroll method from UIScrollViewDelegate (but not all the methods)
I have already tried implementing the code from this issue: How to subclass UIScrollView and make the delegate property private but for some reason, it doesn't do anything (the custom method that was overridden never gets called). I also know that you don't have to implement all the methods from UIScrollViewDelegate if you create a custom delegate class that implements the protocol (as per iPhone: Do I need to implement all methods for UIScrollViewDelegate (or any delegate)) - but when I do this:
MyScrollViewDelegate.h
#interface MyScrollViewDelegate: NSObject <UIScrollViewDelegate>
-(void)scrollViewDidScroll:(UIScrollView *)scrollView;
#end
MyScrollViewDelegate.m
#implementation MyScrollViewDelegate
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"Custom scrollViewDidScroll called.");
// -- some more custom code here --
// ...
}
#end
In the subclass which extends UIScrollView
// this scrollview is initiated by the NIB
- (void)awakeFromNib
{
...
[self setDelegate:[[MyScrollViewDelegate alloc] init]];
}
But while it compiles and runs, when I try to scroll the scrollable view, it crashes with EXC_BAD_ACCESS and a cryptic "(lldb)" message in the debug console.
So I'm bit at a loss here what to do.
I do have an implementation of How to subclass UIScrollView and make the delegate property private that works. My guess why your code didn't do anything: double check if you actually set the scroll view's contentSize to something bigger than your view's size. If it is smaller then there is no scrolling, just bouncing, and the scrollViewDidScroll is not called.
For you code, you actually have two issues in one line. First, the delegate property of UIScrollView is of type assign. That is if the delegate class is not retained somewhere else it will disappear in some time and you will get EXC_BAD_ACCESS. Second, by assigning [[MyScrollViewDelegate alloc] init] to the delegate and not releasing that object you create an orphan object which reference count is 1 and that will never be released. My guess is that the system recognizes the orphan object in run-time and cleans it up, after that you get your EXC_BAD_ACCESS when the delegate is sent a message.
If you prefer to use your version with separate delegate I would fix it as follows:
#interface MyScrollView: UIScrollView
{
id<NSObject, MyScrollViewDelegate> dlgt;
...
}
...
#end
#implementation MyScrollView
- (void)awakeFromNib
{
...
dlgt = [[MyScrollViewDelegate alloc] init];
[self setDelegate:dlgt];
}
-dealloc
{
[dlgt release];
[super dealloc];
}
#end
Still, don't forget to set the contentSize to something bigger than the view bounds. Otherwise there will be no scrolling and no delegate calls.

Objective C terminology: outlets & delegates

I'm having issues understanding the concept of outlets how the iPhone deals with events. Help! Delegates confuse me too. Would someone care to explain, please?
Outlets (in Interface Builder) are member variables in a class where objects in the designer are assigned when they are loaded at runtime. The IBOutlet macro (which is an empty #define) signals Interface Builder to recognise it as an outlet to show in the designer.
For example, if I drag out a button, then connect it to the aButton outlet (defined in my interface .h file), the loading of the NIB file at runtime will assign aButton the pointer to that UIButton instantiated by the NIB.
#interface MyViewController : UIViewController {
UIButton *aButton;
}
#property(nonatomic, retain) IBOutlet UIButton *aButton;
#end
Then in the implementation:
#implementation MyViewController
#synthesize aButton; // Generate -aButton and -setAButton: messages
-(void)viewDidAppear {
[aButton setText:#"Do Not Push. No, seriously!"];
}
#end
This eliminates the need to write code to instantiate and assign the GUI objects at runtime.
As for Delegates, they are event receiving objects used by another object (usually a generalised API class such as a table view). There's nothing inherently special about them. It's more of a design pattern. The delegate class may define several of the expected messages such as:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
...and the API object calls this message on the delegate when it wants to notify it of the event. For example:
-(void)update:(double)time {
if (completed) {
[delegate process:self didComplete:totalTimeTaken];
}
}
And the delegate defines the message:
-(void)process:(Process *)process didComplete:(double)totalTimeTaken {
NSString *log = [NSString stringWithFormat:#"Process completed in %2.2f seconds.", totalTimeTaken];
NSLog(log);
}
Such use might be:
Process *proc = [Process new];
[proc setDelegate:taskLogger];
[proc performTask:someTask];
// Output:
// "Process completed in 21.23 seconds."
A delegate is a object that another object can forward messages to. In other words, it's like when your mom told you to clean your room and you pawned it off on your little brother. Your little bro knows how to do the job (since you were too lazy to ever learn) and so he does it for you.