Multiple delegates per one object?

2019-02-02 09:36发布

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

5条回答
啃猪蹄的小仙女
2楼-- · 2019-02-02 10:04

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.

查看更多
成全新的幸福
3楼-- · 2019-02-02 10:05

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.

查看更多
Emotional °昔
4楼-- · 2019-02-02 10:13

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.

查看更多
一夜七次
5楼-- · 2019-02-02 10:19

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.

查看更多
Emotional °昔
6楼-- · 2019-02-02 10:24

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.

查看更多
登录 后发表回答