In Swift, how do I have a UIScrollView subclass that has an internal and external delegate?

后端 未结 4 1911
悲&欢浪女
悲&欢浪女 2020-12-14 11:24

I\'m subclassing UIScrollView to add some features such as double tap to zoom and an image property for gallery purposes. But in order to do the image part my s

4条回答
  •  夕颜
    夕颜 (楼主)
    2020-12-14 12:09

    Here's a simple working Playground version in Swift 3 that acts purely as an observer rather than only as an interceptor like the other answers here.

    The distinction is that the original scroll view delegate should have all of its delegate methods called like normal versus them being hijacked by another delegate.

    (You can copy/paste this into a playground and run it to test)

    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's 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) {
            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's 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)
    

    This prints

    proxy did scroll

    view's original did scroll delegate method called

    removed the observer

    view's original did scroll delegate method called

    view's original did scroll delegate method called

提交回复
热议问题