Lê Khánh Vinh
Lê Khánh Vinh

Reputation: 2611

Swift proper use of protocol extension

I'm trying to extract some of the code base for re use purpose. My approach is using Protocol and Protocol Extension instead of general BaseClass.

I have create the below a protocol and protocol extension

protocol MovieDisplay {

    var collectionView: UICollectionView! { get set }
    var refreshControl: UIRefreshControl! { get set }

}

extension MovieDisplay where Self: UIViewController {

    var refreshControl: UIRefreshControl {
        let rc = UIRefreshControl()
        rc.backgroundColor = .clear
        rc.tintColor = .lightGray
        if #available(iOS 10.0, *) {
            collectionView.refreshControl = rc
        } else {
            // Fallback on earlier versions
            collectionView.addSubview(rc)
        }
        return rc
    }

}

In my main class that adopt the protocol I declare like this (using default implementation of refreshcontrol)

class PopularMovieVC: UIViewController, MovieDisplay {

    @IBOutlet weak var collectionView: UICollectionView!

}

The problem is function which involve refreshcontrol does not work. It works only when I explicitly declare refreshcontrol variable inside main class and convert extension into function and call it inside main class like below:

func setupRefreshControl() {
            refreshControl.backgroundColor = .clear
            refreshControl.tintColor = .lightGray
            if #available(iOS 10.0, *) {
                collectionView.refreshControl = refreshControl
            } else {
                // Fallback on earlier versions
                collectionView.addSubview(refreshControl)
            }
}

How to properly configure the protocol and protocol extension for default implementation?

Upvotes: 0

Views: 259

Answers (2)

Rob Napier
Rob Napier

Reputation: 299485

Your protocol requires a gettable and settable refreshControl (that returns UIRefreshControl!), but your default implementation only provides a getter (and that getter returns a different type, UIRefreshControl). Your default implementation also returns a different UIRefreshControl every time it is accessed, and modifies collectionView every time it's accessed. None of this, I think, is what you mean.

As vadian notes, I think a base class is what you really want here, if you want to modify collectionView.refreshControl automatically. Conforming to a protocol should never cause implicit changes to other properties, and in most cases it can't. Imagine if PopularMovieVC were conformed to MovieDisplay in an extension in another module. That would lead to confusion at best.

Protocol conformance extends how a type can be used, such as adding new methods. It doesn't change anything about the type itself. If you want to change something about the type itself, you need something like inheritance or composition, not protocol conformance.


If you're doing a bit of this, I wouldn't do it with protocols this way. I'd just create extensions like this:

extension UIRefreshControl {
    static func makeStandard(attachedTo collectionView: UICollectionView) -> UIRefreshControl {
        let rc = UIRefreshControl()
        rc.backgroundColor = .clear
        rc.tintColor = .lightGray
        if #available(iOS 10.0, *) {
            collectionView.refreshControl = rc
        } else {
            // Fallback on earlier versions
            collectionView.addSubview(rc)
        }
        return rc
    }
}

extension UIActivityIndicatorView {
    static func makeStandard() -> UIActivityIndicatorView {
        return UIActivityIndicatorView(style: .gray)
    }
}

Then your view controller might look like:

class MyViewController: UIViewController {
    private var refreshController: UIRefreshControl!
    @IBOutlet var collectionView: UICollectionView!
    let activityIndicator = UIActivityIndicatorView.makeStandard()

    override func viewDidLoad() {
        refreshController = .makeStandard(attachedTo: collectionView)
    }
}

No protocols needed, and this allows you to handle multiple collection views in the same view controller, or any other unusual situation. It also makes it clearer that calling this method will modify the collection view.

Upvotes: 1

vadian
vadian

Reputation: 285170

It doesn't work because the computed property isn't called implicitly.

Adding this line in viewDidLoad should initialize the refresh control

_ = refreshControl

In this case I'd really prefer a base class

Upvotes: 1

Related Questions