Подкласс масштабирующего UIScrollView без методов делегата

Я хочу реализовать UIScrollView подкласс, чтобы представить некоторый пользовательский форматированный контент. Я просто устанавливаю свойство объекта модели в представлении прокрутки, и оно обрабатывает все необходимые макеты и рендеринг для отображения содержимого.

Это отлично работает, но теперь я хотел бы включить масштабирование. Согласно документации, для поддержки масштабирования необходимо установить делегата и реализовать viewForZoomingInScrollView: метод. Я думаю, я мог бы установить делегат на само представление прокрутки и реализовать этот метод в подклассе. Но при этом я потерял бы возможность иметь внешний делегат (например, инкапсулирующий UIViewController), который мог бы получать уведомления о событиях прокрутки.

Если предположить, что документация верна, и нет абсолютно никакого (документированного) способа реализовать масштабирование без делегата, как я могу сохранить возможность иметь регулярного, не связанного с ним делегата?

3 ответа

Решение

Я бы злоупотребил тем фактом, что я являюсь подклассом (специально: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

Основываясь на предложении H2CO3 сохранить скрытый указатель на реального делегата и переслать ему все входящие сообщения, я пришел к следующему решению.

Объявите частную переменную делегата для хранения ссылки на "настоящий" делегат, который передается в setDelegate: метод:

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

Установите делегата на себя, чтобы получать уведомления о событиях прокрутки. использование superитак оригинал setDelegate: Реализация называется, а не наша модифицированная.

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

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

Не забудьте вручную переслать те методы делегата, которые реализованы подклассом.

Может быть, это легче прочитать и понять пару недель спустя:) (пример кода для перехвата locationManager:didUpdateLocations: в подклассе)

Кроме того, та же обработка для установки себя как делегата для суперкласса и перехвата 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;
}
Другие вопросы по тегам