Subclass API Superpowers

Short and simple example of subclassing while preserving your superclass API

September 18, 2017 - 4 minute read -
swift ios uikit

The Problem

I love protocols and extensions as much as the next Swift developer, but when dealing with UIKit, sometimes you just need to subclass to share functionality or implement a reusable peice of UI. Recently I ran into this when subclassing UIScrollView — to implement the behavior I wanted in my subclass, I had to become the scroll view delegate to receive updates on scrollViewDidScroll(_:). This meant conforming to UIScrollViewDelegate and setting self.delegate = self — so far no issues. But what if someone uses your subclass and needs to receive their own updates for scrollViewDidScroll(_:)? It turns out there’s a neat trick that will let you set a delegate for yourself while preserving the UIKit API — i.e., someone else can set your delegate property without messing with your own delegate.

The Solution

Below is an example of how to preserve the superclass API while using it for your own needs as well. It’s a UIScrollView subclass that listens to its own scrollViewDidScroll(_:) while allowing another class to be its delegate.

import UIKit

class LayeredScrollView: UIScrollView {
    
    // 1. Use a private var to store the delegate you're publicly exposing
    private weak var _delegate: UIScrollViewDelegate?

    // 2. Override the super `delegate` property, getting and setting your private backing var
    override var delegate: UIScrollViewDelegate? {
        get {
            return _delegate
        }
        set {
            _delegate = newValue
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)

        // 3. Set the delegate of super to `self` — super maintains the old implementation
        super.delegate = self
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension LayeredScrollView: UIScrollViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 4. Implement whatever you need! (Hopefully more than a print 😝)
        print(scrollView.contentOffset)
        
        // 5. Call your own delegate's version of the method — it's an optional method, hence the ?
        delegate?.scrollViewDidScroll?(scrollView)
    }
    
    // 5. Continued... read on
}

By overriding the property like this and using super’s version for ourself, we can still have another class set themselves as our delegate, while having our own implementation called by super.

The Downside

Point 5. says to “read on” and that’s because there’s a caveat, and some missing code in my sample. Since you’re the superclass’s delegate, you need to implement every delegate method and forward those to your own delegate. This can result in a lot of boilerplate, but isn’t hard to implement — just copy and paste from the header file 😄. If you’re implementing a subclass like this for a framework, it’s well worth the effort to preserve the existing UIScrollView API. Even interally, you never know when someone might set the delegate property and spend hours (or minutes if you’re quite intelligent) wondering why they broke your class. All in all, it’s a neat trick when you need to subclass.