0

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?

halfer
  • 19,824
  • 17
  • 99
  • 186
Lê Khánh Vinh
  • 2,591
  • 5
  • 31
  • 77

2 Answers2

1

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

vadian
  • 274,689
  • 30
  • 353
  • 361
  • adding _ = refreshControl also not working. Do you know any approach which does not involve base class? – Lê Khánh Vinh Nov 28 '18 at 18:48
  • If it doesn't work you are going to fight the framework. What's wrong with a base class? – vadian Nov 28 '18 at 18:52
  • Nothing wrong with a base class. I just dont want to end up with bloated base class in later state and want to try making few protocol and protocol extension which can be reuse separatedly – Lê Khánh Vinh Nov 28 '18 at 19:00
1

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hi, thanks for your clarification. So what the recommended approach? (in case no base class). My bellow solution which convert to default function in protocol extension and call it inside main class should be used? – Lê Khánh Vinh Nov 28 '18 at 18:47
  • Exactly. You can add `setupRefreshControl` as a extension on `MovieDisplay` and call it inside `viewDidLoad` or other convenient location. So it becomes a convenience method, rather than trying to modify the type's behavior. – Rob Napier Nov 28 '18 at 18:56
  • Thanks incase of UIActivityIndicatorView which does not involve implicit changes to others properties. The approach still same? (declare variable in protocol, provide default setup in protocol extension, declare variable in main class, call the setup method?) – Lê Khánh Vinh Nov 28 '18 at 19:03
  • See comments above. I would drop the protocols. They're not necessary. Just use static functions if you want some kind of standard values for your app. – Rob Napier Nov 28 '18 at 21:40