Slide 1

Slide 1 text

Enumerating all of the reasons why enums are AWESOME! By: Conrad Stoll _

Slide 2

Slide 2 text

Enumerating All The Things 0 0 https://www.caseyliss.com/2016/2/27/swift-enums

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

State Driven Development

Slide 5

Slide 5 text

enum

Slide 6

Slide 6 text

Bool

Slide 7

Slide 7 text

var instanceVariable : Bool

Slide 8

Slide 8 text

How often exactly? // Regex for boolean state // Match Bool typed vars that aren't computed properties var.*:.*Bool(\n|.*=.*) var.*=\s?(true|false)

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

var active : Bool var contentLoaded : Bool var dataLoaded : Bool var editing : Bool var enabled : Bool var expanded : Bool var handlingTouches : Bool var hasTakenUserAction : Bool var isCompleted : Bool var isRunning : Bool var lastTouchedButtonSide : Bool var lastTouchedRightSide : Bool var notified : Bool var previewEnded : Bool var saved : Bool var started : Bool

Slide 11

Slide 11 text

Example

Slide 12

Slide 12 text

Starting a Workout func startWorkout() { // ... }

Slide 13

Slide 13 text

One Variable var hasLoggedIn : Bool = false func startWorkout() { guard hasLoggedIn else { return } // ... }

Slide 14

Slide 14 text

Two Variables var hasLoggedIn : Bool = false var workoutInProgress : Bool = false func startWorkout() { guard hasLoggedIn && workoutInProgress == false else { return } workoutInProgress = true // ... }

Slide 15

Slide 15 text

The Tangled Web of State var hasLoggedIn : Bool = false var workoutInProgress : Bool = false var savingWorkout : Bool = false func stopWorkout() { workoutInProgress = false savingWorkout = true saveWorkout { (success) in self.savingWorkout = false } }

Slide 16

Slide 16 text

The Tangled Web of State var hasLoggedIn : Bool = false var workoutInProgress : Bool = false var savingWorkout : Bool = false func startWorkout() { guard hasLoggedIn && workoutInProgress == false && !savingWorkout else { return } workoutInProgress = true // ... }

Slide 17

Slide 17 text

The user CANNOT start a workout: - Hasn't logged into the app yet - In the middle of a workout - Saving the last workout

Slide 18

Slide 18 text

The user CAN start a workout: - Ready to start workout

Slide 19

Slide 19 text

Valid Boolean States User Not Logged In, No Workout, Not Saving User Logged In, No Workout, Not Saving User Logged In, Workout In Progress, Workout Not Saving User Logged In, No Workout, Workout Is Saving var hasLoggedIn : Bool var workoutInProgress : Bool var savingWorkout : Bool

Slide 20

Slide 20 text

Invalid Boolean States User Not Logged In, Workout In Progress, Not Saving User Not Logged In, Workout In Progress, Workout Is Saving User Not Logged In, No Workout, Workout Is Saving User Logged In, Workout In Progress, Workout Is Saving var hasLoggedIn : Bool var workoutInProgress : Bool var savingWorkout : Bool

Slide 21

Slide 21 text

Perfect case for an enum

Slide 22

Slide 22 text

- Hasn't logged into the app yet - In the middle of a workout - Saving the last workout - Ready to start workout

Slide 23

Slide 23 text

enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart }

Slide 24

Slide 24 text

Enums make invalid states impossible to represent!

Slide 25

Slide 25 text

Converting to Enums var hasLoggedIn : Bool = false var workoutInProgress : Bool = false var savingWorkout : Bool = false func startWorkout() { guard hasLoggedIn && workoutInProgress == false && !savingWorkout else { return } workoutInProgress = true // ... }

Slide 26

Slide 26 text

var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn func startWorkout() { // Equivalent guard .readyToStart == lifecycle else { return } // Pattern Matching guard case .readyToStart = lifecycle else { return } lifecycle = .workoutInProgress // ... }

Slide 27

Slide 27 text

Extending enums with variables and functions

Slide 28

Slide 28 text

enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart var canStartWorkout : Bool { get { switch self { case .notLoggedIn: return false case .workoutInProgress: return false case .savingLastWorkout: return false case .readyToStart: return true } } } }

Slide 29

Slide 29 text

Simplified for multiple cases enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart var canStartWorkout : Bool { get { switch self { case .notLoggedIn, .workoutInProgress, .savingLastWorkout: return false case .readyToStart: return true } } } }

Slide 30

Slide 30 text

Simplified using fallthrough enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart var canStartWorkout : Bool { get { switch self { case .notLoggedIn: fallthrough case .workoutInProgress: fallthrough case .savingLastWorkout: return false case .readyToStart: return true } } } }

Slide 31

Slide 31 text

default Danger! enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart var canStartWorkout : Bool { get { switch self { case .readyToStart: return true default: return false } } } }

Slide 32

Slide 32 text

Enums describing the behavior var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn func startWorkout() { guard lifecycle.canStartWorkout else { return } lifecycle = .workoutInProgress // ... }

Slide 33

Slide 33 text

Enums describing the behavior var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn func stopWorkout() { lifecycle = .savingLastWorkout saveWorkout { (success) in self.lifecycle = .readyToStart } }

Slide 34

Slide 34 text

State Driven Development enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart var canStartWorkout : Bool var actionTitle : String var backgroundColor : UIColor var barButtonItems : [UIBarButtonItem] }

Slide 35

Slide 35 text

Title for Buttons enum WorkoutApplicationLifecycle { // ... var actionTitle : Bool { get { switch self { case .notLoggedIn: return "Sign In" case .workoutInProgress: return "End Workout" case .savingLastWorkout: return "Saving Workout" case .readyToStart: return "Start Workout" } } } }

Slide 36

Slide 36 text

Simple Component Interfaces @IBAction func didTapActionButton() { lifecycle.actionTaken(from: self) }

Slide 37

Slide 37 text

Actions for Buttons enum WorkoutApplicationLifecycle { // ... func actionTaken(from viewController : WorkoutViewController) { switch self { case .notLoggedIn: viewController.login() case .workoutInProgress: viewController.stopWorkout() viewController.saveWorkout() case .savingLastWorkout: break case .readyToStart: viewController.startWorkout() } } }

Slide 38

Slide 38 text

Adding Features enum WorkoutApplicationLifecycle { // ... case restoringWorkout var canStartWorkout : Bool { case .restoringWorkout: return false } var actionTitle : Bool { case .restoringWorkout: return "Restoring Workout" } func actionTaken(from viewController : WorkoutViewController) { case .restoringWorkout: break } }

Slide 39

Slide 39 text

Associated Values

Slide 40

Slide 40 text

Defining Associated Values enum WorkoutApplicationLifecycle { // ... case workoutInProgress(current : Workout) }

Slide 41

Slide 41 text

Accessing Associated Values switch self { case .workoutInProgress(let workout): break } if case let .workoutInProgress(workout) = lifecycle { // ... }

Slide 42

Slide 42 text

if case .workoutInProgress = lifecycle { } if case let .workoutInProgress(_) = lifecycle { // ... } if case let .workoutInProgress(current: workout) = lifecycle { // ... } if case .workoutInProgress(let workout) = lifecycle { // ... } if case let .workoutInProgress(workout, user) = lifecycle { // ... }

Slide 43

Slide 43 text

switch self { case .workoutInProgress: // ... case .workoutInProgress(_): // ... case let .workoutInProgress(current: workout): // ... case let .workoutInProgress(workout, user): // ... case .workoutInProgress(let workout): // ... }

Slide 44

Slide 44 text

Recommended Syntax if case let .workoutInProgress(current: workout) = lifecycle { // ... } switch self { case let .workoutInProgress(current: workout): // ... }

Slide 45

Slide 45 text

Using Associated Values enum WorkoutApplicationLifecycle { // ... case workoutInProgress(current: Workout) func actionTaken(from viewController : WorkoutViewController) { switch self { case let .workoutInProgress(current: workout): viewController.stopWorkout() viewController.saveWorkout(current: workout) } } }

Slide 46

Slide 46 text

Using Associated Values enum WorkoutApplicationLifecycle { // ... case workoutInProgress(current: Workout) var navigationTitle : String { switch self { case let .workoutInProgress(current: workout): return workout.type.activityName } } }

Slide 47

Slide 47 text

Setting Associated Values var lifecycle : WorkoutApplicationLifecycle = .notLoggedIn func startWorkout() { guard lifecycle.canStartWorkout else { return } var newWorkout : Workout // ... lifecycle = .workoutInProgress(current: workout) }

Slide 48

Slide 48 text

Table View Cell Configurations

Slide 49

Slide 49 text

enum TableViewCellConfiguration { case simpleCell(title : String) case detailedCell(title : String, description : String) case imageCell(title : String, image : UIImage) case accessoryCell(title : String, image : UIImage, accessory : UITableViewCellAccessoryType) var titleText : String var descriptionText : String? var image : UIImage? var accessoryType : UITableViewCellAccessoryType }

Slide 50

Slide 50 text

enum TableViewCellConfiguration { case workoutCell(workout : Workout) case userProfileCell(loggedIn : User) case exerciseCell(exercise : Exercise) case trainingPlanCell(plan : TrainingPlan) var titleText : String var descriptionText : String? var image : UIImage? var accessoryType : UITableViewCellAccessoryType }

Slide 51

Slide 51 text

class CustomTableViewCell : UITableViewCell { @IBOutlet weak var titleLabel : UILabel! @IBOutlet weak var descriptionLabel : UILabel? @IBOutlet weak var imageView : UIImageView? func setup(as configuration : TableViewCellConfiguration) { titleLabel.text = configuration.titleText descriptionLabel?.text = configuration.descriptionText imageView?.image = configuration.image accessoryType = configuration.accessoryType } }

Slide 52

Slide 52 text

Cell Configuration func tableView(_ tableView: UITableView, cellForItemAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell (withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomTableViewCell let exercise = exercises[indexPath.row] cell.setup(as: .exerciseCell(exercise: exercise)) return cell }

Slide 53

Slide 53 text

View Controller Modes Viewing, Editing, New

Slide 54

Slide 54 text

View Controller Mode Methods - viewSetupForTransitionToState - cancelButtonAction - editButtonAction - saveButtonAction - barButtonSetupForTransitionToState - leftBarButtonItems - rightBarButtonItems

Slide 55

Slide 55 text

View Controller Mode Methods - didSelectRowAtIndexPath - contentSelectionAction - shouldHighlightCellAction - shouldHandleLongPressGestureAction - dragInteractionEnabled - numberOfSections - numberOfRowsInSection

Slide 56

Slide 56 text

Workout Activity Types Running, Cycling, Swimming

Slide 57

Slide 57 text

Workout Activity Type Methods - activityName - imageForActivity - visibleStatsForActivity - supportsGPS - waterLockEnabled

Slide 58

Slide 58 text

if activityType == .running || activityType == .walking { gpsProvider.start() } if activityType == .running || activityType == .walking { gpsProvider.stop() }

Slide 59

Slide 59 text

if activityType == .running || activityType == .walking || activityType == .hiking { gpsProvider.start() } if activityType == .running || activityType == .walking { gpsProvider.stop() }

Slide 60

Slide 60 text

if activityType.supportsGPS { gpsProvider.start() } if activityType.supportsGPS { gpsProvider.stop() }

Slide 61

Slide 61 text

Result1 Success, Failure enum Result { case success(Value) case failure(Error) } 1 https://www.swiftbysundell.com/posts/the-power-of-result-types-in-swift

Slide 62

Slide 62 text

Confetti Moments Colors, Images,

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

Confetti Moments public enum ConfettiMoment { // Color Confetti case confettiMMF case confettiMFP case confetti ! // Emoji Confetti case " // Custom Confetti case customColors(colors : [UIColor], name : String) case customImages(images : [UIImage], name : String) }

Slide 65

Slide 65 text

Confetti Moment Public API extension ConfettiMoment { func effectScene() -> SKScene }

Slide 66

Slide 66 text

Protocols extension ConfettiMoment : CustomStringConvertible { public var description: String { get { switch self { case . ! : return " ! " case .confettiMMF: return "MMF" case .confettiMFP: return "MFP" case .confetti " : return " " case .customColors(_, let name): return name case .customImages(_, let name): return name } } } }

Slide 67

Slide 67 text

Raw Values enum DefaultKeys : String { case loggedInUserId case selectedActivityType } print(DefaultKeys.selectedActivityType.rawValue) selectedActivityType

Slide 68

Slide 68 text

Raw Values enum DefaultKeys : String { case loggedInUserId case selectedActivityType = "SelectedActivityTypeDefaultKey" } print(DefaultKeys.selectedActivityType.rawValue) SelectedActivityTypeDefaultKey

Slide 69

Slide 69 text

RawRepresentable enum ApplicationTheme { case defaultLight case darkMode case blueberry case eggplant case mustard case broccoli } struct Theme : Equatable { let name : String let background : UIColor let cell : UIColor let tint : UIColor let foreground : UIColor let statusBar : UIStatusBarStyle }

Slide 70

Slide 70 text

RawRepresentable enum ApplicationTheme { case defaultLight(theme : Theme) case darkMode(theme : Theme) case blueberry(theme : Theme) case eggplant(theme : Theme) case mustard(theme : Theme) case broccoli(theme : Theme) }

Slide 71

Slide 71 text

RawRepresentable extension ApplicationTheme : RawRepresentable { typealias RawValue = Theme init?(rawValue: Theme) { // ... } var rawValue: Theme { // ... } } print(ApplicationTheme.broccoli.rawValue.name) Brocolli

Slide 72

Slide 72 text

Associated vs. Raw Values

Slide 73

Slide 73 text

New in Swift 4.2 Enumerate Enum Cases2 2 https://oleb.net/blog/2018/06/enumerating-enum-cases/

Slide 74

Slide 74 text

CaseIterable enum WorkoutApplicationLifecycle : CaseIterable { case notLoggedIn case workoutInProgress case savingLastWorkout case readyToStart } WorkoutApplicationLifecycle.allCases [notLoggedIn, workoutInProgress, savingLastWorkout, readyToStart]

Slide 75

Slide 75 text

Custom CaseIterable enum WorkoutApplicationLifecycle { case notLoggedIn case workoutInProgress(current : Workout) case savingLastWorkout case readyToStart } extension WorkoutApplicationLifecycle : CaseIterable { static var allCases: [WorkoutApplicationLifecycle] { return [.notLoggedIn, .savingLastWorkout, .readyToStart] + WorkoutActivityType.allCases.map(.workoutInProgress) } }

Slide 76

Slide 76 text

All the Confetti Moments extension ConfettiMoment : CaseIterable { static var allCases: [ConfettiMoment] { return [. ! , .confettiMMF, .confettiMFP, .confetti ] } }

Slide 77

Slide 77 text

All the Themes enum ApplicationTheme : CaseIterable { case defaultLight case darkMode case blueberry case eggplant case mustard case broccoli } ApplicationTheme.allCases [.defaultLight, .darkMode, .blueberry, .eggplant, .mustard, .broccoli]

Slide 78

Slide 78 text

Additional Resources - Enum Driven Table Views34 - Pattern Matching in Swift5 5 http://alisoftware.github.io/swift/pattern-matching/2016/03/27/pattern-matching-1/ 4 https://www.raywenderlich.com/5542-enum-driven-tableview-development 3 https://www.natashatherobot.com/swift-enums-tableviews/

Slide 79

Slide 79 text

Additional Resources - Enums and Optionals6 - Writing Self Documenting Swift Code7 - Code Encapsulation in Swift8 - Enumerations9 9 https://ericasadun.com/2015/05/26/swift-the-hall-of-the-dwarven-enumeration-king/#more-1529 8 https://www.swiftbysundell.com/posts/code-encapsulation-in-swift 7 https://www.swiftbysundell.com/posts/writing-self-documenting-swift-code 6 http://khanlou.com/2018/04/enums-and-optionals/

Slide 80

Slide 80 text

Thanks! _ @conradstoll _