Correct way to publish model to delegated view? - ios

I have a UITableViewController which provides an array of strings as data source:
import UIKit
class ItemsViewController: UITableViewController {
let items = [
"Bob",
"Joe",
]
#IBAction
func unwindToSegue(segue: UIStoryboardSegue) {
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Item", forIndexPath: indexPath) as UITableViewCell
cell.textLabel.text = items[indexPath.row]
return cell
}
}
I now added a second scene with a UIViewController which is activated when the user clicks on a UITableCell through a show segue.
I would now like to take the string assigned with the former UITableCell and inserted into the ItemViewController:
import UIKit
class ItemViewController: UIViewController {
#IBOutlet
weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//var name = segue.someMethodHowIGetTheDataAssignedToTableCell
//nameLabel.text = name
}
}
How do I pass the model (in this case the string) assigned to the single TableCell over to the new ItemViewController?

Like this. Just add a return push segue from ItemViewController back to ItemsViewController. Name them less similar in the future :-)
import UIKit
class ItemsViewController: UITableViewController {
let items = [
"Bob",
"Joe",
]
#IBAction
func unwindToSegue(segue: UIStoryboardSegue) {
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Item", forIndexPath: indexPath) as UITableViewCell
cell.textLabel.text = items[indexPath.row]
return cell
}
//Passing details to detail VC
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath = self.tableView.indexPathForSelectedRow()
let theDestination = (segue.destinationViewController as ItemViewController)
theDestination.itemName = items[indexPath!.row]
}
}
import UIKit
class ItemViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
var itemName = ""
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = itemName
}
}

Related

Getting a EXC_BAD_ACCESS when trying to initialize my custom Cell in TableViewController

My application fetches data from a mock API.
Using a custom cell, I display the names of authors on my landing page viewController.
When I click on a cell, it takes that author's book information to display on a 2nd TableViewController.
But even though the implementation is the same as for the landing page. My app freezes until I get a EXC_BAD_ACCESS error
It seems like it's stuck in an infinite loop, but without a proper error, it's hard to know why.
Infinite Loop?
I can get this to work without using a custom cell, but then I cannot display all the information I want (only book title or release date), so the data is there.
import UIKit
class BooksTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var pages: UILabel!
#IBOutlet weak var release: UILabel!
// #IBOutlet var coverImage: UIImageView!
static let cellIdentifier = "BooksTableViewCell"
//
override func awakeFromNib() {
super.awakeFromNib()
}
static func nib() -> UINib {
return UINib(nibName: "BooksTableViewCell", bundle: nil)
}
//MARK: configure
public func configure(with viewModel: BooksCellViewModel) {
name.text = viewModel.name
pages.text = String(viewModel.pages)
release.text = viewModel.release
// coverImage.image = viewModel.image
}
}
import UIKit
class BooksTableViewController: UITableViewController {
var books: [Book] = []
var authorName: String = ""
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(BooksTableViewCell.nib(), forCellReuseIdentifier: BooksTableViewCell.cellIdentifier)
tableView.delegate = self
tableView.dataSource = self
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return authorName
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return books.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Hello1")
let cell = tableView.dequeueReusableCell(withIdentifier: BooksTableViewCell.cellIdentifier, for: indexPath) as! BooksTableViewCell
print("Hello2")
let model = books[indexPath.row]
cell.configure(with: BooksCellViewModel(name: model.title, pages: model.pages, release: model.releaseDate))
return cell
}
}
The landing page controller and cell is similar but works with no problems
import UIKit
class LandingTableViewController: UITableViewController {
let parser = DataAPI()
var authors = [Author]()
var books = [Book]()
var authorName = ""
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(AuthorTableViewCell.nib(), forCellReuseIdentifier: AuthorTableViewCell.cellIdentifier)
tableView.delegate = self
tableView.dataSource = self
parser.getData {
data in
self.authors = data
//Reload UI on Main thread:
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "List of Authors"
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return authors.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: AuthorTableViewCell.cellIdentifier, for: indexPath) as! AuthorTableViewCell
let model = authors[indexPath.row]
cell.configure(with: AuthorCellViewModel(name: model.authorName))
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
books = authors[indexPath.row].books
authorName = authors[indexPath.row].authorName
performSegue(withIdentifier: "Show Books", sender: nil)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
if (segue.identifier == "Show Books") {
let showBooksViewController: BooksTableViewController = segue.destination as! BooksTableViewController
showBooksViewController.books = books
showBooksViewController.authorName = authorName
}
}
}
I was able to fix the issue by correctly naming my variables. I needed to be using releaseDate not release as per my model object.

TableView is not appending new data

I am trying to create a simple app which lets appending data into an array in first ViewController and then send it to the SecondViewController through the segue. However instead of appending new data it is updating first index of an array.
import UIKit
class ViewController: UIViewController {
var data = [String]()
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func sendButton(_ sender: Any) {
if textField.text != nil {
let activity = textField.text
data.append(activity!) //Data should be appended here.
performSegue(withIdentifier: "sendSegue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendSegue" {
let destVC: AppTableViewController = segue.destination as! AppTableViewController
destVC.newData = data
}
}
}
Here is my SecondTableViewController and it just update first index so I am just keep changing my first index instead of adding new values into it.
import UIKit
class AppTableViewController: UITableViewController {
var newData = [String]()
#IBOutlet var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = newData[indexPath.row]
return cell
}
}
Based on this link,
Please change the function name sendButton to btnSendButton.
So that it will run before prepare.
As of now sendButton is running after prepare. So the data is not appending.

Pass the myCell.textLabel?.text value via a segue in a dynamic prototype

I'm trying to segue from one UITableView to another UITableView. I want to segue and pass the myCell.textLabel?.text value of the selected cell to the second UITableView.
The code for my first UITableView (MenuTypeTableViewController and the code for my second UITableView (TypeItemsTableViewController) is also below.
I'm fully aware this involves the prepareForSegue function which currently I've not created, purely because I'm unsure where I override it and how to pass in the textLabel value to it.
Hope my question makes sense, I will update with suggestions and edits.
class MenuTypeTableViewController: UITableViewController, MenuTypeServerProtocol {
//Properties
var cellItems: NSArray = NSArray()
var menuType: MenuTypeModel = MenuTypeModel()
override func viewDidLoad() {
super.viewDidLoad()
let menuTypeServer = MenuTypeServer()
menuTypeServer.delegate = self
menuTypeServer.downloadItems()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "cellType"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
let item: MenuTypeModel = cellItems[indexPath.row] as! MenuTypeModel
myCell.textLabel?.text = item.type
return myCell
}
func itemsDownloaded(items: NSArray) {
cellItems = items
tableView.reloadData()
}
}
class TypeItemsTableViewController: UITableViewController, TypeItemsServerProtocol {
//Properties
var cellItems: NSArray = NSArray()
var typeItemList: TypeItemsModel = TypeItemsModel()
override func viewDidLoad() {
super.viewDidLoad()
let typeItemsServer = TypeItemsServer()
typeItemsServer.delegate = self
typeItemsServer.downloadItems()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "cellTypeItem"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
let item: TypeItemsModel = cellItems[indexPath.row] as! TypeItemsModel
myCell.textLabel?.text = item.name
return myCell
}
func itemsDownloaded(items: NSArray) {
cellItems = items
tableView.reloadData()
}
}
Hi try the following set of code, I have added few additional changes in your code make use of it, I hope it will solve your issue.
I have added only the extra codes which you needed
class TypeItemsTableViewController: UITableViewController, TypeItemsServerProtocol {
// Add this variable in this class and use it whereever you needed it in this class
var selectedItem: String?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get the selected cell
let selectedCell = tableView.cellForRow(at: indexPath)
// Now maintain the text which you want in this class variable
selectedItem = selectedCell?.textLabel?.text
// Now perform the segue operation
performSegue(withIdentifier: "TypeItemsTableViewController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TypeItemsTableViewController" {
let destinationVC = segue.destination as? TypeItemsTableViewController
destinationVC?.selectedItem = self.selectedItem // Pass the selected item here which we have saved on didSelectRotAt indexPath delegate
}
}
In Second class:
class TypeItemsTableViewController: UITableViewController, TypeItemsServerProtocol {
// Add this variable in this class and use it whereever you needed it in this class
var selectedItem: String?
What you can do is to make a variable in your second UITableView
var String: labelSelected?
then in you prepare for segue method just set the labelSelected to the value of the cell.
refToTableViewCell.labelSelected = youCell.textlabel?.text
If you set up a segue in storyboards from one storyboard to another, you can use the code below in your prepareForSegue method. You'll need to add a testFromMenuTableViewController property to your TypeItemsTableViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? TypeItemsTableViewController,
let path = self.tableView.indexPathForSelectedRow,
let cell = self.tableView.cellForRow(at: path),
let text = cell.textLabel?.text {
destination.textFromMenuTypeTableViewController = text
}
}
For more info check this SO answer.

Passing coredata from tableview to another tableview

I am struggling with getting my care data to populate my second tableview controller. The data is populating the first tableview and I can select a row and the segue is used to go to the second table but the labels are not populated.
I've looked all over and have found older samples or obj-c but I cannot figure it out, so any help pointing this n00b in the right direction will be helpful.
Here is what I have, I think I am missing how to populate a variable to pass in prepareForSegue in the list tableview, but I could be wrong. I get a warning error in that function (Warning cannot assign value of type 'ListEntity' to type '[ListEntity]').
CoreData
Entity = ListEntity
Attributes = title, event & location (all as Strings)
listTableViewController
import UIKit
import CoreData
class ListTableViewController: UITableViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var lists = [ListEntity]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "The List"
let addButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: #selector(ListTableViewController.addButtonMethod))
navigationItem.rightBarButtonItem = addButton
}
func addButtonMethod() {
print("Perform action")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
reloadData()
tableView.reloadData()
}
func reloadData() {
let fetchRequest = NSFetchRequest(entityName: "ListEntity")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [ListEntity] {
lists = results
}
} catch {
fatalError("There was an error fetching the list!")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lists.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ListCell") as! ListTableViewCell
let list = lists[indexPath.row]
cell.configurationWithSetup(list)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("DetailsSegue", sender: self)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "DetailsSegue" {
let destinationVC = segue.destinationViewController as! DetailsTableViewController
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
print(indexPath.row) // Print the Row selected to console
// Place the code to pass data here?
// destinationVC.lists = lists[indexPath.row]
// Warning cannot assign value of type 'ListEntity' to type '[ListEntity]'
}
}
}
listTableViewCell
import UIKit
class ListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configurationWithSetup(list: AnyObject) {
titleLabel.text = list.valueForKey("title") as! String?
}
}
detailsTableViewController
import UIKit
import CoreData
class DetailsTableViewController: UITableViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var lists = [ListEntity]()
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell") as! DetailsTableViewCell
let list = lists[indexPath.row]
cell.configurationWithSetup(list)
return cell
}
}
detailsTableViewCell
import UIKit
import CoreData
class DetailsTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var eventLabel: UILabel!
#IBOutlet weak var locationLabel: UILabel!
func configurationWithSetup(list: AnyObject) {
titleLabel.text = list.valueForKey("title") as! String?
eventLabel.text = list.valueForKey("event") as! String?
locationLabel.text = list.valueForKey("location") as! String?
}
}
The warning contains the answer - just change
var lists = [ListEntity]() to
var lists = ListEntity(), or var lists:ListEntity! and when you prepare for segue set that value.
Then you will need to change
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell") as! DetailsTableViewCell
// as data source is not array you can just you the item you passed
// let list = lists[indexPath.row]
cell.configurationWithSetup(lists)
return cell
}
You should use a static table view if you just want one cell
More info per you current issue
class DetailsTableViewController: UITableViewController {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var theDetailListEntity:ListEntity!
override func viewDidLoad() {
super.viewDidLoad()
print(theDetailListEntity) // check that you passed it across
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DataCell") as! DetailsTableViewCell
cell.configurationWithSetup(theDetailListEntity)
return cell
}
}
Don't forget to add prepare for segue in the listTableViewController otherwise theDetailListEntity won't be set... and then it will crash.
Depending on how you set up your segue, it may differ. But this is what you need
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("showMyDetailView", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showMyDetailView" {
guard let
vc = segue.destinationViewController as? DetailsTableViewController,
ip = sender as? NSIndexPath else { fatalError() }
let item = lists[ip.row]
vc.theDetailListEntity = item
// set the item in the next VC
tableView.deselectRowAtIndexPath(ip, animated: true)
}
}

Swift: Passing data from a tableView in a ViewController to another ViewController

I'm trying to pass the indexPath.row data from the selected cell to the next view controller using both the didSelectCellAtIndexPath and the prepareForSegue methods but the value isnt passing through.
Code for both ViewControllers is show below:
import UIKit
class MainMenu: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
// array of the menu options
let mainMenuOptions = ["Exercises 1", "Exercises 2", "Exercises 3"]
// UITableView
#IBOutlet weak var exerciseOptions: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
exerciseOptions.delegate = self
exerciseOptions.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Table configuration
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ExercisesTableViewCells"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ExercisesTableViewCell
cell.textLabel!.text = mainMenuOptions[indexPath.row]
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
// Segue to VC based on row selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0{
self.performSegueWithIdentifier("SegueToExercises", sender: self)
}
else if indexPath.row == 1{
self.performSegueWithIdentifier("SegueToExercises", sender: self)
}
else{
self.performSegueWithIdentifier("SegueToReminders", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SegueToExercise"){
let viewController = segue.destinationViewController as? ExerciseMenu
viewController!.mainMenuValue = exerciseOptions.indexPathForSelectedRow!.row
}
}
For the Second View Controller:
import UIKit
class ExerciseMenu: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var exerciseOptionsTitle: UILabel!
#IBOutlet weak var exerciseOptionsTable: UITableView!
#IBAction func beginWorkout(sender: AnyObject) {
}
// receives data from MainMenu
var mainMenuValue: Int!
override func viewDidLoad() {
super.viewDidLoad()
exerciseOptionsTable.delegate = self
exerciseOptionsTable.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ExerciseMenuCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ExercisesTableViewCell
cell.textLabel!.text = String(mainMenuValue)
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
See this thread for possible explanations for this behavior.
The first step of troubleshooting would be to put a breakpoint in your prepareForSegue method to see if exerciseOptions.indexPathForSelectedRow is already nil at that point, in which case something is happening before the segue even occurs that's messing with the value (which the link above can help you diagnose).
Try making a property in your table view controller called something like selectedRow, and in didSelectRowAtIndexPath, set self.selectedRow = indexPath.row. This way you have your own variable that'll be more reliable than exerciseOptions.indexPathForSelectedRow. Then change the code in prepareForSegue appropriately. So to give you the full methods as I'm envisioning them:
(As a property of the first view controller) var selectedRow: Int?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0{
self.performSegueWithIdentifier("SegueToExercises", sender: self)
self.selectedRow = 0
}
else if indexPath.row == 1{
self.performSegueWithIdentifier("SegueToExercises", sender: self)
self.selectedRow = 1
}
else{
self.performSegueWithIdentifier("SegueToReminders", sender: self)
self.selectedRow = indexPath.row
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SegueToExercise"){
let viewController = segue.destinationViewController as? ExerciseMenu
viewController!.mainMenuValue = self.selectedRow!
}
}
Disclaimer: I'm still learning Swift, so my judgment regarding optionals/optional unwrapping might be unideal for a technical reason I'm not aware of, but the overall theory here is sound, I think.

Resources