这是我想要实现的:
我想子类化 UIScrollView 以获得额外的功能。这个子类应该能够对滚动使用react,所以我必须将委托(delegate)属性设置为 self 以接收如下事件:
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { ... }
另一方面,其他类也应该仍然能够接收这些事件,就像它们正在使用基本的 UIScrollView 类一样。
所以我对如何解决这个问题有不同的想法,但所有这些都不完全让我满意:(
我的主要方法是..像这样使用自己的委托(delegate)属性:
@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 结束滚动时,我的子类可以做一些特殊的事情,但仍然通知事件的外部委托(delegate)。到目前为止有效。但是因为我想让其他开发人员可以使用这个子类,所以我想限制对基类委托(delegate)属性的访问,因为它只能由子类使用。我认为很可能是其他开发者凭直觉使用了基类的委托(delegate)属性,即使我在头文件中注释了这个问题。如果有人改变委托(delegate)属性,子类将不会做它应该做的事情,我现在无法做任何事情来阻止它。这就是我不知道如何解决它的地方。
我尝试的是尝试覆盖委托(delegate)属性,使其像这样只读:
@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'
好吧,坏主意,因为我在这里显然违反了里氏替换原则。
下一步尝试 --> 尝试像这样覆盖委托(delegate) setter :
...
- (void) setDelegate(id<UIScrollViewDelegate>)newDelegate {
if (newDelegate != self) self.myOwnDelegate = newDelegate;
else _delegate = newDelegate; // <--- This does not work!
}
...
如评论所述,此示例无法编译,因为似乎未找到 _delegate ivar?!所以我查找了 UIScrollView 的头文件,发现了这个:
@package
...
id _delegate;
...
@package 指令将 _delegate ivar 的访问限制为只能由框架本身访问。因此,当我想设置 _delegate ivar 时,我必须使用合成 setter。我看不到以任何方式覆盖它的方法 :( 但我不敢相信没有办法解决这个问题,也许我只见树木不见森林。
感谢您提供解决此问题的任何提示。
解决方案:
它现在适用于@rob mayoff 的解决方案。正如我在下方评论的那样,scrollViewDidScroll: 调用存在问题。我终于找到了,问题是什么,即使我不明白为什么会这样:/
就在我们设置 super 委托(delegate)的那一刻:
- (id) initWithFrame:(CGRect)frame {
...
_myDelegate = [[[MyPrivateDelegate alloc] init] autorelease];
[super setDelegate:_myDelegate]; <-- Callback is invoked here
}
有一个回调到 _myDelegate。调试器在
- (BOOL) respondsToSelector:(SEL)aSelector {
return [self.userDelegate respondsToSelector:aSelector];
}
以“scrollViewDidScroll:”选择器作为参数。
有趣的是此时self.userDelegate还没有设置指向nil,所以返回值为NO!这似乎导致 scrollViewDidScroll: 方法之后不会被触发。如果该方法被实现,它看起来像是一个预检查,如果它失败了,这个方法根本不会被触发,即使我们之后设置了我们的 userDelegate 属性。我不知道为什么会这样,因为大多数其他委托(delegate)方法没有这种预检查。
所以我的解决方案是,在 PrivateDelegate setDelegate 方法中调用 [super setDelegate...] 方法,因为这是我非常确定我的 userDelegate 方法已设置的地方。
所以我将以这个实现片段结束:
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 方法,但它目前可以满足我的需要,虽然感觉很老套:/
如果有人有改进的想法,我将不胜感激。
感谢@rob 的示例!
最佳答案
使 MySubclass
成为自己的委托(delegate)存在问题。假设您不想为 所有 UIScrollViewDelegate
方法运行自定义代码,但是无论您是否有自己的实现,都必须将消息转发给用户提供的委托(delegate)或不。所以你可以尝试实现所有的委托(delegate)方法,其中大部分只是像这样转发:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self.myOwnDelegate scrollViewDidZoom:scrollView];
}
这里的问题是有时新版本的 iOS 会添加新的委托(delegate)方法。例如,iOS 5.0 添加了 scrollViewWillEndDragging:withVelocity:targetContentOffset:
。所以你的 scrollview 子类不会面向 future 。
处理此问题的最佳方法是创建一个单独的私有(private)对象,它仅充当 ScrollView 的委托(delegate),并处理转发。这个专用委托(delegate)对象可以将它收到的每条消息转发给用户提供的委托(delegate),因为它仅接收委托(delegate)消息。
这就是你要做的。在你的头文件中,你只需要为你的 scrollview 子类声明接口(interface)。您不需要公开任何新方法或属性,因此它看起来像这样:
MyScrollView.h
@interface MyScrollView : UIScrollView
@end
所有实际工作都在 .m
文件中完成。首先,我们为私有(private)委托(delegate)类定义接口(interface)。它的工作是为某些委托(delegate)方法回调 MyScrollView
,并将所有 消息转发给用户的委托(delegate)。所以我们只想给它方法是 UIScrollViewDelegate
的一部分。我们不希望它有额外的方法来管理对用户委托(delegate)的引用,因此我们只将该引用保留为实例变量:
MyScrollView.m
@interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate> {
@public
id<UIScrollViewDelegate> _userDelegate;
}
@end
接下来我们将实现MyScrollView
。它需要创建它需要拥有的 MyScrollViewPrivateDelegate
实例。由于 UIScrollView
不拥有它的委托(delegate),我们需要对此对象的额外强引用。
@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:
来存储和返回对用户委托(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;
}
我们还需要定义我们的私有(private)委托(delegate)可能需要使用的任何额外方法:
- (void)myScrollViewDidEndDecelerating {
// do whatever you want here
}
@end
现在我们终于可以定义MyScrollViewPrivateDelegate
的实现了。我们需要明确定义应包含我们私有(private)自定义代码的每个方法。该方法需要执行我们的自定义代码,并将消息转发给用户的委托(delegate),如果用户的委托(delegate)响应消息:
@implementation MyScrollViewPrivateDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[(MyScrollView *)scrollView myScrollViewDidEndDecelerating];
if ([_userDelegate respondsToSelector:_cmd]) {
[_userDelegate scrollViewDidEndDecelerating:scrollView];
}
}
我们还需要处理所有其他我们没有自定义代码的 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
关于objective-c - 如何继承 UIScrollView 并将委托(delegate)属性设为私有(private),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9978279/