Delegates: Revisited
Not that long ago, I wrote an article on the delegate pattern and its use in iOS as simple way to communicate across controllers. After I wrote it I got a few questions about delegates and about container views. After the third response, I found myself writing the same thing over and over, so I decided just to write a small app and use it to explain myself. You can view the source here.
The app itself it pretty simple. It’s main view has a map and a table. The behaviors we’d like are when a row is tapped on the map should zoom in on its corresponding pin and if a pin is tapped the table should move to the corresponding row.

Now we can dump all of that in one controller, but it would be a bit of a mess. Instead we’ll divide it into three controllers. Two are pretty obvious, one for the mapView and the other for the tableView. A third one will also be required to route messages between controllers. It can also be used to change controllers associated with with the container views, but we won’t have need for that today.
To start open up for storyboard and delete whatever is there. Next drag out a View Controller and then drop a Container View into that view controller. Select the Container View and then click on the add new constraint button. Next, set the top, left, and right constraints to zero, uncheck Constrain to margins, and set the height to 250. Under the update frames dropdown select ‘Items of New Constraints’. The add constraint button at the bottom should say add 4 constraints, click it.

Next, drag a second container view underneath the first. Select it and then click on the add new constraint button. Set the top, left, right, and bottom constraints to zero and uncheck Constrain to margins. Under the update frames dropdown select ‘Items of New Constraints’. The add constraint button at the bottom should say add 4 constraints, click it.

Now, click on the segue between the top container view and the bottom. Select its attributes inspector and set its identity to ContainerToChildMapView. Do the same for the other segue, but change its identity to ContainerToChildTableView.
Ok, last steps for now. Drag a Map Kit View on the top container’s view. Set its top, left, right, and bottom constraints to 0. Next, drag a table view onto the bottom container’s view. Set its constraints to the same as the Map Kit View’s. Now, drag a table view cell onto the table view. Under the cell’s attributes inspector set it identity to locationCell. Finally, drag a label onto the table view cell. Click on the label and then select the align button. Check both horizontally in container and vertically in container and leave them at 0. Under the update frames dropdown select ‘Items of New Constraints’. Select add 2 constraints.

Ok, let’s write some code.
First, we’ll need a model. There may be a file called ViewController, delete it. Create a new group called model and in that group create a file called RestaurantLocation.swift. Have it subclass NSObject and implement the MKAnnotation protocol. MKMapView.addAnnotation(_:) takes objects of type MKAnnotation to make ‘pins’. Conforming to MKAnnotation requires your class to have three variables, title, subtitle, and coordinate.
It should like something like this:
Next let’s write a store. Create a new swift file in the model group called RestaurantStore.swift. The store will manage the RestaurantLocations and will be passed to the necessary controller. This pattern is called dependency injection and the basic idea is the lessen the responsibility that the dependent class has. It just needs to know that it needs a store. It makes testing simpler, too.
All the store will do is manage an array of RestaurantLocations:
Create a new group called controllers. Inside this group let create a new cocoa touch class called ChildMapViewController.swift and have it subclass UIViewController. We just need one variable for now,
@IBOutlet var mapView: MKMapView!Now, got back to the storyboard. Click on the top view controller, select its identity inspector and change the class to ChildMapViewController. Then drag from the Controller icon to the MKMapView and select MapView.
After this we’ll extend ChildMapViewController. Create a new file called ChildMapViewDelegate.swift. Create an extension of ChildMapViewController and have it implement MKMapViewDelegate. There are two method we’ll implement, mapView(_:ViewForAnnotation:) and mapView(_:didSelect:).
mapView(_:ViewForAnnotation:) is called everytime an annotation is added to the map, while mapView(_:didSelect:) is called whenever an MKAnnotation is tapped. We attempt to cast annotation as type RestaurantLocation. If the conversion is successful, we create an identifier and check to see if there is a reusable MkAnnotationView, if there is we set it’s annotation to the one we’re adding and return it, or if there isn’t one we create a new one with the annotation and make sure it can display its text bubble or callout.
Ok, let’s go back to ChildViewMapController.swift. We need to add a variable for the RestaurantStore, define how the mapView should appear in viewDidLoad(), and add a few functions as well.
zoom(coordinate:) simply zooms in on an area centered around its coordinate value.
Let’s deal with the tableView.
First create a new section called views and create a new Cocoa Touch Class called LocationTableViewCell and have it inherit from UITableViewCell. It just needs a single variable,
@IBOutlet var locationNameLabel: UILabel!Go to the storyboard and set the class of the tableViewCell to LocationTableViewCell. Drag from the label to the controller and select locationNameLabel.
In the controller group create a swift file named ChildTableDataSource. It should inherit from NSObject and implement UITableViewDataSource. It just needs the store, and implementation of tableView(_:numberOfRowsInSection:) & tableView(_:cellForRowAt:)
Now create a new Cocoa touch class name ChildTableViewController. Have it inherit from UIViewController. It also needs only three variables an instance of ChildTableDataSource, an optional for restaurantStore and:
@IBOutlet var tableView: UITableView!Link it with the storyboard to the tableview like you did with mapView. Make sure you’ve set the class to ChildTableViewController. In viewDidLoad() set the tableview datasource to the dataSource variable you created and the store to restaurantStore.
Now, lets create the parentView. Create a new Cocoa Touch class inside the controller group called ParentContainerController. Have it inherit from UIViewController. It need just one variable and that’s for the restaurantStore. We’ll use prepare(for:sender:) to set the the containers.
In the storyboard set the class of the parentViewController to ParentContainerController.
Finally, let’s some add some entries to our store, add these lines to RestaurantStore.swift’s init():
self.addLocation(name: “Takorea”, icbm: (33.776892, -84.383149))
self.addLocation(name: “Ru Sans”, icbm: (33.846957, -84.372463))
self.addLocation(name: “Sushi Huku”, icbm: (33.905360, -84.428686))
self.addLocation(name: “Escorpion”, icbm: (33.776495, -84.384844))
self.addLocation(name: “Manuel’s Tavern”,
icbm: (33.770733, -84.352757))
self.addLocation(name: “Battle and Brew”,
icbm: (33.917155, -84.380927))
self.addLocation(name: “Mirko Pasta”,
icbm: (33.870006, -84.380633))
self.addLocation(name: “Superica”,
icbm: (33.859933, -84.381148))
self.addLocation(name: “Atlanta Fish Market”,
icbm: (33.836689, -84.378747))
self.addLocation(name: “Cafe Sunflower”,
icbm: (33.812618, -84.393207))Build and run. It should look like this

At the moment the app doesn’t do anything. You can move around the map and you can move up and down the tableView, but so what? We want some action! We’ll get it by implementing mapView(_:didSelect:) and tableView(_:didSelectRowAt:).
Open up ChildMapViewDelegate.swift and implement mapView(_:didSelect:). This function is pretty simple. You’ll try and cast the view’s annotation as a restaurantLocation and if successful you’ll call zoom on it’s coordinates.
Next go back to ChildTableViewController.swift. We’ll implement tableView(_:didSelectRowAt:) here, but we also need ChildTableViewController to implement UITableViewDelegate. We just call scrollToRow(at:at:animated:).
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
self.tableView.scrollToRow(at: indexPath,
at: .top,
animated: true)
}Ok, now build and run. The app will zoom on pins and scroll down to tables. You’ll note if the cell is particularly far down the list, the app can’t scroll all the way down to it. We’ll add some padding to avoid this.
First lets add an enum to RestaurantLocation so we can check if it has something to display or not. Modify the following in RestaurantLocation.swift.
Next modify tableView(_:cellForRowAt:) to use the enum to set the content of the cell along with the user interaction state of the cell.
Ok, now we need to edit addLocation(name:icbm) to addLocation(name:icbm:contentStatus:) and modify all of the calls we made to it as well. After that we’ll add a few blank entries to act as padding:
Build and run. You’ll see that when you tap a cell it will scroll down to it and you’ll see blank entries beneath it. You can also define a new cell class and just use that instead, but this was simpler for explanatory purposes.
Now to connect our controllers.
In ChildMapViewController.swift create a protocol called SelectAnnotationDelegate with a single function called pickAnnotation(_:) that should call zoom(coordinate:) and select the pin. Make sure ChildMapViewController implements SelectAnnotationDelegate.
In ChildTableViewController.swift create a protocol called SelectedRowDelegate with a single function called pickRow(_:) that should call selectRow(at:animated:scrollPosition:) to select the row corresponding to the argument given to pickRow(_:). Make sure ChildTableViewController implements SelectRowDelegate.
Open up ParentContainerController.swift and create a new protocol called TappedDelegate with two methods annotationTapped(_:) & cellTapped(_:). You’ll also need two optional variables for the delegates of the childViews and they should be set using prepare(for:sender:).
Next, we’ll modify the behavior of mapView(_:didSelect:) and tableView(_:didSelectRowAt:).
In ChildMapViewController add an optional variable called tappedDelegate of type TappedDelegate and in ChildMapViewDelegate modify mapView(_:didSelect:) to check if tappedDelegate has been set and if so to call annotationTapped(_:)
In ChildTableViewController add an optional variable called tappedDelegate of type TappedDelegate and in tableView(_:didSelectRowAt:) add the following line:
tappedDelegate?.cellTapped(indexPath.row)Finally, in ParentViewController set the tappedDelegate variable for each childView to self and there you have it. Now the app will work. You can communicate between both controllers using parentView and delegates. It’s much easier to follow than NotificationCenter as it simple uses methods implement in that class to do it’s work.

You can go through the complete source here:
https://github.com/shefHauwanga/mapTableDelegateExample/tree/master/mapToListOne
