TL;DR
AlfredViewController Source Code
//
// AlfredViewController.swift
// BatmansDelegate
//
// Created by h1i2j3 on 5/25/18.
// Copyright © 2018 h1i2j3. All rights reserved.
//
import UIKit
class AlfredViewController: UIViewController {
// MARK: - UI Connections
@IBOutlet weak private var messageLabel: UILabel!
@IBOutlet weak private var lessonTopicTextField: UITextField!
// MARK: - ViewController Life Cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
lessonTopicTextField.text = nil
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "TeachLessonTopic" else { return }
let batman = segue.destination as! BatmanViewController
batman.delegate = self
guard let lessonTopic = lessonTopicTextField.text else { return }
batman.lessonTopic =
!lessonTopic.isEmpty ? lessonTopic : "The Delegate Pattern"
}
}
// MARK: - BatmanViewController Delegates
extension AlfredViewController: BatmanViewControllerDelegate {
func batmanViewController(_ controller: BatmanViewController,
didChangeLessonTopic lessonTopic: String) {
messageLabel.text = "Teaching \(lessonTopic) as requested, Master Bruce."
navigationController?.popViewController(animated: true)
}
func batmanViewControllerCancel(_ controller: BatmanViewController) {
messageLabel.text = "I'll keep the tea warm until you return Master Bruce."
navigationController?.popViewController(animated: true)
}
}
BatmanViewController Source Code
//
// BatmanViewController.swift
// BatmansDelegate
//
// Created by h1i2j3 on 5/25/18.
// Copyright © 2018 h1i2j3. All rights reserved.
//
import UIKit
// MARK: - BatmanViewControllerDelegate Protocol
/// The delegate of a `BatmanViewController` object must adopt the
/// `BatmanViewControllerDelegate` protocol.
protocol BatmanViewControllerDelegate: class {
/// Tells the delegate that the lessonTopic was changed by the user/Batman.
func batmanViewController(_ controller: BatmanViewController,
didChangeLessonTopic lessonTopic: String)
/// Tells the delegate to cancel changes to the specified controller.
func batmanViewControllerCancel(_ controller: BatmanViewController)
}
class BatmanViewController: UIViewController {
// MARK: - Public API
var lessonTopic: String?
weak var delegate: BatmanViewControllerDelegate?
// MARK: - UI Connections
@IBOutlet weak private var lessonTopicLabel: UILabel!
@IBOutlet weak private var newLessonTopicTextField: UITextField!
@IBAction private func changeLessonTopic() {
guard let lessonTopic = newLessonTopicTextField.text else { return }
delegate?.batmanViewController(self, didChangeLessonTopic: lessonTopic)
}
@IBAction private func cancelInstruction() {
delegate?.batmanViewControllerCancel(self)
}
// MARK: - ViewController Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
guard let lessonTopic = lessonTopic else { return }
lessonTopicLabel.text = "Current lesson is on \(lessonTopic)"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Screenshots:
BatmansDelegate :: Beginner Tutorial Exploring the Delegate Pattern :: Part I
Batman and his Delegate, Alfred
Tutorial Time: 5-8 minutes. Actual time may vary based on InterfaceBuilder/Storyboard proficiency. This is a tutorial designed for beginners by a beginner, and for anyone who’d like to build a simple app designed around the Delegate Pattern for reference or practice.
Purpose: To create an app that exemplifies the delegate pattern with minimal storyboard/InterfaceBuilder design, and applies the delegate pattern to a pseudo real life analogy so that the steps/concepts can be recalled from memory, and eventually lead to mastery.
Disclaimer: This mini tutorial is not intended as a replacement of iOS Apprentice Chapter 13 “Delegates and Protocols”. Use this as supplemental material.
Note: This mini tutorial relies heavily on the assumption that you are able to recreate the application based upon the images & values provided - this is not a User Interface guide.
You should be able to accomplish this if you have completed up to Chapter 14, possibly Chapter 13 [Could someone please confirm?].
Possible Exceptions
- The utilization of UIKit’s property Tint Colour - Chapter 19 “UI Improvements”, page 430 (but it is not necessary to build this app)
- The utilization of UIStacks - This book doesn’t go into it, but there are videos on the site that shows you how (but it is not necessary to build this app)
https://raywenderlich.com/tag/stack-view - Where guard statements are used I have provided the code for an if statement.
Scenario
- Life Example A Student needs a Mentor to teach a Lesson Topic and respond to the Student’s needs.
- In iOS ViewControllerB needs ViewControllerA to be it’s delegate so Data/Notifications can be sent to ViewControllerA from ViewControllerB
Pseudo Real Life Analogy:
Think of the Student as Batman or Bruce, and his Mentor as Alfred.
Remember it: (Who's Who?)
Batman starts with B: ViewControllerB.
Alfred starts with A: ViewControllerA.
Summary of the Delegate Pattern - Batman Style
BatmanViewController - Batman's Process
- Batman requires a mentor, a delegate, who will be able to carry out the task of teaching in a way that will work with his superhero lifestyle.
- Batman writes out his specific needs in a custom delegate protocol.
- He then sets up a means to communicate these needs through a delegate object he creates.
- Batman will be able to use the delegate object as a means to communicate his needs to his delegate during his lesson.
AlfredViewController - Alfred's Process
- Alfred is destined to become Batman’s mentor, his delegate.
- He conforms to Batman’s custom delegate protocol so he will be able to receive & respond to Batman’s communicated needs via Batman’s delegate object and it’s methods, because he will be of the
BatmanViewControllerDelegate
type.
While Batman is out fighting crime, their comlink goes down, and Alfred has to give his cell a ring - using their established segue connection, in prepare(for:sender:).
- When Alfred dials Batman, he makes sure that the segue identifier is the right one - else he’ll end up calling Catwoman, and word on the street is that she’s not looking for a delegate right now.
- Alfred reaches Batman’s voice mail - which indicates that his message will end up at the right destination - batman is assigned segue.destination as! BatmanViewController.
- Alfred finally is able to announce to Batman that he will be Batman’s Delegate! - batman.delegate = self
Make the BatmansDelegate App
Initial set up of Alfred & Batman ViewControllers
Set Up the New Project in Xcode
iOS Application: Single View App
Product Name: BatmansDelegate
Language: Swift
3 boxes unchecked
View AlfredViewController Source Code
//
// AlfredViewController.swift
// BatmansDelegate
//
// Created by h1i2j3 on 5/25/18.
// Copyright © 2018 h1i2j3. All rights reserved.
//
import UIKit
class AlfredViewController: UIViewController {
// MARK: - UI Connections
@IBOutlet weak private var messageLabel: UILabel!
@IBOutlet weak private var lessonTopicTextField: UITextField!
// MARK: - ViewController Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
}
View BatmanViewController Source Code
//
// BatmanViewController.swift
// BatmansDelegate
//
// Created by h1i2j3 on 5/25/18.
// Copyright © 2018 h1i2j3. All rights reserved.
//
import UIKit
class BatmanViewController: UIViewController {
// MARK: - UI Connections
@IBOutlet weak private var lessonTopicLabel: UILabel!
@IBOutlet weak private var newLessonTopicTextField: UITextField!
@IBAction private func changeLessonTopic() {
navigationController?.popViewController(animated: true)
}
@IBAction private func cancelInstruction() {
navigationController?.popViewController(animated: true)
}
// MARK: - ViewController Life Cycle
override internal func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Alfred & Batman Scenes wired up with constraints using UIStackViews
Alfred Scene
Set Up
AlfredViewController with constraints
- Open the device configuration pane (looks like View as: iPhone 8) and select iPhone SE
- Set Simulator to be iPhone SE (upper left)
- Identity Inspector → Custom Class: AlfredViewController
- Embed AlfredViewController in Navigation Controller
Customize Navigation Controller
- select Navigation Bar → checkmark Prefers Large Titles (page 245)
Drag in 1 of each:
- Label
- Button
- Text Field
Set the following in Attributes Inspector:
- Label: System 24.0
- Button: System 17.0
- Round Style Text Field: System 17.0
- View Controller → Title: Alfred
- Drag in: Navigation Item → Title: Alfred
Add Constraints
AlfredViewController with constraints
See Document Outline
AlfredViewController Document Outline
_Constrain to Margins_ | Top | Left | Right | Bottom | |||
Label | 15 | 20 | 20 | ||||
Button | 5 (to label) | 20 | |||||
RS Text Field | 5 (to label) | 20 | 5 (to button) |
AutoLayout Error due to Content Size Ambiguity
To resolve the AutoLayout Error: Decrease horizontal hugging of “Round Style Text Field” from 250 to 249 to make it grow before other views (the button in this case)
There are 2 ways you can do this:
Resolve this in the Document Outline
Resolve in the Size Inspector of Round Style Text Field
Size Inspector → Content Hugging Priority → Horizontal: 249
Add Constraints using UIStackView <- Not Required
AlfredViewController with constrained stacks
Topic Stack View
- Embed Round Style Text Field and Button in a UIStack
- Axis: Horizontal stack
- Alignment: Fill
- Distribution: Fill Proportionally
- Spacing 5
Message & Topic Stack View
- Embed Label and Topic Stack View in a UIStack
- Axis: Vertical stack
- Alignment: Fill
- Distribution: Fill Proportionally
- Spacing 5
Set Round Style Text Field (75%) and Button (25%) of width of the UIStackView
- Set Round Style Text Field and Button to equal widths
(ctrl drag from Text Field to Button → Equal Widths - Select the equal widths constraint you just created
- Equal Widths Constraint → Multiplier: 3
Change Equal Widths Constraint from 1 (equal), to 3
See Document Outline
_Constrain to Margins_ | Top | Left | Right | Bottom |
Message & Topic Stack View | 15 | 20 | 20 |
Wire Connections
Wire Label to outlet messageLabel
Set text: Time for your lesson, Master Bruce.
Lines: 0
Wire Round Style Text Field to outlet lessonTopicTextField
Set Placeholder Text: Lesson Topic
Button is already set up via ActionSegue: Show
Set Default Title: Teach
See Wired AlfredViewController
Wired AlfredViewController
Wired AlfredViewController - UIStackView ← Not Required
See Document Outline
AlfredViewController Document Outline
AlfredViewController Document Outline - UIStackView ← Not Required
Batman Scene
Set Up
BatmanViewController with constraints
- Drag in: ViewController → Custom Class: BatmanViewController
- Segue from Alfred’s Button to Batman → ActionSegue: Show
Drag in 1 of each
- Label
- Text Field
Set the following in Attributes Inspector
- Label: System 24.0
- Round Style Text Field: System 17.0
- View Controller → Title: Batman
- Drag in: Navigation Item → Title: Batman
- Drag in: 2 Bar Button Items onto the Navigation Item
Left Bar Button Item → Title: Bat-Signal
Right Bar Button Item → Title: Change Topic - Segue → Identifier: TeachLessonTopic
Add Constraints
BatmanViewController with constraints
Constrain to margins for all choices
See Document Outline
_Constrain to Margins_ | Top | Left | Right | Bottom | ||
Label | 15 | 20 | 20 | |||
RS Text Field | 10 (to label) | 20 | 20 |
Add Constraints using UIStackView <- Not Required
BatmanViewController with constrained stacks
New Topic Stack View
- Embed Label and Round Style Text Field in a stack
- Axis: Vertical stack
- Alignment: Fill
- Distribution: Fill
- Spacing 10
See Document Outline
_Constrain to Margins_ | Top | Left | Right | Bottom |
New Topic Stack View | 15 | 20 | 20 |
Wire Connections
Wire Label to outlet lessonTopicLabel
Set text: Current Lesson is on Lesson Topic
Lines: 0
Wire Round Style Text Field to outlet newLessonTopicTextField
Set Placeholder Text: New Lesson Topic
Wire Left Bar Button Item: Bat-Signal to action cancelInstruction
Wire Right Bar Button Item: Change Topic to action changeLessonTopic
See BatmanViewController
Wired BatmanViewController
Wired BatmanViewController - UIStackView ← Not Required
See Document Outline
BatmanViewController Document Outline
BatmanViewController Document Outline - UIStackView ← Not Required
Build and Run BatmansDelegate :: #1
Build the app, your screens should look identical to the iPhone Screen Shots provided and function as described in Current State Functionality
Current State Functionality
Actions
Teach
segues to BatmanViewController
Bat-Signal
unwinds to AlfredViewController
Change Topic
unwinds to AlfredViewController
Placeholder text
Lesson Topic enter text
New Lesson Topic enter text
Delegate
Delegate Pattern:
iOS Apprentice Chapter 13 “Delegates and Protocols” page 287 summarizes the delegate pattern and provides visual representations of ViewControllerA & ViewControllerB, this tutorial complements these diagrams.
Remember: (Delegate Definition)
Delegate: A person acting for another. [Source: merriam-webster.com]
Think of a delegate as an appointed/chosen object that does work for another object.
Delegate Pattern Relationship:
- Life Alfred is Batman’s mentor / Alfred is the mentor of Batman
- iOS VCA is VCB’s delegate / VCA is the delegate of VCB
Delegate Protocol:
iOS Apprentice Chapter 13 “Delegates and Protocols” page 289 summarizes what the delegate protocol is, and touches on protocols in general.
- Life Batman appoints Alfred to be his mentor, so Alfred can teach/respond to Batman.
- iOS VCB appoints VCA to be it’s delegate, so VCA can send/recieve Data to/from VCB.
BatmanViewController
Summary of Batman's Process - Batman Style
A copy of the process was placed here for connivence
- Batman requires a mentor, a delegate, who will be able to carry out the task of teaching in a way that will work with his superhero lifestyle.
- Batman writes out his specific needs in a custom delegate protocol.
- He then sets up a means to communicate these needs through a delegate object he creates.
- Batman will be able to use the delegate object as a means to communicate his needs to his mentor/delegate during his lesson.
Batman’s Delegate Protocol
Batman’s ideal mentor (delegate):
- Delegate must change the lesson to a different topic per Batman’s request.
- Delegate must realize that Batman canceled the lesson early due to the Bat-Signal.
Place this code at the top of BatmanViewController
right above the class definition. (page 289)
import UIKit
// MARK: - BatmanViewControllerDelegate Protocol
/// The delegate of a `BatmanViewController` object must adopt the
/// `BatmanViewControllerDelegate` protocol.
protocol BatmanViewControllerDelegate: class {
/// Tells the delegate that the lessonTopic was changed by the user/Batman.
func batmanViewController(_ controller: BatmanViewController,
didChangeLessonTopic lessonTopic: String)
/// Tells the delegate to cancel changes to the specified controller.
func batmanViewControllerCancel(_ controller: BatmanViewController)
}
Batman’s Delegate Variable:
Batman needs a way to communicate the following to his delegate:
- Batman changed the lesson topic, and needs to let the delegate know what that new lesson topic is.
- Batman needs to go due to the Bat-Signal, and needs his delegate to cancel the lesson.
Batman needs to create an object of type BatmanViewControllerDelegate?
through which his delegate will be able to call Batman’s delegate methods
and effectively receive the messages Batman communicated.
// MARK: - Public API
weak var delegate: BatmanViewControllerDelegate?
//weak var delegateAlfred <- change the name to this if it helps!
In the changeLessonTopic()
action, Batman’s new lesson topic is set up to be passed to his delegate by means of his batmanViewController(_:didChangeLessonTopic:)
delegate method.
@IBAction private func changeLessonTopic() {
guard let lessonTopic = newLessonTopicTextField.text else { return }
delegate?.batmanViewController(self, didChangeLessonTopic: lessonTopic)
navigationController?.popViewController(animated: true)
}
See guard let <-> if let code
The author touches briefly on guard on page (490)
guard let -assignment- else { return }
is similar to if let -assignment- {}
// Same code as above
if let lessonTopic = newLessonTopicTextField.text {
delegate?.batmanViewController(self, didChangeLessonTopic: lessonTopic)
}
navigationController?.popViewController(animated: true)
In the cancelInstruction()
action, Batman leaves to go fight crime due to the Bat-Signal. He communicates this departure via his delegate method batmanViewControllerCancel()
which can be synonymous with cancel()
.
More about cancel()
on page 291.
@IBAction private func cancelInstruction() {
delegate?.batmanViewControllerCancel(self)
navigationController?.popViewController(animated: true)
}
Batman has successfully set up a way to communicate his needs/any changes in self, to his delegate object to ensure his mentor lessons will go smoothly.
It’s up to the object that decides to take on the responsibly of being Batman’s delegate to take the final steps.
Build and Run BatmansDelegate :: #2
Build the app to make sure it still works.
Current State Functionality :: No changes since last build
Actions
Teach
segues to BatmanViewController
Bat-Signal
unwinds to AlfredViewController
Change Topic
unwinds to AlfredViewController
Placeholder text
Lesson Topic enter text
New Lesson Topic enter text
iPhone Screen Shots :: No changes since last build
Constraints applied, Segues and Actions wired
Wired Outlets
View AlfredViewController Source Code :: No changes since last build
//
// AlfredViewController.swift
// BatmansDelegate
//
// Created by h1i2j3 on 5/25/18.
// Copyright © 2018 h1i2j3. All rights reserved.
//
import UIKit
class AlfredViewController: UIViewController {
// MARK: - UI Connections
@IBOutlet weak private var messageLabel: UILabel!
@IBOutlet weak private var lessonTopicTextField: UITextField!
// MARK: - ViewController Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
}
View BatmanViewController Source Code
//
// BatmanViewController.swift
// BatmansDelegate
//
// Created by h1i2j3 on 5/25/18.
// Copyright © 2018 h1i2j3. All rights reserved.
//
import UIKit
// MARK: - BatmanViewControllerDelegate Protocol
/// The delegate of a `BatmanViewController` object must adopt the
/// `BatmanViewControllerDelegate` protocol.
protocol BatmanViewControllerDelegate: class {
/// Tells the delegate that the lessonTopic was changed by the user/Batman.
func batmanViewController(_ controller: BatmanViewController,
didChangeLessonTopic lessonTopic: String)
/// Tells the delegate to cancel changes to the specified controller.
func batmanViewControllerCancel(_ controller: BatmanViewController)
}
class BatmanViewController: UIViewController {
// MARK: - Public API
weak var delegate: BatmanViewControllerDelegate?
// MARK: - UI Connections
@IBOutlet weak private var lessonTopicLabel: UILabel!
@IBOutlet weak private var newLessonTopicTextField: UITextField!
@IBAction private func changeLessonTopic() {
guard let lessonTopic = newLessonTopicTextField.text else { return }
delegate?.batmanViewController(self, didChangeLessonTopic: lessonTopic)
navigationController?.popViewController(animated: true)
}
@IBAction private func cancelInstruction() {
delegate?.batmanViewControllerCancel(self)
navigationController?.popViewController(animated: true)
}
// MARK: - ViewController Life Cycle
override internal func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}