I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
Pick a Delegate… Any Delegate… On Clean View Controllers in Swift
The delegation pattern is ubiquitous in iOS development – the pattern is a “core competency” for developing in Cocoa, and if you program with the iOS SDK for any length of time and you’ll end up writing some code that resembles someInstance.delegate = someDelegate
.
One of the toughest things that I’ve experienced is choosing what someDelegate
is. All too often, a View Controller ends up being assigned the responsibility of being the delegate for everything in its hierarchy. My question is: Is there a cleaner way?
Let’s pick up on the example I proposed in my recent post about sending e-mails in-app. For “quick and dirty” pragmatism, I just crammed everything into the View Controller with the promise of coming back and (hopefully) showing a cleaner way. Here is a quick link to example posed before if you’d like to review it before proceeding.
What if…
What if we could make some adjustments so that the View Controller was trimmed down to the example on the right (click for larger view):
I’ve created a fully-working example on GitHub if you’d like to download it and play.
So the question at hand: Is the class labeled “Clean Example” preferable (ie, better)? First, let’s explore how I accomplished the “clean” View Controller. Then I’ll tip my hand on and share what I like about this approach…
EmailComposer
In order to accomplish the self-declared Clean View Controller above, I placed all of the configuration processes and the delegate method for the MFMailComposeViewController
in a new class called EmailComposer
. It should look familiar if you recall the previous example:
1import Foundation
2import MessageUI
3
4class EmailComposer: NSObject, MFMailComposeViewControllerDelegate {
5 // Did this in order to mitigate needing to import MessageUI in my View Controller
6 func canSendMail() -> Bool {
7 return MFMailComposeViewController.canSendMail()
8 }
9
10 func configuredMailComposeViewController() -> MFMailComposeViewController {
11 let mailComposerVC = MFMailComposeViewController()
12 mailComposerVC.mailComposeDelegate = self
13
14 mailComposerVC.setToRecipients(["[email protected]"])
15 mailComposerVC.setSubject("Sending you an in-app e-mail...")
16 mailComposerVC.setMessageBody("Sending e-mail in-app is not so bad!", isHTML: false)
17
18 return mailComposerVC
19 }
20
21 // MARK: MFMailComposeViewControllerDelegate Method
22 func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
23 controller.dismissViewControllerAnimated(true, completion: nil)
24 }
25}
So literally, the only thing I did is
- Cut the function definitions for
configuredMailComposeViewController
, and theMFMailComposeViewControllerDelegate
method. - Paste them into the new
EmailComposer
class, which inherits fromNSObject
(a requirement for this particular delegate protocol’s conformity), and conforms to theMFMailComposeViewControllerDelegate
protocol. - Adjust my View Controller to create an instance of
EmailComposer
, obtain a configuredMFMailComposeViewController
, and present it whenever the user taps on a button in my UI.
Conclusions
- The View Controller in its final version is focused. It’s primary concern is presentation and handling of user interaction with the View itself, rather than needing to worry with configuring an
MFMailComposeViewController
and its delegate callback. EmailComposer
is less of a hassle to test, in the sense that I no longer need to instantiate a View Controller in myXCTestCase
class just to test myMFMailComposeViewController
stuff. It’s a real pain to test an actual View Controller instance, so I like that I can easily create an instance ofEmailComposer
and test away without the bulk.- No need to import MessageUI in my View Controller.
All in all, this is the cleanest, simplest, most balanced solution (that I could think of) to factoring out some logic to another class, so as to make my View Controller as clean as possible.
The goal was to make sure the appropriate responsibilities are assigned to the right classes. Presentation logic is all in the View Controller. Configuration and delegate callback implementation is done in EmailComposer
.
I’m thinking through applying this same idea to other more complicated examples (UITableViewDataSource and UITableViewDelegate come to mind), and I think it would do us a lot of good to strategize on how to avoid making the View Controller the “catch-all” delegate / data source class for everything that’s currently on the screen_._
Hopefully these thoughts spark some ideas in the Swift community. This post has already been revised slightly based on feedback that I’ve received from folks on Twitter. If you have additional ideas in regards to choosing the right delegate, holler my way! I’d love to hear from you.
Thanks for reading.