Taming the Massive Controllers in iOS Part 2

In the last article we introduced a massive controller and worked towards a more leaner design. We separated out the UITableView data sources into a designated data source and data manager classes. The refactoring moved the responsibility of providing the data to the UITableView from the ShoppingListTableViewController into separate concrete implementations. In this article we are going to look at other parts of the ShoppingListTableViewController which can be tamed into separate classes.

UIAlertController

Take a look at the addShoppingListButtonPressed function below:

@IBAction func addShoppingListButtonPressed() {
let alertController = UIAlertController(title: “Grocry”, message: “Enter Shopping List”, preferredStyle: .Alert)
let saveAction = UIAlertAction(title: “Save”, style: .Default) { (action :UIAlertAction) in
let shoppingNameTextField = alertController.textFields![0]
self.saveShoppingList(shoppingNameTextField.text!)
}
alertController.addTextFieldWithConfigurationHandler { (textField) in
}
alertController.addAction(saveAction)
self.presentViewController(alertController, animated: true, completion: nil)
}

The above code is responsible for displaying an alert to the user. The alert contains a UITextField which is used to enter a new shopping list name. Once, the name is entered and the “Save” button is pressed the new shopping list is stored in the SQLite database using Core Data.

The code above works fine but we can definitely improve it by utilizing the Swift Extensions for UIAlertController.

extension UIAlertController {
static func alertForNewShoppingList(closure :(title :String) -> ()) -> UIAlertController {
let alertController = UIAlertController(title: “Grocry”, message: “Enter Shopping List”, preferredStyle: .Alert)
let saveAction = UIAlertAction(title: “Save”, style: .Default) { (action :UIAlertAction) in
let shoppingNameTextField = alertController.textFields![0]
closure(title: shoppingNameTextField.text!)
}
alertController.addTextFieldWithConfigurationHandler { (textField) in
}
alertController.addAction(saveAction)
return alertController
}
}

There is nothing magical about the above code! We simply moved the same code into a separate function which is a Swift Extension for UIAlertController class. This refactoring results in the following code:

@IBAction func addShoppingListButtonPressed() {
let alertController = UIAlertController.alertForNewShoppingList { (title :String) in
self.saveShoppingList(title)
}
self.presentViewController(alertController, animated: true, completion: nil)
}

Much simpler right!

NSManagedObjectContext

In the addShoppingListButtonPressed we called the self.saveShoppingList function to persist the new shopping list record into the database. The saveShoppingList function is implemented below:

private func saveShoppingList(title :String) {
let shoppingList = NSEntityDescription.insertNewObjectForEntityForName(“ShoppingList”, inManagedObjectContext: self.managedObjectContext)
shoppingList.setValue(title, forKey: “title”)
try! self.managedObjectContext.save()
}

The NSEntityDescription.insertNewObjectForEntityForName is quite a mouthful. Also, for some reason we are using the KVC techniques when assigning the new title to the shoppingList instance even though we have created a custom class for ShoppingList which inherits from the NSManagedObject as shown below:

class ShoppingList: NSManagedObject, ManagedObjectType {
@NSManaged var title :String!
static var entityName: String {
return "ShoppingList"
}
static var sortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(key: "title", ascending: true)]
}
}

If we utilize the ShoppingList class above our code will look something like this:

private func saveShoppingList(title :String) {
guard let shoppingList = NSEntityDescription.insertNewObjectForEntityForName(“ShoppingList”, inManagedObjectContext: self.managedObjectContext) as? ShoppingList else {
fatalError(“ShoppingList class not found”)
}
shoppingList.title = title
try! self.managedObjectContext.save()
}

A little better but now we have to case it to the ShoppingList class before assigning it to the shoppingList variable. Also, we still have that NSEntityDescription.insertNewObjectForEntityName which look pretty ugly to type all the time.

Swift Extensions to the Rescue!

We can extend the NSManagedObjectContext class to add a function insertObject which is responsible for creating the type of object we need to be inserted.

import Foundation
import CoreData
extension NSManagedObjectContext {
func insertObject<A :NSManagedObject where A :ManagedObjectType>() -> A {
guard let obj = NSEntityDescription.insertNewObjectForEntityForName(A.entityName, inManagedObjectContext: self) as? A else {
fatalError(“Wrong object type”)
}
return obj
}
}

Now, you can easily update your code to take advantage of the NSManagedObjectContext Extension as shown below:

private func saveShoppingList(title :String) {
let shoppingList :ShoppingList = self.managedObjectContext.insertObject()
shoppingList.title = title
try! self.managedObjectContext.save()
}

Much nicer and simpler!

Segue

Segues are intregal part of your application since they allow you to navigate between different screens. Currently, if you select a shoppingList you will be taken to the grocery items screen which will display all the grocery items for the selected shopping list. The implementation of the prepareForSegue is shown below:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == “showGroceryItems” {
guard let controller = segue.destinationViewController as? GroceryItemsTableViewController3 else {
fatalError(“GroceryItemsTableViewController3 not found”)
}
guard let indexPath = self.tableView.indexPathForSelectedRow else {
fatalError(“indexPath not found”)
}
let shoppingList = self.manager.objectAtIndexPath(indexPath)
controller.shoppingList = shoppingList
}
}

Straight away you can see the issue! The segue.identifier is comparing against a string value. It is very easy to make a mistake and spell the string wrong and then all of a sudden the segue will stop working. To work around this issue we created a custom protocol “SegueHandlerType” which can be used on the classes who want to perform a segue.

protocol SegueHandlerType {
associatedtype SegueIdentifier :RawRepresentable
}

We also utilized the Protocol Extensions feature of the Swift language to provide the default implementation of segueIdentifierForSegue as shown below:

extension SegueHandlerType where Self :UIViewController, SegueIdentifier.RawValue == String {
func segueIdentiierForSegue(segue :UIStoryboardSegue) -> SegueIdentifier {
guard let identifier = segue.identifier,
let segueIdentifier = SegueIdentifier(rawValue: identifier)
else {
fatalError(“Unknown segue”)
}
return segueIdentifier
}
func performSegue(segueIdentifier :SegueIdentifier) {
performSegueWithIdentifier(segueIdentifier.rawValue, sender: nil)
}
}

Now our ShoppingListTableViewController class can conform to the SegueHandlerType class as shown below:

class ShoppingListTableViewController3: UITableViewController, SegueHandlerType {
enum SegueIdentifier : String {
case showGroceryItems = “showGroceryItems”
}

And we can use the segueIdentifierForSegue function inside the prepareForSegue function:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segueIdentiierForSegue(segue) {
case .showGroceryItems:
performSegueForShowGroceryItems(segue)
}
}
private func performSegueForShowGroceryItems(segue :UIStoryboardSegue) {
guard let controller = segue.destinationViewController as? GroceryItemsTableViewController3 else {
fatalError(“GroceryItemsTableViewController3 not found”)
}
guard let indexPath = self.tableView.indexPathForSelectedRow else {
fatalError(“indexPath not found”)
}
let shoppingList = self.manager.objectAtIndexPath(indexPath)
controller.shoppingList = shoppingList
}

Much better and looks nicer!

In this post we looked at different pieces of code contained inside the ShoppingListTableViewController and moved it to their designated places. This resulted in more readable and maintainable code which can provide greater benefits in the future.

References

Like what you read? Give Mohammad Azam a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.