如何子类化UIScrollView并将委托属性设为私有 [英] How to subclass UIScrollView and make the delegate property private

查看:134
本文介绍了如何子类化UIScrollView并将委托属性设为私有的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下是我想要实现的目标:

Here is what I want to achieve:

我想将UIScrollView子类化以获得其他功能。这个子类应该能够对滚动做出反应,所以我必须将delegate属性设置为self来接收以下事件:

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:

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

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

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

所以我有不同的想法如何解决这个问题,但所有这些并不完全令我满意:(

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

这样我的子类可以做一些特别的事情继承的scrollview结束滚动,但仍然通知外部委托事件。这到目前为止工作。但是因为我想让这个子类可供其他开发人员使用,我想限制对基类委托属性的访问,因为它只应由子类使用。我认为其他开发人员最有可能直观地使用基类的委托属性,即使我在头文件中注释问题。如果有人改变委托属性,子类将不会做它应该做的事情,我现在无法做任何事情来阻止它。这就是我不知道如何解决它的问题。

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.

我试图覆盖委托属性,使其只读:

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

这将导致警告

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

好主意,因为我在这里显然违反了liskovs替换原则。

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

接下来尝试 - >试图像这样覆盖委托制定者:

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

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

如评论所示,此示例无法编译,因为似乎_delegate ivar wasn'找到了吗?!所以我查了UIScrollView的头文件,发现了这个:

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:

@package
    ...
    id           _delegate;
...

@package指令限制_delegate ivar的访问权限只能由框架本身访问。因此,当我想设置_delegate ivar时,我必须使用合成的setter。我看不出以任何方式覆盖它的方法:(但我不敢相信没有办法解决这个问题,也许我看不到木头的树木。

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.

感谢您解决此问题的任何提示。

I appreciate for any hint on solving this problem.

现在可以使用@rob mayoff的解决方案了。正如我在下面评论的那样,scrollViewDidScroll:call有问题。我终于找到了,问题是什么,即使我不明白为什么会这样:/

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 :/

在我们设置超级代表的那一刻:

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
}

有回调_myDelegate。调试器中断

there is a callback to _myDelegate. The debugger breaks at

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

以scrollViewDidScroll:选择器作为参数。

with the "scrollViewDidScroll:" selector as argument.

此时有趣的事情是self.userDelegate尚未设置并指向nil,因此返回值为NO!这似乎导致之后不会触发scrollViewDidScroll:方法。它看起来像是一个precheck如果方法被实现,如果失败,这个方法根本不会被触发,即使我们之后设置了userDelegate属性。我不知道为什么会这样,因为大多数其他委托方法没有这个预先检查。

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.

所以我的解决办法就是调用[super]在PrivateDelegate setDelegate方法中的setDelegate ...]方法,因为这是我非常确定我的userDelegate方法设置的地方。

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.

所以我最终会得到此实现代码段:

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

其余代码保持不变。我仍然不满意这种解决方法,因为它需要至少调用一次setDelegate方法,但它现在可以满足我的需求,虽然感觉非常hacky:/

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 :/

如果有人有想法如何改进,我会很感激。

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

谢谢@rob为你的榜样!

Thanks @rob for your example!

推荐答案

使 MySubclass 成为自己的委托时出现问题。据推测,您不希望为 UIScrollViewDelegate 方法的所有运行自定义代码,但您必须将消息转发给用户提供的委托你是否有自己的实现。所以你可以尝试实现所有委托方法,其中大多数只是这样转发:

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:

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

这里的问题是有时新版本的iOS会添加新的委托方法。例如,iOS 5.0添加了 scrollViewWillEndDragging:withVelocity:targetContentOffset:。所以你的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.

处理这个问题的最好方法是创建一个独立的私有对象,它只是你的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:

@interface MyScrollView : UIScrollView
@end

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

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:

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

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

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.

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

我们需要覆盖 setDelegate:委托:存储并返回对用户委托的引用:

We need to override setDelegate: and delegate: to store and return a reference to the user's 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

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

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:

@implementation MyScrollViewPrivateDelegate

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

我们需要处理所有其他 UIScrollViewDelegate 我们没有自定义代码的方法,以及将在iOS未来版本中添加的所有消息。我们必须实现两种方法来实现:

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:

- (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

这篇关于如何子类化UIScrollView并将委托属性设为私有的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆