Как создать подкласс UIScrollView и сделать свойство делегата закрытым

Вот чего я хочу добиться:

Я хочу создать подкласс UIScrollView, чтобы иметь дополнительную функциональность. Этот подкласс должен иметь возможность реагировать на прокрутку, поэтому я должен установить для свойства делегата значение 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

Таким образом, мой подкласс может сделать что-то особенное, когда унаследованное представление прокрутки заканчивает прокрутку, но все еще информирует внешнего делегата о событии. Пока это работает. Но поскольку я хочу сделать этот подкласс доступным для других разработчиков, я хочу ограничить доступ к свойству делегата базового класса. , так как он должен использоваться только подклассом. Я думаю, что наиболее вероятно, что другие разработчики интуитивно используют свойство делегата базового класса, даже если я прокомментирую проблему в заголовочном файле. Если кто-то изменит свойство делегата, подкласс выиграет' Я не делаю то, что должен делать, и я ничего не могу сделать, чтобы предотвратить это прямо сейчас И это тот момент, когда я понятия не имею, как это решить.

Я попытался переопределить свойство делегата, чтобы сделать его доступным только для чтения следующим образом:

@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'

Хорошо, плохая идея, так как здесь я явно нарушаю принцип подстановки Лискова.

Следующая попытка --> Попытка переопределить установщик делегата следующим образом:

...
- (void) setDelegate(id<UIScrollViewDelegate>)newDelegate {
    if (newDelegate != self) self.myOwnDelegate = newDelegate;
    else _delegate = newDelegate; // <--- This does not work!
}
...

Как было сказано, этот пример не компилируется, так как похоже, что переменная _delegate не найдена?! Итак, я просмотрел заголовочный файл UIScrollView и нашел это:

@package
    ...
    id           _delegate;
...

Директива @package ограничивает доступ к _delegate ivar только самой структурой. Поэтому, когда я хочу установить _delegate ivar, я ДОЛЖЕН использовать синтезированный сеттер. Я не вижу способа переопределить это каким-либо образом :( Но я не могу поверить, что нет способа обойти это, может быть, я не вижу леса за деревьями.

Я ценю любой подсказка по решению этой проблемы.


Решение:

Теперь работает с решением @rob mayoff. Как я прокомментировал ниже, была проблема с вызовом scrollViewDidScroll:. Наконец-то я узнал, в чем проблема. , даже я не понимаю, почему так :/

Прямо в тот момент, когда мы устанавливаем суперделегат:

- (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, поэтому возвращаемое значение НЕТ!Похоже, это приводит к тому, что методы scrollViewDidScroll: не будут запускаться впоследствии.Это похоже на предварительную проверку, если метод реализован, и если он не работает, этот метод вообще не будет запущен, даже если мы впоследствии установим наше свойство userDelegate. Я не знаю, почему это так, поскольку большинство других методов делегата не имеют этой предварительной проверки.

Итак, мое решение для этого состоит в том, чтобы вызвать метод [super setDelegate...] в методе PrivateDelegate 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 за пример!

26
задан ROMANIA_engineer 20 June 2017 в 19:59
поделиться