Search code examples
iosswiftprotocols

Swift - Default Implementation of protocol functions in another protocol


The following code is just an example: I need to implement MapProtocol in my ViewController class. The ViewController itself has a variable of type MKMapView. However in my case the MKMapViewDelegate needs to be implemented by an extension of the protocol and cannot be implemented by the ViewController class, but this does not work. The delegate function won't be called at all (only if implemented by the ViewController)

Am I missing a swift restriction for adding default implementations for protocols within another protocol? If yes, is there are proper workaround?

Real case scenario: I have two ViewControllers which share some redundant code (MKMapViewDelegate, etc). So I wanted to outsource this code. I can’t use a superclass because both of the viewcontrollers are already subclasses of two different Types. My first approach was to use an extended protocol.

import UIKit
import MapKit

class ViewController: UIViewController, MapProtocol {

    var mapView: MKMapView = MKMapView()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        setupMapView()
    }

    private func setupMapView() {
        self.view.addSubview(mapView)

        mapView.mapType = MKMapType.standard
        mapView.isZoomEnabled = true
        mapView.isScrollEnabled = true
        mapView.delegate = self

        mapView.translatesAutoresizingMaskIntoConstraints = false
        mapView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
        mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
        mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
        mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
    }
}

/** Works if uncommented */
//extension ViewController: MKMapViewDelegate {
//    func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
//        NSLog("test")
//    }
//}

protocol MapProtocol: MKMapViewDelegate {
    var mapView: MKMapView { set get }
}

extension MapProtocol {
    func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
        NSLog("test")
    }
}

Solution

  • If i understood the question correctly, you want to avoid repeating "map related code" in different viewcontroller.

    In such a case, why not having a "data source" approach, create an additional class ViewControllerMapInjector or whichever name, and in the initialization pass both the vc and execute whatever method you prefer

    class ViewControllerMapInjector: MKMapViewDelegate {
        private let vc: UIViewController
        init(vc: UIViewController) {
           self.vc = vc
        }
    
        func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
            NSLog("test") // or use the vc as you want. 
        }
    }
    

    then in the viewController setup() just

    self.mapView.delegate = ViewControllerMapInjector(vc: self)

    Eventually you may need to apply additional patterns to the view controller to let additional methods be visible to the provider if needed (in the end the simplest solution is to go full OO and subclass the ViewController). It's a bit convoluted, but the alternative i can think of are not much simpler.