В Swift, как у меня есть подкласс UIScrollView, который имеет внутренний и внешний делегат?
Я подклассифицирую UIScrollView
, чтобы добавить некоторые функции, такие как двойное нажатие для масштабирования и свойство изображения для целей галереи. Но для того, чтобы сделать часть изображения, мой подкласс должен быть его собственным делегатом и реализовать viewForZoomingInScrollView
.
Но тогда, когда кто-то использует мой подкласс просмотра прокрутки, они могут также получать уведомления делегата, а также видеть scrollViewDidScroll
или что у вас есть.
В Swift, как мне получить оба этих?
Ответы
Ответ 1
Вот быстрая версия этого шаблона:
Хотя forwardInvocation:
отключен в Swift, мы все равно можем использовать forwardingTargetForSelector:
class MyScrollView: UIScrollView {
class _DelegateProxy: NSObject, UIScrollViewDelegate {
weak var _userDelegate: UIScrollViewDelegate?
override func respondsToSelector(aSelector: Selector) -> Bool {
return super.respondsToSelector(aSelector) || _userDelegate?.respondsToSelector(aSelector) == true
}
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
if _userDelegate?.respondsToSelector(aSelector) == true {
return _userDelegate
}
else {
return super.forwardingTargetForSelector(aSelector)
}
}
func viewForZoomingInScrollView(scrollView: MyScrollView) -> UIView? {
return scrollView.viewForZooming()
}
// Just a demo. You don't need this.
func scrollViewDidScroll(scrollView: MyScrollView) {
scrollView.didScroll()
_userDelegate?.scrollViewDidScroll?(scrollView)
}
}
private var _delegateProxy = _DelegateProxy()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = _delegateProxy
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = _delegateProxy
}
override var delegate:UIScrollViewDelegate? {
get {
return _delegateProxy._userDelegate
}
set {
self._delegateProxy._userDelegate = newValue;
/* It seems, we don't need this anymore.
super.delegate = nil
super.delegate = _delegateProxy
*/
}
}
func viewForZooming() -> UIView? {
println("self viewForZooming")
return self.subviews.first as? UIView // whatever
}
func didScroll() {
println("self didScroll")
}
}
Ответ 2
Здесь простая рабочая версия игровой площадки в Swift 3, которая действует исключительно как наблюдатель, а не только как перехватчик, как и другие ответы здесь.
Отличие заключается в том, что у делегата представления прокрутки оригинала должны быть все его методы делегирования, которые называются нормальными или их уволенными другим делегатом.
(Вы можете скопировать/вставить это в игровое поле и запустить его для тестирования)
import UIKit
final class ScrollViewObserver: NSObject, UIScrollViewDelegate {
// MARK: - Instantiation
init(scrollView: UIScrollView) {
super.init()
self.scrollView = scrollView
self.originalScrollDelegate = scrollView.delegate
scrollView.delegate = self
}
deinit {
self.remove()
}
// MARK: - API
/// Removes ourselves as an observer, resetting the scroll view original delegate
func remove() {
self.scrollView?.delegate = self.originalScrollDelegate
}
// MARK: - Private Properties
fileprivate weak var scrollView: UIScrollView?
fileprivate weak var originalScrollDelegate: UIScrollViewDelegate?
// MARK: - Forwarding Delegates
/// Note: we forward all delegate calls here since Swift does not support forwardInvocation: or NSProxy
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Run any custom logic or send any notifications here
print("proxy did scroll")
// Then, forward the call to the original delegate
self.originalScrollDelegate?.scrollViewDidScroll?(scrollView)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidZoom?(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewWillBeginDragging?(scrollView)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
self.originalScrollDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.originalScrollDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewWillBeginDecelerating?(scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidEndDecelerating?(scrollView)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.originalScrollDelegate?.viewForZooming?(in: scrollView)
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
self.originalScrollDelegate?.scrollViewWillBeginZooming?(scrollView, with: view)
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
self.originalScrollDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale)
}
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return self.originalScrollDelegate?.scrollViewShouldScrollToTop?(scrollView) == true
}
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidScrollToTop?(scrollView)
}
}
final class TestView: UIView, UIScrollViewDelegate {
let scrollView = UIScrollView()
fileprivate(set) var scrollObserver: ScrollViewObserver?
required init() {
super.init(frame: .zero)
self.scrollView.delegate = self
self.scrollObserver = ScrollViewObserver(scrollView: self.scrollView)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("view original did scroll delegate method called")
}
}
let testView = TestView()
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 100), animated: true)
testView.scrollObserver?.remove()
print("removed the observer")
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 200), animated: true)
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 300), animated: true)
Отпечатает
прокси сделал прокрутку
просмотреть оригинал прокрутил метод делегирования под названием
удалил наблюдателя
просмотреть оригинал прокрутил метод делегирования под названием
просмотреть оригинал прокрутил метод делегирования под названием
Ответ 3
Я не знаю о 100% -ном решении Swift для этого.
Принимая этот ответ ObjC к той же проблеме и пытаясь перенести его в Swift, оказывается, что это невозможно, так как NSInvocation недоступен в Swift.
Что мы можем сделать, так это реализовать предложенный MyScrollViewPrivateDelegate в ObjC (не забудьте импортировать его в заголовочный файл моста) и подкласс вида прокрутки в Swift, как показано ниже:
MyScrollView.swift
import UIKit
class MyScrollView: UIScrollView {
private let myDelegate = MyScrollViewPrivateDelegate()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = myDelegate
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = myDelegate
}
override var delegate: UIScrollViewDelegate? {
set {
myDelegate.userDelegate = newValue
super.delegate = nil
super.delegate = myDelegate
}
get {
return myDelegate.userDelegate
}
}
func viewForZooming() -> UIView {
return UIView()// return whatever you want here...
}
}
MyScrollViewPrivateDelegate.h
#import <UIKit/UIKit.h>
@interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate>
@property (weak, nonatomic) id<UIScrollViewDelegate> userDelegate;
@end
MyScrollViewPrivateDelegate.m
#import "MyScrollViewPrivateDelegate.h"
#import "YOUR_MODULE-Swift.h"
@implementation MyScrollViewPrivateDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
// you could check if the user delegate responds to viewForZoomingInScrollView and call it instead...
return [(MyScrollView *)scrollView viewForZooming];
}
- (BOOL)respondsToSelector:(SEL)selector
{
return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:_userDelegate];
}
@end
Ответ 4
UIScrollView
уже определяет свойство delegate
, что означает, что любой подкласс UIScrollView
не сможет определить свойство с именем delegate
.
Ниже CustomScrollView
имеет экземпляр UIScrollView
вместо подкласса UIScrollView
. Это позволяет CustomScrollView
определять свойство delegate
без конфликта с суперклассом.
protocol CustomScrollViewDelegate: class {
func scrollViewDidScroll()
}
class CustomScrollView: UIView, UIScrollViewDelegate {
var scrollView: UIScrollView = UIScrollView()
weak var delegate: CustomScrollViewDelegate? // External delegate
override init (frame : CGRect) {
super.init(frame : frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup() {
scrollView.delegate = self // Internal delegate
addSubview(scrollView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll() // External delegation
}
}
Ответ 5
Swift 4+ версия Rintaro отличный ответ:
class MyScrollView: UIScrollView {
class _DelegateProxy: NSObject, UIScrollViewDelegate {
weak var _userDelegate: UIScrollViewDelegate?
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || _userDelegate?.responds(to: aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if _userDelegate?.responds(to: aSelector) == true {
return _userDelegate
}
return super.forwardingTarget(for: aSelector)
}
//This function is just a demonstration, it can be replaced/removed.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
(scrollView as? MyScrollView)?.didScroll()
_userDelegate?.scrollViewDidScroll?(scrollView)
}
}
fileprivate let _delegateProxy = _DelegateProxy()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = _delegateProxy
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = _delegateProxy
}
override var delegate: UIScrollViewDelegate? {
get {
return _delegateProxy._userDelegate
}
set {
_delegateProxy._userDelegate = newValue
}
}
func didScroll() {
print("didScroll")
}
}