Multiple delegates per one object?
Asked Answered
I

5

17

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).

Irrationality answered 25/1, 2012 at 8:42 Comment(3)
Why would you do this? Why can't you just pass a message from your customScrollView to your VC that has the SV (with a second (custom)delegate)?Kirkham
Maybe that's what I need to do, so if I implemented scrollViewDidZoom in my SV subclass, how could I send my VC a message whenever it was triggered? @totumus maximusIrrationality
You will have to make a custom delegate(protocol) in ur custom view and make your VC delegate of it. At the moment your scrollview delegates gets called you also call one of the custom delegate methods in your custom delegate. This way the scrollview keeps responsibility of itself and your parent view gets to react on the particular scroll function. I'll put it into an answer for yah.Kirkham
K
7

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.

Kirkham answered 25/1, 2012 at 9:13 Comment(1)
@TotumusMaximus a ScrollView already has a property named "delegate", here you try to declare a property with the same name, which is BADHaire
A
16

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.

Alaynaalayne answered 20/2, 2013 at 9:47 Comment(4)
should it be declared like @property (assign) CCDelegateSplitter* delegates ?Squamulose
instead of a [NSMutableSet set]; use [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; as it will hold a weak reference to the delegate objects and will avoid the retain cycle for themPickmeup
Do not use this solution, the delegates cannot be deallocated due to the reference. Use what Nav suggested, NSNotification, or something else. Just not this...Temekatemerity
Or go for [NSPointerArray weakObjectsPointerArray]; that can hold weak pointers and doesnt draw you in the pitfalls keeping strong referencesJessejessee
K
7

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.

Kirkham answered 25/1, 2012 at 9:13 Comment(1)
@TotumusMaximus a ScrollView already has a property named "delegate", here you try to declare a property with the same name, which is BADHaire
M
3

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.

Mould answered 25/1, 2012 at 8:54 Comment(0)
P
2

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.

Procedure answered 25/1, 2012 at 8:47 Comment(0)
M
0

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.

Manualmanubrium answered 22/12, 2018 at 0:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.