这是我想要实现的目标:
Here is what I want to achieve:
我想子类化 UIScrollView 以获得额外的功能.这个子类应该能够对滚动做出反应,所以我必须将委托属性设置为 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
这样,当继承的滚动视图结束滚动时,我的子类可以做一些特殊的事情,但仍会通知事件的外部委托.到目前为止有效.但由于我想让这个子类对其他开发人员可用,我想限制对基类委托属性的访问,因为它应该只由子类使用.我认为其他开发人员很可能直观地使用了基类的委托属性,即使我在头文件中评论了问题.如果有人改变了委托属性,子类将不会做它应该做的事情,我现在不能做任何事情来阻止它.这就是我不知道如何解决它的地方.
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?!于是我查找了 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 时,我必须使用综合设置器.我看不到以任何方式覆盖它的方法 :( 但我不敢相信没有办法解决这个问题,也许我只见树木不见森林.
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: 调用存在问题.我终于找到了,问题是什么,即使我不明白为什么会这样:/
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: 方法之后不会被触发.它看起来像是一个预检查该方法是否被实现,如果它失败了,这个方法根本不会被触发,即使我们之后设置了我们的 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.
所以我的解决方案是,在 PrivateDelegate setDelegate 方法中调用 [super 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
方法的 all 运行自定义代码,但是无论您是否有自己的实现,都必须将消息转发给用户提供的委托或不.所以你可以尝试实现所有的委托方法,其中大多数只是像这样转发:
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:
.所以你的滚动视图子类不会是面向未来的.
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.
处理此问题的最佳方法是创建一个单独的私有对象,该对象仅充当滚动视图的委托,并处理转发.此专用委托对象可以将其接收到的每条消息转发给用户提供的委托,因为它仅接收委托消息.
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.
这就是你要做的.在你的头文件中,你只需要为你的滚动视图子类声明接口.您不需要公开任何新方法或属性,因此它看起来像这样:
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
,并将 all 消息转发给用户的委托.所以我们只想给它提供 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:
和 delegate:
来存储和返回对用户委托的引用:
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 并使委托属性私有的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!