4

我想实现一个UIScrollView子类来呈现一些自定义格式的内容。我只是设置了滚动视图的模型对象属性,它处理所有必需的布局和渲染以显示内容。

这工作正常,但现在我想包括缩放。根据文档,要支持缩放,您必须设置一个委托并实现该viewForZoomingInScrollView:方法。我想我可以将委托设置为滚动视图本身并在子类中实现该方法。但是这样做我将失去拥有可以通知滚动事件的外部委托(如封装的 UIViewController)的能力。

假设文档是正确的,并且绝对没有(记录的)方法可以在没有委托的情况下实现缩放,我怎么能仍然保留拥有一个常规的、不相关的委托的可能性?

4

3 回答 3

5

基于 H2CO3 的建议,即保存一个指向真正代表的隐藏指针并将所有传入消息转发给它,我提出了以下解决方案。

声明一个私有委托变量来存储对传递给setDelegate:方法的“真实”委托的引用:

@interface BFWaveScrollView ()
@property (nonatomic, weak) id<UIScrollViewDelegate> ownDelegate;
@end

将委托设置为 self 以收到有关滚动事件的通知。使用super,所以setDelegate:调用的是原始实现,而不是我们修改过的。

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

覆盖setDelegate:以保存对“真实”委托的引用。

- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
    _ownDelegate = delegate;
}

UIScrollView尝试调用其委托的方法时,它会首先检查委托是否respondsToSelector:。如果选择器是UIScrollViewDelegate协议的一部分,我们必须将其转发给真正的委托(不要忘记#import <objc/runtime.h>)。

- (BOOL)selectorIsScrollViewDelegateMethod:(SEL)selector {
    Protocol *protocol = objc_getProtocol("UIScrollViewDelegate");
    struct objc_method_description description = protocol_getMethodDescription(
                                                   protocol, selector, NO, YES);
    return (description.name != NULL);
}

- (BOOL)respondsToSelector:(SEL)selector {
    if ([self selectorIsScrollViewDelegateMethod:selector]) {
        return [_ownDelegate respondsToSelector:selector] ||
               [super respondsToSelector:selector];
    }
    return [super respondsToSelector:selector];
}

最后,将所有未在子类中实现的委托方法转发给真正的委托:

- (id)forwardingTargetForSelector:(SEL)selector {
    if ([self selectorIsScrollViewDelegateMethod:selector]) {
        return _ownDelegate;
    }
    return [super forwardingTargetForSelector:selector];
}

不要忘记手动转发那些由子类实现的委托方法。

于 2013-03-27T23:15:38.387 回答
2

我会滥用我是一个子类的事实(故意:P)。所以你可以破解它。真的很糟糕,我应该为提出这个解决方案而感到难过。

@interface MyHackishScrollView: UIScrollView {
    id <UIScrollViewDelegate> ownDelegate;
}

@end

@implementation MyHackishScrollView

- (void)setDelegate:(id <UIScrollViewDelegate>)newDel
{
    ownDelegate = newDel;
    [super setDelegate:self];
}

- (UIView *)viewForScrollingInScrollView:(UIScrollView *)sv
{
    return whateverYouWant;
}

// and then implement all the delegate methods
// something like this:
- (void)scrollViewDidScroll:(UIScrollView *)sv
{
    [ownDelegate scrollViewDidScroll:self];
}

// etc.

@end
于 2013-03-27T22:20:29.537 回答
0

也许几周后这更容易阅读和理解:)(用于拦截 locationManager:didUpdateLocations: 在子类中的示例代码)

除了将 self 设置为超类的委托和拦截 setDelegate 以将用户的委托保存到 mDelegate 的相同处理。

编辑:

-(BOOL)respondsToSelector:(SEL)selector {
    if (sel_isEqual(selector, @selector(locationManager:didUpdateLocations:)))
        return true;
    return [mDelegate respondsToSelector:selector];
}

- (id)forwardingTargetForSelector:(SEL)selector {
    if (sel_isEqual(selector, @selector(locationManager:didUpdateLocations:)))
        return self;
    return mDelegate;
}
于 2013-06-06T13:07:19.843 回答