Mex*_*xyn 26 delegates subclass objective-c uiscrollview ios
这是我想要实现的目标:
我想子类化UIScrollView以获得其他功能.这个子类应该能够对滚动作出反应,所以我必须将delegate属性设置为self来接收如下事件:
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { ... }
另一方面,其他类仍然应该能够接收这些事件,就像它们使用基础UIScrollView类一样.
所以我有不同的想法如何解决这个问题,但所有这些并不完全令我满意:(
我的主要方法是......使用像这样的自己的委托属性:
@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结束滚动时,我的子类可以执行一些特殊操作,但仍然会通知事件的外部委托.这到目前为止工作.但是因为我想让这个子类可供其他开发人员使用,我想限制对基类委托属性的访问,因为它只应由子类使用.我认为其他开发人员最有可能直观地使用基类的委托属性,即使我在头文件中注释问题.如果有人改变委托属性,子类将不会做它应该做的事情,我现在无法阻止它.这就是我不知道如何解决它的问题.
我尝试尝试覆盖委托属性,使其只读取:
@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替换原则.
接下来尝试 - >尝试覆盖委托设置器,如下所示:
...
- (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:call出现了问题.我终于找到了,问题是什么,即使我不明白为什么会这样:/
就在我们设置超级代表的那一刻:
- (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属性.我不知道为什么会这样,因为大多数其他委托方法都没有预先检查.
所以我的解决方法是调用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方法,但它现在适用于我的需求,虽然感觉非常hacky:/
如果有人有想法如何改进,我会很感激.
谢谢你@rob为你的榜样!
rob*_*off 43
制作MySubclass自己的委托时出现问题.想必你不想为运行自定义代码的所有的UIScrollViewDelegate方法,但你有你是否有自己的实现或不将消息转发给用户提供的委托.所以你可以尝试实现所有的委托方法,其中大多数只是像这样转发:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self.myOwnDelegate scrollViewDidZoom:scrollView];
}
这里的问题是有时新版本的iOS会添加新的委托方法.例如,iOS 5.0添加了scrollViewWillEndDragging:withVelocity:targetContentOffset:.所以你的scrollview子类将不会是面向未来的.
处理此问题的最佳方法是创建一个单独的私有对象,该对象仅充当您的scrollview的委托,并处理转发.此专用委托对象可以将收到的每条消息转发给用户提供的委托,因为它只接收委托消息.
这是你做的.在头文件中,您只需要为scrollview子类声明接口.您不需要公开任何新方法或属性,因此它看起来像这样:
@interface MyScrollView : UIScrollView
@end
所有实际工作都在.m文件中完成.首先,我们定义私有委托类的接口.它的工作是回调MyScrollView一些委托方法,并将所有消息转发给用户的委托.所以我们只想给它一些方法UIScrollViewDelegate.我们不希望它有额外的方法来管理对用户委托的引用,所以我们只将该引用保留为实例变量:
@interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate> {
@public
    id<UIScrollViewDelegate> _userDelegate;
}
@end
接下来我们将实施MyScrollView.它需要创建一个MyScrollViewPrivateDelegate它需要拥有的实例.由于a 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];
}
我们需要覆盖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;
}
我们还需要定义我们的私有委托可能需要使用的任何额外方法:
- (void)myScrollViewDidEndDecelerating {
    // do whatever you want here
}
@end
现在我们可以最终定义实现了MyScrollViewPrivateDelegate.我们需要明确定义应包含私有自定义代码的每个方法.该方法需要执行我们的自定义代码,并将消息转发给用户的委托,如果用户的委托响应消息:
@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
| 归档时间: | 
 | 
| 查看次数: | 18370 次 | 
| 最近记录: |