如何子类化UIScrollView并使委托属性为私有

时间:2021-09-15 21:03:54

Here is what I want to achieve:

以下是我想实现的目标:

I want to subclass an UIScrollView to have additional functionality. This subclass should be able to react on scrolling, so i have to set the delegate property to self to receive events like:

我想对UIScrollView进行子类化以获得其他功能。这个子类应该能够对滚动进行响应,所以我必须将委托属性设置为self,以接收如下事件:

- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { ... }

On the other hand, other classes should still be able to receive these events too, like they were using the base UIScrollView class.

另一方面,其他类也应该能够接收这些事件,就像它们使用基本UIScrollView类一样。

So I had different ideas how to solve that problem, but all of these are not entirely satisfying me :(

所以我有不同的想法来解决这个问题,但是所有这些都不能让我完全满意

My main approach is..using an own delegate property like this:

我的主要方法是. .使用自己的委托属性如下:

@interface MySubclass : UIScrollView<UIScrollViewDelegate>
@property (nonatomic, assign) id<UIScrollViewDelegate> myOwnDelegate;
@end

@implementation MySubclass
@synthesize myOwnDelegate;

- (id) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.delegate = self;
    }
    return self;
}

// Example event
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    // Do something custom here and after that pass the event to myDelegate
    ...
    [self.myOwnDelegate scrollViewDidEndDecelerating:(UIScrollView*)scrollView];
}
@end

In that way my subclass can do something special when the inherited scrollview ends scrolling, but still informs the external delegate of the event. That works so far. But as I want to make this subclass available to other developers, I want to restrict access to the base class delegate property, as it should only be used by the subclass. I think it's most likely that other devs intuitively use the delegate property of the base class, even if I comment the problem in the header file. If someone alters the delegate property the subclass won't do what it's supposed to do and I can't do anything to prevent that right now. And that's the point where i don't have a clue how to solve it.

这样,当继承的scrollview结束滚动时,子类可以做一些特殊的事情,但仍然通知事件的外部委托。到目前为止。但是,由于我想使这个子类对其他开发人员可用,所以我想限制对基类委托属性的访问,因为它应该只被子类使用。我认为其他开发人员很可能会直观地使用基类的委托属性,即使我在头文件中注释这个问题。如果有人修改了委托属性子类不会做它应该做的事情我现在也不能做任何事情来阻止它。这就是我不知道怎么解的点。

What I tried is trying to override the delegate property to make it readonly like this:

我试图重写委托属性使它像这样重新读取:

@interface MySubclass : UIScrollView<UIScrollViewDelegate>
...
@property (nonatomic, assign, readonly) id<UIScrollViewDelegate>delegate;
@end

@implementation MySubclass
@property (nonatomic, assign, readwrite) id<UIScrollViewDelegate>delegate;
@end

That will result in a warning

这将导致一个警告

"Attribute 'readonly' of property 'delegate' restricts attribute 'readwrite' of property inherited from 'UIScrollView'

Ok bad idea, as i'm obviously violating liskovs substitution principle here.

不好意思,显然我违反了liskovs替换原则。

Next try --> Trying to override the delegate setter like this:

下一个尝试——>试图覆盖委托setter如下:

...
- (void) setDelegate(id<UIScrollViewDelegate>)newDelegate {
    if (newDelegate != self) self.myOwnDelegate = newDelegate;
    else _delegate = newDelegate; // <--- This does not work!
}
...

As commented, this example does not compile as it seems that the _delegate ivar wasn't found?! So i looked up the header file of UIScrollView and found this:

如前所述,这个示例没有编译,因为似乎没有找到_delegate ivar ?我查了UIScrollView的头文件,发现

@package
    ...
    id           _delegate;
...

The @package directive restricts the access of the _delegate ivar to be accessible only by the framework itself. So when i want to set the _delegate ivar I HAVE TO use the synthesized setter. I can't see a way to override it in any way :( But i can't believe that there isn't a way around this, maybe i can't see the wood for the trees.

@package指令限制了只能由框架本身访问_delegate ivar。所以当我想设置_delegate ivar时,我必须使用合成setter。我看不出有什么方法可以超越它:(但我不相信没有办法绕过它,也许我看不见树木。)

I appreciate for any hint on solving this problem.

对于解决这个问题的任何提示,我都很感激。


Solution:

It works now with the solution of @rob mayoff . As i commented right below there was a problem with the scrollViewDidScroll: call. I finally did find out, what the problem is, even i don't understand why this is so :/

它现在与@rob mayoff的解决方案一起工作。正如我在下面评论的,scrollViewDidScroll有一个问题:调用。我终于找到了,问题是什么,即使我不明白为什么会这样:/。

Right in the moment when we set the super delegate:

就在我们设置超级委托的那一刻:

- (id) initWithFrame:(CGRect)frame {
    ...
    _myDelegate = [[[MyPrivateDelegate alloc] init] autorelease];
    [super setDelegate:_myDelegate]; <-- Callback is invoked here
}

there is a callback to _myDelegate. The debugger breaks at

有一个回调到_myDelegate。调试器将在

- (BOOL) respondsToSelector:(SEL)aSelector {
    return [self.userDelegate respondsToSelector:aSelector];
}

with the "scrollViewDidScroll:" selector as argument.

使用“scrollViewDidScroll:”选择器作为参数。

The funny thing at this time self.userDelegate isnt set yet and points to nil, so the return value is NO! That seems to cause that the the scrollViewDidScroll: methods won't get fired afterwards. It looks like a precheck if the method is implemented and if it fails this method won't get fired at all, even if we set our userDelegate property afterwards. I don't know why this is so, as the most other delegate methods don't have this precheck.

这个时候很有趣的事,self。userDelegate还没有设置,指向nil,所以返回值是NO!这似乎导致scrollViewDidScroll:方法在之后不会被触发。如果实现了这个方法,并且失败了,那么这个方法就不会被触发,即使我们之后设置了userDelegate属性。我不知道为什么是这样,因为大多数委托方法都没有这个precheck。

So my solution for this is, to invoke the [super setDelegate...] method in the PrivateDelegate setDelegate method, as this is the spot i'm pretty sure my userDelegate method is set.

我的解决方案是,调用[super setDelegate。方法在PrivateDelegate setDelegate方法中,因为这是我非常确定我的userDelegate方法被设置的地方。

So I'll end up with this implementation snippet:

因此,我将以这个实现片段结束:

MyScrollViewSubclass.m

MyScrollViewSubclass.m

- (void) setDelegate:(id<UIScrollViewDelegate>)delegate {
    self.internalDelegate.userDelegate = delegate;  
    super.delegate = self.internalDelegate;
}

- (id) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.internalDelegate = [[[MyScrollViewPrivateDelegate alloc] init] autorelease];
        // Don't set it here anymore
    }
    return self;
}

The rest of the code remains untouched. I'm still not really satisfied with this workaround, because it makes it necessary to call the setDelegate method at least once, but it works for my needs for the moment, although it feels very hacky :/

其余的代码仍然未被修改。我仍然不太满意这个变通方法,因为它使得至少需要调用setDelegate方法一次,但是它暂时满足了我的需要,尽管它感觉非常简单:/

If someone has ideas how to improve that, I'd appreciate that.

如果有人知道如何改进,我会很感激的。

Thanks @rob for your example!

谢谢@rob的例子!

5 个解决方案

#1


43  

There is a problem with making MySubclass its own delegate. Presumably you don't want to run custom code for all of the UIScrollViewDelegate methods, but you have to forward the messages to the user-provided delegate whether you have your own implementation or not. So you could try to implement all of the delegate methods, with most of them just forwarding like this:

生成MySubclass自己的委托有一个问题。假定您不想为所有UIScrollViewDelegate方法运行自定义代码,但是您必须将消息转发给用户提供的委托,无论您是否有自己的实现。你可以尝试实现所有的委托方法,其中大部分都是这样转发的:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self.myOwnDelegate scrollViewDidZoom:scrollView];
}

The problem here is that sometimes new versions of iOS add new delegate methods. For example, iOS 5.0 added scrollViewWillEndDragging:withVelocity:targetContentOffset:. So your scrollview subclass won't be future-proof.

这里的问题是,有时iOS的新版本会添加新的委托方法。例如,iOS 5.0增加了scrollviewwillenddrag:withVelocity:targetContentOffset:。因此,您的scrollview子类不会是永久的。

The best way to handle this is to create a separate, private object that just acts as your scrollview's delegate, and handles forwarding. This dedicated-delegate object can forward every message it receives to the user-provided delegate, because it only receives delegate messages.

处理此问题的最佳方法是创建一个单独的、私有的对象,该对象充当您的scrollview的委托,并处理转发。这个专用委托委托对象可以将它收到的每个消息转发给用户提供的委托,因为它只接收委托消息。

Here's what you do. In your header file, you only need to declare the interface for your scrollview subclass. You don't need to expose any new methods or properties, so it just looks like this:

这是你做什么。在头文件中,只需声明scrollview子类的接口。您不需要公开任何新的方法或属性,因此它看起来是这样的:

MyScrollView.h

@interface MyScrollView : UIScrollView
@end

All the real work is done in the .m file. First, we define the interface for the private delegate class. Its job is to call back into MyScrollView for some of the delegate methods, and to forward all messages to the user's delegate. So we only want to give it methods that are part of UIScrollViewDelegate. We don't want it to have extra methods for managing a reference to the user's delegate, so we'll just keep that reference as an instance variable:

所有真正的工作都在.m文件中完成。首先,我们为私有委托类定义接口。它的工作是为一些委托方法调用MyScrollView,并将所有消息转发给用户的委托。我们只给它UIScrollViewDelegate的一部分方法。我们不希望它有额外的方法来管理对用户委托的引用,所以我们将该引用作为一个实例变量:

MyScrollView.m

@interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate> {
@public
    id<UIScrollViewDelegate> _userDelegate;
}
@end

Next we'll implement MyScrollView. It needs to create an instance of MyScrollViewPrivateDelegate, which it needs to own. Since a UIScrollView doesn't own its delegate, we need an extra, strong reference to this object.

接下来我们将实现MyScrollView。它需要创建MyScrollViewPrivateDelegate的一个实例,它需要拥有这个实例。由于UIScrollView不拥有它的委托,所以我们需要对这个对象进行额外的强引用。

@implementation MyScrollView {
    MyScrollViewPrivateDelegate *_myDelegate;
}

- (void)initDelegate {
    _myDelegate = [[MyScrollViewPrivateDelegate alloc] init];
    [_myDelegate retain]; // remove if using ARC
    [super setDelegate:_myDelegate];
}

- (id)initWithFrame:(CGRect)frame {
    if (!(self = [super initWithFrame:frame]))
        return nil;
    [self initDelegate];
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (!(self = [super initWithCoder:aDecoder]))
        return nil;
    [self initDelegate];
    return self;
}

- (void)dealloc {
    // Omit this if using ARC
    [_myDelegate release];
    [super dealloc];
}

We need to override setDelegate: and delegate: to store and return a reference to the user's delegate:

我们需要重写setDelegate:和delegate:来存储并返回对用户委托的引用:

- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
    _myDelegate->_userDelegate = delegate;
    // Scroll view delegate caches whether the delegate responds to some of the delegate
    // methods, so we need to force it to re-evaluate if the delegate responds to them
    super.delegate = nil;
    super.delegate = (id)_myDelegate;
}

- (id<UIScrollViewDelegate>)delegate {
    return _myDelegate->_userDelegate;
}

We also need to define any extra methods that our private delegate might need to use:

我们还需要定义我们的私人委托可能需要使用的任何额外方法:

- (void)myScrollViewDidEndDecelerating {
    // do whatever you want here
}

@end

Now we can finally define the implementation of MyScrollViewPrivateDelegate. We need to explicitly define each method that should contain our private custom code. The method needs to execute our custom code, and forward the message to the user's delegate, if the user's delegate responds to the message:

现在我们终于可以定义MyScrollViewPrivateDelegate的实现了。我们需要显式地定义每个应该包含我们的私有自定义代码的方法。如果用户的委托响应消息,则该方法需要执行我们的自定义代码,并将消息转发给用户的委托:

@implementation MyScrollViewPrivateDelegate

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [(MyScrollView *)scrollView myScrollViewDidEndDecelerating];
    if ([_userDelegate respondsToSelector:_cmd]) {
        [_userDelegate scrollViewDidEndDecelerating:scrollView];
    }
}

And we need to handle all of the other UIScrollViewDelegate methods that we don't have custom code for, and all of those messages that will be added in future versions of iOS. We have to implement two methods to make that happen:

我们需要处理所有其他的UIScrollViewDelegate方法我们没有定制代码,所有这些消息都将在iOS的未来版本中添加。我们必须实施两种方法来实现这一点:

- (BOOL)respondsToSelector:(SEL)selector {
    return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    // This should only ever be called from `UIScrollView`, after it has verified
    // that `_userDelegate` responds to the selector by sending me
    // `respondsToSelector:`.  So I don't need to check again here.
    [invocation invokeWithTarget:_userDelegate];
}

@end

#2


2  

Thanks @robmayoff I encapsulated to be a more generic delegate interceptor: Having original MessageInterceptor class:

感谢@robmayoff我封装成一个更通用的委托拦截器:拥有原始的MessageInterceptor类:

MessageInterceptor.h

MessageInterceptor.h

@interface MessageInterceptor : NSObject

@property (nonatomic, assign) id receiver;
@property (nonatomic, assign) id middleMan;

@end

MessageInterceptor.m

MessageInterceptor.m

@implementation MessageInterceptor

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.middleMan respondsToSelector:aSelector]) { return    self.middleMan; }
    if ([self.receiver respondsToSelector:aSelector]) { return self.receiver; }
    return [super forwardingTargetForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ([self.middleMan respondsToSelector:aSelector]) { return YES; }
    if ([self.receiver respondsToSelector:aSelector]) { return YES; }
    return [super respondsToSelector:aSelector];
}

@end

It is used in your generic delegate class:

通用委托类中使用:

GenericDelegate.h

GenericDelegate.h

@interface GenericDelegate : NSObject

@property (nonatomic, strong) MessageInterceptor * delegate_interceptor;

- (id)initWithDelegate:(id)delegate;

@end

GenericDelegate.m

GenericDelegate.m

@implementation GenericDelegate

- (id)initWithDelegate:(id)delegate {
    self = [super init];
    if (self) {
        self.delegate_interceptor = [[MessageInterceptor alloc] init];
        [self.delegate_interceptor setMiddleMan:self];
        [self.delegate_interceptor setReceiver:delegate];
    }
    return self;
}

// delegate methods I wanna override:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 1. your custom code goes here
    NSLog(@"Intercepting scrollViewDidScroll: %f %f", scrollView.contentOffset.x, scrollView.contentOffset.y);

    // 2. forward to the delegate as usual
    if ([self.delegate_interceptor.receiver respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [self.delegate_interceptor.receiver scrollViewDidScroll:scrollView];
    }
 }
//other delegate functions you want to intercept
...

So I can intercept any delegate I need to on any UITableView, UICollectionView, UIScrollView... :

我可以拦截任何UITableView UICollectionView UIScrollView。:

@property (strong, nonatomic) GenericDelegate *genericDelegate;
@property (nonatomic, strong) UICollectionView* collectionView;

//intercepting delegate in order to add separator line functionality on this scrollView
self.genericDelegate = [[GenericDelegate alloc]initWithDelegate:self];
self.collectionView.delegate = (id)self.genericDelegate.delegate_interceptor;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    NSLog(@"Original scrollViewDidScroll: %f %f", scrollView.contentOffset.x, scrollView.contentOffset.y);
}

In this case UICollectionViewDelegate scrollViewDidScroll: function will be executed on our GenericDelegate (with any code we want to add) and on in the implementation of our own class

在这种情况下,UICollectionViewDelegate scrollViewDidScroll:函数将在我们的GenericDelegate(带有我们想添加的任何代码)和我们自己类的实现中执行

Thats my 5 cents, thanks to @robmayoff and @jhabbott previous answer

这是我的5分,感谢@robmayoff和@jhabbott之前的回答

#3


1  

Another option is to subclass and use the power of abstract functions. For instance, you create

另一种选择是子类化并使用抽象函数的功能。例如,你创造

@interface EnhancedTableViewController : UITableViewController <UITableViewDelegate>

There you override some delegate method and define your abstract function

这里你重写了一些委托方法并定义了你的抽象函数

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // code before interception
    [self bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:indexPath];
    // code after interception
}

- (void)bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {};

Now all you need to do is to subclass your EnhancedTableViewController and use your abstract functions instead of delegate ones. Like this:

现在你需要做的就是子类化你的强化dtableviewcontroller使用你的抽象函数而不是委托函数。是这样的:

@interface MyTableViewController : EnhancedTableViewController ...

- (void)bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //overriden implementation with pre/post actions
};

Let me know if there is anything wrong here.

如果有什么问题,请告诉我。

#4


0  

Don't subclass it, encapsulate it instead ;)

不要子类化它,而是封装它;)

Make a new UIView subclass - your header file would look like :

创建一个新的UIView子类-你的头文件看起来像:

@interface MySubclass : UIView 
@end

And just have a UIScrollView as a subview - your .m file would look like :

把UIScrollView作为子视图——你的。m文件应该是这样的:

@interface MySubclass () <UIScrollViewDelegate>

@property (nonatomic, strong, readonly) UIScrollView *scrollView;

@end


@implementation MySubClass

@synthesize scrollView = _scrollView;

- (UIScrollView *)scrollView {
    if (nil == _scrollView) {
        _scrollView = [UIScrollView alloc] initWithFrame:self.bounds];
        _scrollView.delegate = self;
        [self addSubView:_scrollView];
    }
    return _scrollView;
}

...
your code here
...

@end

Inside your subclass, just always use self.scrollView and it will create the scroll view the first time you ask for it.

在你的子类中,总是使用self。它会在你第一次请求时创建滚动视图。

This has the benefit of hiding the scroll view completely from anyone using your MySubClass - if you needed to change how it worked behind the scenes (i.e. change from a scroll view to a web view) it would be very easy to do :)

这样做的好处是,可以将滚动视图完全隐藏起来,不让任何使用MySubClass的人看到——如果您需要更改它在幕后的工作方式(即从滚动视图更改为web视图),那么很容易做到:

It also means that no-one can alter how you want the scroll view to behave :)

这也意味着没有人可以改变你想要的滚动视图的行为:)

PS I've assumed ARC - change strong to retain and add dealloc if necessary :)

PS我假设电弧变化强烈以保留和增加dealloc如果必要:)


EDIT

编辑

If you want your class to behave exactly as a UIScrollView then you could try this adding this method (taken from these docs and untested!) :

如果您希望您的类的行为与UIScrollView完全一样,那么您可以尝试添加这个方法(从这些文档中提取,未经测试!)

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([self.scrollView respondsToSelector:anInvocation.selector])
        [anInvocation invokeWithTarget:self.scrollView];
    else
        [super forwardInvocation:anInvocation];
}

#5


0  

For those of you looking to do this sort of thing with UIControl classes, see my STAControls project (specifically Base classes like this one). I have implemented delegate forwarding for the project using Rob Mayoff's answer.

对于那些希望使用UIControl类做这类事情的人,请参阅我的STAControls项目(特别是像这样的基类)。我已经使用Rob Mayoff的答案实现了项目的委托转发。

#1


43  

There is a problem with making MySubclass its own delegate. Presumably you don't want to run custom code for all of the UIScrollViewDelegate methods, but you have to forward the messages to the user-provided delegate whether you have your own implementation or not. So you could try to implement all of the delegate methods, with most of them just forwarding like this:

生成MySubclass自己的委托有一个问题。假定您不想为所有UIScrollViewDelegate方法运行自定义代码,但是您必须将消息转发给用户提供的委托,无论您是否有自己的实现。你可以尝试实现所有的委托方法,其中大部分都是这样转发的:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self.myOwnDelegate scrollViewDidZoom:scrollView];
}

The problem here is that sometimes new versions of iOS add new delegate methods. For example, iOS 5.0 added scrollViewWillEndDragging:withVelocity:targetContentOffset:. So your scrollview subclass won't be future-proof.

这里的问题是,有时iOS的新版本会添加新的委托方法。例如,iOS 5.0增加了scrollviewwillenddrag:withVelocity:targetContentOffset:。因此,您的scrollview子类不会是永久的。

The best way to handle this is to create a separate, private object that just acts as your scrollview's delegate, and handles forwarding. This dedicated-delegate object can forward every message it receives to the user-provided delegate, because it only receives delegate messages.

处理此问题的最佳方法是创建一个单独的、私有的对象,该对象充当您的scrollview的委托,并处理转发。这个专用委托委托对象可以将它收到的每个消息转发给用户提供的委托,因为它只接收委托消息。

Here's what you do. In your header file, you only need to declare the interface for your scrollview subclass. You don't need to expose any new methods or properties, so it just looks like this:

这是你做什么。在头文件中,只需声明scrollview子类的接口。您不需要公开任何新的方法或属性,因此它看起来是这样的:

MyScrollView.h

@interface MyScrollView : UIScrollView
@end

All the real work is done in the .m file. First, we define the interface for the private delegate class. Its job is to call back into MyScrollView for some of the delegate methods, and to forward all messages to the user's delegate. So we only want to give it methods that are part of UIScrollViewDelegate. We don't want it to have extra methods for managing a reference to the user's delegate, so we'll just keep that reference as an instance variable:

所有真正的工作都在.m文件中完成。首先,我们为私有委托类定义接口。它的工作是为一些委托方法调用MyScrollView,并将所有消息转发给用户的委托。我们只给它UIScrollViewDelegate的一部分方法。我们不希望它有额外的方法来管理对用户委托的引用,所以我们将该引用作为一个实例变量:

MyScrollView.m

@interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate> {
@public
    id<UIScrollViewDelegate> _userDelegate;
}
@end

Next we'll implement MyScrollView. It needs to create an instance of MyScrollViewPrivateDelegate, which it needs to own. Since a UIScrollView doesn't own its delegate, we need an extra, strong reference to this object.

接下来我们将实现MyScrollView。它需要创建MyScrollViewPrivateDelegate的一个实例,它需要拥有这个实例。由于UIScrollView不拥有它的委托,所以我们需要对这个对象进行额外的强引用。

@implementation MyScrollView {
    MyScrollViewPrivateDelegate *_myDelegate;
}

- (void)initDelegate {
    _myDelegate = [[MyScrollViewPrivateDelegate alloc] init];
    [_myDelegate retain]; // remove if using ARC
    [super setDelegate:_myDelegate];
}

- (id)initWithFrame:(CGRect)frame {
    if (!(self = [super initWithFrame:frame]))
        return nil;
    [self initDelegate];
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (!(self = [super initWithCoder:aDecoder]))
        return nil;
    [self initDelegate];
    return self;
}

- (void)dealloc {
    // Omit this if using ARC
    [_myDelegate release];
    [super dealloc];
}

We need to override setDelegate: and delegate: to store and return a reference to the user's delegate:

我们需要重写setDelegate:和delegate:来存储并返回对用户委托的引用:

- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
    _myDelegate->_userDelegate = delegate;
    // Scroll view delegate caches whether the delegate responds to some of the delegate
    // methods, so we need to force it to re-evaluate if the delegate responds to them
    super.delegate = nil;
    super.delegate = (id)_myDelegate;
}

- (id<UIScrollViewDelegate>)delegate {
    return _myDelegate->_userDelegate;
}

We also need to define any extra methods that our private delegate might need to use:

我们还需要定义我们的私人委托可能需要使用的任何额外方法:

- (void)myScrollViewDidEndDecelerating {
    // do whatever you want here
}

@end

Now we can finally define the implementation of MyScrollViewPrivateDelegate. We need to explicitly define each method that should contain our private custom code. The method needs to execute our custom code, and forward the message to the user's delegate, if the user's delegate responds to the message:

现在我们终于可以定义MyScrollViewPrivateDelegate的实现了。我们需要显式地定义每个应该包含我们的私有自定义代码的方法。如果用户的委托响应消息,则该方法需要执行我们的自定义代码,并将消息转发给用户的委托:

@implementation MyScrollViewPrivateDelegate

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [(MyScrollView *)scrollView myScrollViewDidEndDecelerating];
    if ([_userDelegate respondsToSelector:_cmd]) {
        [_userDelegate scrollViewDidEndDecelerating:scrollView];
    }
}

And we need to handle all of the other UIScrollViewDelegate methods that we don't have custom code for, and all of those messages that will be added in future versions of iOS. We have to implement two methods to make that happen:

我们需要处理所有其他的UIScrollViewDelegate方法我们没有定制代码,所有这些消息都将在iOS的未来版本中添加。我们必须实施两种方法来实现这一点:

- (BOOL)respondsToSelector:(SEL)selector {
    return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    // This should only ever be called from `UIScrollView`, after it has verified
    // that `_userDelegate` responds to the selector by sending me
    // `respondsToSelector:`.  So I don't need to check again here.
    [invocation invokeWithTarget:_userDelegate];
}

@end

#2


2  

Thanks @robmayoff I encapsulated to be a more generic delegate interceptor: Having original MessageInterceptor class:

感谢@robmayoff我封装成一个更通用的委托拦截器:拥有原始的MessageInterceptor类:

MessageInterceptor.h

MessageInterceptor.h

@interface MessageInterceptor : NSObject

@property (nonatomic, assign) id receiver;
@property (nonatomic, assign) id middleMan;

@end

MessageInterceptor.m

MessageInterceptor.m

@implementation MessageInterceptor

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.middleMan respondsToSelector:aSelector]) { return    self.middleMan; }
    if ([self.receiver respondsToSelector:aSelector]) { return self.receiver; }
    return [super forwardingTargetForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ([self.middleMan respondsToSelector:aSelector]) { return YES; }
    if ([self.receiver respondsToSelector:aSelector]) { return YES; }
    return [super respondsToSelector:aSelector];
}

@end

It is used in your generic delegate class:

通用委托类中使用:

GenericDelegate.h

GenericDelegate.h

@interface GenericDelegate : NSObject

@property (nonatomic, strong) MessageInterceptor * delegate_interceptor;

- (id)initWithDelegate:(id)delegate;

@end

GenericDelegate.m

GenericDelegate.m

@implementation GenericDelegate

- (id)initWithDelegate:(id)delegate {
    self = [super init];
    if (self) {
        self.delegate_interceptor = [[MessageInterceptor alloc] init];
        [self.delegate_interceptor setMiddleMan:self];
        [self.delegate_interceptor setReceiver:delegate];
    }
    return self;
}

// delegate methods I wanna override:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 1. your custom code goes here
    NSLog(@"Intercepting scrollViewDidScroll: %f %f", scrollView.contentOffset.x, scrollView.contentOffset.y);

    // 2. forward to the delegate as usual
    if ([self.delegate_interceptor.receiver respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [self.delegate_interceptor.receiver scrollViewDidScroll:scrollView];
    }
 }
//other delegate functions you want to intercept
...

So I can intercept any delegate I need to on any UITableView, UICollectionView, UIScrollView... :

我可以拦截任何UITableView UICollectionView UIScrollView。:

@property (strong, nonatomic) GenericDelegate *genericDelegate;
@property (nonatomic, strong) UICollectionView* collectionView;

//intercepting delegate in order to add separator line functionality on this scrollView
self.genericDelegate = [[GenericDelegate alloc]initWithDelegate:self];
self.collectionView.delegate = (id)self.genericDelegate.delegate_interceptor;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    NSLog(@"Original scrollViewDidScroll: %f %f", scrollView.contentOffset.x, scrollView.contentOffset.y);
}

In this case UICollectionViewDelegate scrollViewDidScroll: function will be executed on our GenericDelegate (with any code we want to add) and on in the implementation of our own class

在这种情况下,UICollectionViewDelegate scrollViewDidScroll:函数将在我们的GenericDelegate(带有我们想添加的任何代码)和我们自己类的实现中执行

Thats my 5 cents, thanks to @robmayoff and @jhabbott previous answer

这是我的5分,感谢@robmayoff和@jhabbott之前的回答

#3


1  

Another option is to subclass and use the power of abstract functions. For instance, you create

另一种选择是子类化并使用抽象函数的功能。例如,你创造

@interface EnhancedTableViewController : UITableViewController <UITableViewDelegate>

There you override some delegate method and define your abstract function

这里你重写了一些委托方法并定义了你的抽象函数

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // code before interception
    [self bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:indexPath];
    // code after interception
}

- (void)bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {};

Now all you need to do is to subclass your EnhancedTableViewController and use your abstract functions instead of delegate ones. Like this:

现在你需要做的就是子类化你的强化dtableviewcontroller使用你的抽象函数而不是委托函数。是这样的:

@interface MyTableViewController : EnhancedTableViewController ...

- (void)bfTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //overriden implementation with pre/post actions
};

Let me know if there is anything wrong here.

如果有什么问题,请告诉我。

#4


0  

Don't subclass it, encapsulate it instead ;)

不要子类化它,而是封装它;)

Make a new UIView subclass - your header file would look like :

创建一个新的UIView子类-你的头文件看起来像:

@interface MySubclass : UIView 
@end

And just have a UIScrollView as a subview - your .m file would look like :

把UIScrollView作为子视图——你的。m文件应该是这样的:

@interface MySubclass () <UIScrollViewDelegate>

@property (nonatomic, strong, readonly) UIScrollView *scrollView;

@end


@implementation MySubClass

@synthesize scrollView = _scrollView;

- (UIScrollView *)scrollView {
    if (nil == _scrollView) {
        _scrollView = [UIScrollView alloc] initWithFrame:self.bounds];
        _scrollView.delegate = self;
        [self addSubView:_scrollView];
    }
    return _scrollView;
}

...
your code here
...

@end

Inside your subclass, just always use self.scrollView and it will create the scroll view the first time you ask for it.

在你的子类中,总是使用self。它会在你第一次请求时创建滚动视图。

This has the benefit of hiding the scroll view completely from anyone using your MySubClass - if you needed to change how it worked behind the scenes (i.e. change from a scroll view to a web view) it would be very easy to do :)

这样做的好处是,可以将滚动视图完全隐藏起来,不让任何使用MySubClass的人看到——如果您需要更改它在幕后的工作方式(即从滚动视图更改为web视图),那么很容易做到:

It also means that no-one can alter how you want the scroll view to behave :)

这也意味着没有人可以改变你想要的滚动视图的行为:)

PS I've assumed ARC - change strong to retain and add dealloc if necessary :)

PS我假设电弧变化强烈以保留和增加dealloc如果必要:)


EDIT

编辑

If you want your class to behave exactly as a UIScrollView then you could try this adding this method (taken from these docs and untested!) :

如果您希望您的类的行为与UIScrollView完全一样,那么您可以尝试添加这个方法(从这些文档中提取,未经测试!)

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([self.scrollView respondsToSelector:anInvocation.selector])
        [anInvocation invokeWithTarget:self.scrollView];
    else
        [super forwardInvocation:anInvocation];
}

#5


0  

For those of you looking to do this sort of thing with UIControl classes, see my STAControls project (specifically Base classes like this one). I have implemented delegate forwarding for the project using Rob Mayoff's answer.

对于那些希望使用UIControl类做这类事情的人,请参阅我的STAControls项目(特别是像这样的基类)。我已经使用Rob Mayoff的答案实现了项目的委托转发。