Try swift nyc james dempsey header

Flexible View Controller Interfaces With Swift 4

In this session, James takes a look at how to use Swift 4 existentials to create view controller interfaces that are easier to read and ready for seamless use with feature flags or other means of swapping between different view controller versions on the fly.


Introduction

My name is James Dempsey, and I will cover flexible View Controller interfaces with Swift 4. Over 20 years ago, I worked on technologies that became UIKit, Foundation, and Swift. During that time, I worked primarily in Objective-C, and recently I’ve had to learn to transition Swift aggressively.

I currently work at a startup that does personal cloud storage solutions - everything from the servers on the backend all the way through to the apps. I work on the iOS and Mac OS app.

Over two years ago at my company, the code base was 100% Objective-C. We started using Swift when Swift 2 was released and was 50% Swift by the time Swift 3 was available.

Issues with Protocols

I miss the .h file in Objective-C, as in, I miss the ability to see a summary of an interface. Currently, I have to look at the source file and go through the implementation to pick out what the interface is.

Get more development news like this

In Swift, a good way for expressing an interface is a Protocol. To note, to the people who say that Xcode automatically shows the interfaces in the Assistant Editor, the drawback to this is that it takes time, and I may not always be in Xcode.

Below is a pseudo-header that meets my requirements. I make it as an ‘AnyObject’ so that if we use that protocol, Swift will treat it by reference type as opposed to a value type.


protocol UserDetailVCProtocol: AnyObject {
  var currentUser: User { get set}
}

class UserDetailVC: UIViewController, UserDetailVCProtocol {
...
}

This change occurred in Swift 4 - this used to be marked with the keyword class, but now, AnyObject is used.

The advantage to this approach is that each concrete type has an interface summary. But, the disadvantage is that double bookkeeping becomes an issue. I have to add this keyword in the protocol in addition to the View Controller, and this may get out of sync. In Swift, the compiler will no longer enforce that a class conforms to a protocol. Moreover, this makes me require to subclass a class just to use a protocol.

Flexible Interfaces for ViewControllers

The example ViewController I will use is for user details to display account information. Here, I have a feature flag that allows for the new user interface to be shown or hidden.


let storyboard = UIStoryboard(name: "Main", bundle: nil)

if DebugSettings.shouldShowNewUserSettings {
  let vc = storyboard.instantiateViewController(withIdentifier: "userDetailsViewController) as! UserDetailViewControllerImpl
  vc.currentUser = currentUser
  present(vc, animated:true)
} else {
  let vc = storyboard.instantiateViewController(withIdentifier: "LegacyUserDetailsViewController) as! UserDetailViewControllerImpl
    vc.currentUser = currentUser
    present(vc, animated: true)
}

The different ViewControllers live the main storyboard with their own identifier. Notice because Swift is so strongly typed, I need to typecast what comes out of the instantiation. This means I can’t just reuse a variable if I pull out something of another type from the storyboard because it will complain that it’s not of the right type.

As a result, I have duplicated code, and I’m casting things everywhere. In Objective-C, you had the freedom to lie to the compiler, and cast objects to whatever I wanted.


NSString *vcid = useNew ? "NewVC" : "OldVC";
NewVC *vc = (NewVew *)[storyboard instantiateViewControllerWithIdentifier:vcid];

Swift does not allow this, and I came to the realization that I want to tell the compiler the truth without a lot of hassle, and I realized I should add a protocol:


protocol UserDetailViewControllerProtocol: AnyObject {
  var currentUser: User? { get set }
}

Then I make both the new and old ViewController classes conform to this protocol:


class UserDetailViewControllerImpl: UIViewController, UserDetailViewControllerProtocol

class LegacyUserDetailsViewControllerImpl: UIViewController, UserDetailViewControllerProtocol

Now, I can simply the present ViewController code like so:

let vcIndentifier = DebugSettings.shoudlShouldNewUserSettings ? "UserDetailsViewController" : "LegacyUserDetailsViewController"

let vc = storyboard.instantiateViewController(withIdentifier: vcIndentifier) as! UserDetailsViewControllerProtocol

vc.currentUser = currentUser
present((vc as! UIViewController), animated: true)

This was the best I could do until Swift 4.

Protocol Composition

Protocol Composition has been around since the beginning of Swift, and it allows for an ad-hoc way to take multiple protocols to make up a type on the fly, specifying that it will conform to a protocol. A canonical example of this behavior would be NSCopying and NSCoding.

Since Swift 4, Protocol Composition can include classes.

This now allows me to use the object as a protocol and as a ViewController, without having to do any additional typecasting:


let vc = storyboard.instantiateViewController(withIdentifier: vcIndentifier) as! UserDetailsViewControllerProtocol & UIViewController

vc.currentUser = currentUser
present(vc, animated: true)

I can moreover make a type alias:


typealias UserDetailViewController = UserDetailViewControllerProtocol & UIViewController

protocol UserDetailViewControllerProtocol: AnyObject {
  var currentUser: User? { get set }
}


Which allows me to further simplify it as follows:



let vc = storyboard.instantiateViewController(withIdentifier: vcIndentifier) as! UserDetailsViewController

vc.currentUser = currentUser
present(vc, animated: true)

I now have what is similar to a header, which has a low overhead at the top of the file. You could imagine having two protocols, one for internal use and one for public use.

Next Up: Advanced Swift #10: Everything You Ever Wanted to Know on Sequence & Collection

General link arrow white

About the content

This talk was delivered live in September 2017 at try! Swift NYC. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

James Dempsey

James Dempsey is a fifteen-year Apple veteran gone indie. At Apple, he was an evangelist, technical trainer, curriculum manager, and software engineer, working on Aperture, iOS, and OS X releases Leopard through Lion. James is currently on the technical staff at Upthere, working to build a cloud computer for humankind’s information. He is also the frontman of James Dempsey and the Breakpoints, a band that performs humorous original songs about technical topics. Their debut album Backtrace topped the iTunes comedy charts in the US, UK, and Canada, reaching #5 on the Billboard comedy album chart

4 design patterns for a RESTless mobile integration »

close