Подкласс масштабирующего 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;
}