Slide 1

Slide 1 text

STUPID ENUM TRICKS IN SWIFT AND KOTLIN WE ARE DEVELOPERS | VIENNA, AUSTRIA | MAY 2018 BAKKENBAECK.COM | JUSTHUM.COM | DESIGNATEDNERD.COM BY ELLEN SHAPIRO | @DESIGNATEDNERD

Slide 2

Slide 2 text

PREFACE: A NOTE ON PRONUNCIATION

Slide 3

Slide 3 text

"EE-NUMB" VS. "EE-NOOM"

Slide 4

Slide 4 text

"EE-NUMB" VS. "EE-NOOM" ⬆ (I PRONOUNCE IT THIS WAY)

Slide 5

Slide 5 text

"EE-NUMB" VS. "EE-NOOM" (THIS WAY IS ALSO FINE)

Slide 6

Slide 6 text

GIF

Slide 7

Slide 7 text

GIF

Slide 8

Slide 8 text

WHAT IS AN ENUM?

Slide 9

Slide 9 text

A LIST OF VALUES THAT ARE POSSIBLE

Slide 10

Slide 10 text

OPTION SET

Slide 11

Slide 11 text

OPTION SET

Slide 12

Slide 12 text

POSSIBLE IT COULD BE ANY ONE OF THESE THINGS IN THE LIST

Slide 13

Slide 13 text

SWIFT enum ColorName: String { case red case orange case yellow case green case blue case violet }

Slide 14

Slide 14 text

IT COULD ALSO BE SOMETHING TOTALLY DIFFERENT

Slide 15

Slide 15 text

SWIFT func colorForName(_ name: String) -> UIColor? { switch name { case ColorName.red.rawValue: return UIColor.red case ColorName.orange.rawValue: return UIColor.orange // same for other ColorName cases default: return nil }

Slide 16

Slide 16 text

EXHAUSTIVE ENUMS

Slide 17

Slide 17 text

EXAUSTIVE BUT THESE ARE THE ONLY THINGS WHICH ARE POSSIBLE

Slide 18

Slide 18 text

SWIFT enum TrafficLightColor { case red case yellow case green }

Slide 19

Slide 19 text

LIMIT YOUR POSSIBILITIES

Slide 20

Slide 20 text

LIMIT YOUR RUNTIME ERRORS

Slide 21

Slide 21 text

SWIFT STANDARD LIBRARY public enum ComparisonResult : Int { case orderedAscending case orderedSame case orderedDescending }

Slide 22

Slide 22 text

KOTLIN STANDARD LIBRARY public enum ClassKind { CLASS, INTERFACE, ENUM_CLASS, ENUM_ENTRY, ANNOTATION_CLASS, OBJECT; }

Slide 23

Slide 23 text

HOW DO I CREATE MY OWN ENUM?

Slide 24

Slide 24 text

SWIFT enum TrafficLightColor { case red case yellow case green }

Slide 25

Slide 25 text

KOTLIN enum class

Slide 26

Slide 26 text

KOTLIN enum class TrafficLightColor { Red, Yellow, Green; }

Slide 27

Slide 27 text

HOW DO I USE ENUMS?

Slide 28

Slide 28 text

switch / when

Slide 29

Slide 29 text

SWIFT var currentColor: TrafficLightColor = .red switch currentColor { case .red: print("STOP RIGHT THERE") case .yellow: print("Whoa, slow down there, buddy.") case .green: print("Go, go, go!") }

Slide 30

Slide 30 text

SWIFT var currentColor: TrafficLightColor = .red switch currentColor { case .red: print("STOP RIGHT THERE") case .green: print("Go, go, go!") // Error: Unhandled case! }

Slide 31

Slide 31 text

SWIFT var currentColor: TrafficLightColor = .red switch currentColor { case .red: print("STOP RIGHT THERE") case .yellow, .green: print("Go, go, go!") }

Slide 32

Slide 32 text

KOTLIN var currentColor = TrafficLightColor.Red when (currentColor) { TrafficLightColor.Red -> println("STOP RIGHT THERE") TrafficLightColor.Yellow -> println("Whoa, slow down there, buddy.") TrafficLightColor.Green -> println("Go, go, go!") }

Slide 33

Slide 33 text

WHAT DO I GET FOR FREE WITH ENUMS?

Slide 34

Slide 34 text

KOTLIN: A LOT!

Slide 35

Slide 35 text

KOTLIN Instance Gets You Example instance.name String value of case's written name Days.THURSDAY.name is "THURSDAY", Days.Thursday.name is "Thursday" instance.ordinal Index of case in list of cases

Slide 36

Slide 36 text

KOTLIN Type Gets You Type.valueOf(string: String) Enum value of string, or null Type.values() Generated list of all values in the enum class

Slide 37

Slide 37 text

SWIFT: IT DEPENDS

Slide 38

Slide 38 text

BY DEFAULT: NOT MUCH

Slide 39

Slide 39 text

RawRepresentable

Slide 40

Slide 40 text

SWIFT enum SettingsSection { case profile case contact case legalese case logout }

Slide 41

Slide 41 text

SWIFT enum SettingsSection: Int { case profile case contact case legalese case logout }

Slide 42

Slide 42 text

SWIFT enum SettingsSection: Int { case profile // 0 case contact // 1 case legalese // 2 case logout // 3 }

Slide 43

Slide 43 text

SWIFT enum SettingsSection: Int { case profile // 0 case legalese // 1 case contact // 2 case logout // 3 }

Slide 44

Slide 44 text

SWIFT guard let section = SettingsSection(rawValue: 10) else { assertionFailure("Section 10 does not exist!") }

Slide 45

Slide 45 text

SWIFT enum JSONKey: String { case user_name case email_address case latitude case longitude }

Slide 46

Slide 46 text

SWIFT enum JSONKey: String { case user_name case email_address case latitude = "lat" case longitude = "long" }

Slide 47

Slide 47 text

SWIFT Codable class User: Codable { let userName: String let email: String let latitude: Double let longitude: Double enum CodingKeys: String, CodingKey { case userName = "user_name" case email = "email_address" case latitude = "lat" case longitude = "long" } }

Slide 48

Slide 48 text

SWIFT 4.2: CaseIterable

Slide 49

Slide 49 text

SWIFT

Slide 50

Slide 50 text

SWIFT enum SettingsSection: Int, CaseIterable { case profile case legalese case contact case logout }

Slide 51

Slide 51 text

SWIFT enum SettingsSection: Int, CaseIterable { case profile case legalese case contact case logout } // Automatically generated: static var allCases: [SettingsSection]

Slide 52

Slide 52 text

SWIFT func numberOfSectionsInTableView(_ tableView: UITableView) -> Int { return SettingsSection.allCases.count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String { let section = SettingsSection.allCases[section] return section.localizedTitle }

Slide 53

Slide 53 text

WHAT CAN I ADD TO ENUMS?

Slide 54

Slide 54 text

COMPUTED PROPERTIES

Slide 55

Slide 55 text

SWIFT enum LandingScreenButton { case signIn case signUp case viewTerms }

Slide 56

Slide 56 text

SWIFT enum LandingScreenButton { case signIn case signUp case viewTerms var localizedTitle: String { switch self { case .signIn: return NSLocalizedString("Sign In", "Sign in button title") case .signUp: return NSLocalizedString("Sign Up", "Sign up button title") case .viewTerms: return NSLocalizedString("View Terms & Conditions", "Title for button to view legalese") } }

Slide 57

Slide 57 text

SWIFT public enum LandingScreenButton { case signIn case signUp case viewTerms public var localizedTitle: String { switch self { case .signIn: return NSLocalizedString("Sign In", "Sign in button title") case .signUp: return NSLocalizedString("Sign Up", "Sign up button title") case .viewTerms: return NSLocalizedString("View Terms & Conditions", "Title for button to view legalese") } }

Slide 58

Slide 58 text

UI TESTING

Slide 59

Slide 59 text

SWIFT / XCUI

Slide 60

Slide 60 text

KOTLIN / ESPRESSO

Slide 61

Slide 61 text

ASSET HANDLING [IOS]

Slide 62

Slide 62 text

ASSET CATALOGS

Slide 63

Slide 63 text

SWIFT / IOS: BEFORE guard let image = UIImage(named: "cinnamon_rolls") else { return }

Slide 64

Slide 64 text

SWIFT / IOS: BEFORE guard let image = UIImage(named: "cinnamon_rolls") else { return }

Slide 65

Slide 65 text

SWIFT / IOS: BEFORE guard let image = UIImage(named: "cinamon_rolls") else { return }

Slide 66

Slide 66 text

SWIFT / IOS: BEFORE guard let image = UIImage(named: "cinamon_rolls") else { return }

Slide 67

Slide 67 text

SWIFT / IOS: AFTER public enum Asset: String { case cinnamon_rolls case innocent case no case snack case window var image: UIImage { return UIImage(named: self.rawValue)! } }

Slide 68

Slide 68 text

SWIFT / IOS: TESTS! func testAllAssetsAreThere() { XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) }

Slide 69

Slide 69 text

SWIFT / IOS: TESTS! func testAllAssetsAreThere() { XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) XCTAssertNotNil(UIImage(named: Asset.cinnamon_rolls.rawValue)) } !

Slide 70

Slide 70 text

SWIFT / IOS: TESTS! func testAllAssetsAreThere() { for asset in Asset.allCases { XCTassertNotNil(UIImage(named: asset.rawValue), "Image for \(asset.rawValue) was nil!") } }

Slide 71

Slide 71 text

SWIFT / IOS: TESTS! func testAllAssetsAreThere() { for asset in Asset.allCases { XCTassertNotNil(UIImage(named: asset.rawValue), "Image for \(asset.rawValue) was nil!") } } !!!!!!!

Slide 72

Slide 72 text

USE CODE GENERATION

Slide 73

Slide 73 text

USE CODE GENERATION (OR THIS CAN GET SUPER-TEDIOUS ON BIG PROJECTS)

Slide 74

Slide 74 text

SWIFT CODE GENERATION > https://github.com/SwiftGen/SwiftGen > https://github.com/krzysztofzablocki/Sourcery > https://github.com/stencilproject/Stencil

Slide 75

Slide 75 text

BENDING ENUMS TO YOUR WILL

Slide 76

Slide 76 text

COMBINING ENUMS + PROTOCOLS / INTERFACES

Slide 77

Slide 77 text

SWIFT: BEFORE @IBAction private func signIn() { guard self.validates() else { return } performSegue(withIdentifier: "toCatsLoggedIn", sender: nil) }

Slide 78

Slide 78 text

SWIFT: THE ENUM enum MainStoryboardSegue: String { case toSignIn case toSignUp case toTerms case toCats case toCatsLoggedIn }

Slide 79

Slide 79 text

SWIFT: THE PROTOCOL protocol SeguePerforming { var rawValue: String { get } }

Slide 80

Slide 80 text

SIDE NOTE: LEARN TO CHEAT

Slide 81

Slide 81 text

SWIFT: THE PROTOCOL WITH A GENERIC EXTENSION FUNCTION protocol SeguePerforming { var rawValue: String { get } } extension UIViewController { func perform(segue: T, sender: Any? = nil) { performSegue(withIdentifier: segue.rawValue, sender: sender) } }

Slide 82

Slide 82 text

SWIFT: THE ENUM, REVISED enum MainStoryboardSegue: String, SeguePerforming { case toSignIn case toSignUp case toTerms case toCats case toCatsLoggedIn }

Slide 83

Slide 83 text

SWIFT: AFTER @IBAction private func signIn() { guard self.validates() else { return } self.perform(MainStoryboardSegue.toCats) }

Slide 84

Slide 84 text

ASSOCIATED OBJECTS

Slide 85

Slide 85 text

SWIFT enum DownloadState { case notStarted, case downloading(let progress: Float) case success(let data: Data) case error(let error: Error?) }

Slide 86

Slide 86 text

KOTLIN enum class CreditCard(val prefix: String) { Visa("4"), Mastercard("5"), Amex("3"); }

Slide 87

Slide 87 text

KOTLIN sealed class

Slide 88

Slide 88 text

KOTLIN sealed class DownloadStateWithInfo { }

Slide 89

Slide 89 text

KOTLIN sealed class DownloadStateWithInfo { class NotStarted: DownloadStateWithInfo() }

Slide 90

Slide 90 text

KOTLIN sealed class DownloadStateWithInfo { class NotStarted: DownloadStateWithInfo() class Downloading(val progress: Float): DownloadStateWithInfo() }

Slide 91

Slide 91 text

KOTLIN sealed class DownloadStateWithInfo { class NotStarted: DownloadStateWithInfo() class Downloading(val progress: Float): DownloadStateWithInfo() class Succeeded(val data: ByteArray): DownloadStateWithInfo() }

Slide 92

Slide 92 text

KOTLIN sealed class DownloadStateWithInfo { class NotStarted: DownloadStateWithInfo() class Downloading(val progress: Float): DownloadStateWithInfo() class Succeeded(val data: ByteArray): DownloadStateWithInfo() class Failed(val error: Error): DownloadStateWithInfo() }

Slide 93

Slide 93 text

BLACK MAGIC WITH ✨ GENERICS

Slide 94

Slide 94 text

KOTLIN enum class KeyboardType { QWERTY, ABCDEF, QWERTZ, AZERTY; }

Slide 95

Slide 95 text

KOTLIN inline fun > T.next(): T { val currentIndex = this.ordinal val nextIndex = currentIndex + 1 val allValues = enumValues() return if (nextIndex >= allValues.size) { allValues[0] } else { allValues[nextIndex] } }

Slide 96

Slide 96 text

KOTLIN inline fun > T.next(): T { // val currentIndex = this.ordinal // val nextIndex = currentIndex + 1 // val allValues = enumValues() // return if (nextIndex >= allValues.size) { // allValues[0] // } else { // allValues[nextIndex] // } }

Slide 97

Slide 97 text

KOTLIN inline fun > T.next(): T { // val currentIndex = this.ordinal // val nextIndex = currentIndex + 1 val allValues = enumValues() // return if (nextIndex >= allValues.size) { // allValues[0] // } else { // allValues[nextIndex] // } }

Slide 98

Slide 98 text

KOTLIN inline fun > T.next(): T { // val currentIndex = this.ordinal // val nextIndex = currentIndex + 1 // val allValues = enumValues() return if (nextIndex >= allValues.size) { allValues[0] } else { allValues[nextIndex] } }

Slide 99

Slide 99 text

KOTLIN KeyboardType.QWERTY.next() // returns KeyboardType.ABCDEF KeyboardType.ABCDEF.next() // returns KeyboardType.QWERTZ KeyboardType.QWERTZ.next() // returns KeyboardType.AZERTY KeyboardType.AZERTY.next() // returns KeyboardType.QWERTY

Slide 100

Slide 100 text

KOTLIN KeyboardType.QWERTY.next() KeyboardType.ABCDEF.next() KeyboardType.QWERTZ.next() KeyboardType.AZERTY.next()

Slide 101

Slide 101 text

THE LIMITS OF ENUM AWESOMESAUCE

Slide 102

Slide 102 text

ASSOCIATED OBJECTS = AWESOME LISTABILITY = AWESOME LISTABILITY + ASSOCIATED OBJECTS =

Slide 103

Slide 103 text

KOTLIN sealed class DownloadStateWithInfo { class NotStarted: DownloadStateWithInfo() class Downloading(val progress: Float): DownloadStateWithInfo() class Succeeded(val data: ByteArray): DownloadStateWithInfo() class Failed(val error: Error): DownloadStateWithInfo() }

Slide 104

Slide 104 text

KOTLIN DownloadStateWithInfo.NotStarted().next()

Slide 105

Slide 105 text

KOTLIN DownloadStateWithInfo.NotStarted().next() !"!

Slide 106

Slide 106 text

A LITTLE EASY TO GET CARRIED AWAY

Slide 107

Slide 107 text

SWIFT: GOOD enum LandingScreenButton { // Stuff we saw before var localizedTitle: String { switch self { case .signIn: return NSLocalizedString("Sign In", "Sign in button title") case .signUp: return NSLocalizedString("Sign Up", "Sign up button title") case .viewTerms: return NSLocalizedString("View Terms & Conditions", "Title for button to view legalese") } }

Slide 108

Slide 108 text

SWIFT: OVER THE TOP enum LandingScreenButton { // Stuff we saw before func handleClick(in viewController: UIViewController) { switch self { case .signIn: viewController.navigationController? .pushViewController(SignInViewController(), animated: true) case .signUp: viewController.navigationController? .pushViewController(RegistrationViewController(), animated: true) case .viewTerms: viewController.present(LegaleseViewController(), animated: true) } } }

Slide 109

Slide 109 text

IS THIS FUNCTIONALITY INHERENTLY RELATED TO THE THING I'M REPRESENTING?

Slide 110

Slide 110 text

SEPERATION OF CONCERNS STILL APPLIES!

Slide 111

Slide 111 text

OBLIGATORY SUMMARY SLIDE

Slide 112

Slide 112 text

OBLIGATORY SUMMARY SLIDE > Enums are a great way to represent distinct state

Slide 113

Slide 113 text

OBLIGATORY SUMMARY SLIDE > Enums are a great way to represent distinct state > Limit your cases, limit your bugs

Slide 114

Slide 114 text

OBLIGATORY SUMMARY SLIDE > Enums are a great way to represent distinct state > Limit your cases, limit your bugs > Value determined by the current case -> computed var

Slide 115

Slide 115 text

OBLIGATORY SUMMARY SLIDE > Enums are a great way to represent distinct state > Limit your cases, limit your bugs > Value determined by the current case -> computed var > Generated enums help reduce stringly-typed code

Slide 116

Slide 116 text

OBLIGATORY SUMMARY SLIDE > Enums are a great way to represent distinct state > Limit your cases, limit your bugs > Value determined by the current case -> computed var > Generated enums help reduce stringly-typed code > Don't forget about separation of concerns

Slide 117

Slide 117 text

CODE EXAMPLES IN SWIFT AND KOTLIN! https://github.com/designatednerd/ StupidEnumTricks

Slide 118

Slide 118 text

LINKS! > The Swift book's section on Enums: https://developer.apple.com/library/ content/documentation/Swift/Conceptual/ Swift_Programming_Language/ Enumerations.html > Kotlin Enum Class documentation: https://kotlinlang.org/docs/reference/ enum-classes.html