Architecture - View and Navigation Logic Abstraction

Views

Aside from pulling out protocol code and putting it into its own file (as covered in my most recent post), we can also pull out view code to reduce the size of our VCs. 

In a typical VC, say we are putting view setup code into our "viewDidLoad" method - we may instantiate several UI components and add them as subviews here. You may choose to create other methods that customize the subclassed UI components when called (which we do from viewDidLoad), ending the customization with adding the component as a subview. This may include using Visual Formatting Language to implement AutoLayout in code. See below:

class BloatedVC: UIViewController {

  private let restaurantName = UILabel()

  private let restaurantImage = UIImageView()

  private let blankSpaceSeparator = UIView()


  override func viewDidLoad() {

  super.viewDidLoad()

  view.addSubview(restaurantName)

  view.addSubview(restaurantImage)

  view.addSubview(blankSpaceSeparator)

  // add a ton of constraint code here, along with other customization including fram  e sizing, color, animations, etc.

  }
}

Here's a thought - instead of putting this code into a VC, why not factor it out into a subclass of UIView? We can have all the same code in that subclass, literally the exact same code, but instead of putting it all in our VC, we just create an instance of that new class in our VC and say

class BloatedVC: UIViewController {

  private var viewObject: ExtractedVCViewCode?

  override func loadView() {
  if let abstractedView = viewObject {
  self.view = abstractedView
    }
  }
}

It's important to note that we set up our view in "loadView", not "viewDidLoad". This is called by a VC when its view is currently nil, so you can instantiate the abstracted view in there.

The code for the abstracted view looks almost identical to the original code we had in "viewDidLoad":

class ExtractedVCViewCode: UIView {

  private let restaurantName = UILabel()
  private let restaurantImage = UIImageView()
  private let blankSpaceSeparator = UIView()

  struct LayoutConstants {
    static let padding: CGFloat = 15.0
    //other constants here for use in autolayout
  }

  override init(frame: CGRect) {
    super.init(frame:frame)
    addSubview(restaurantName)
    addSubview(restaurantImage)
    addSubview(blankSpaceSeparator)

    // add a ton of constraint code here, along with other customization including g     frame sizing, color, animations, etc.
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
}

Navigation

Another quick (and to some, perhaps insignificant) way to reduce the amount of code in a VC is by separating out navigation logic - if a cell is clicked, instantiate this VC, pass in this restaurant as the data being provided, and present VC. This won't work for you if you're using IB segues (I prefer writing everything in code for many reasons unless I have a very simple idea I want to quickly prototype). 

First, we have two view controllers - the first displays a list of restaurants, the second displays information for one specific restaurant. We'll leave out typical navigation code, as it will be handled by a third class that neither VC knows about (yay for abstraction). Here's the gist of things - VC #1 declares a protocol with a method that takes a restaurant object as an argument - we also declare a weak var delegate that is of that delegate type. In "didSelectRowAtIndexPath", we invoke that method and say "whoever is implementing our delegate method, go ahead and execute now, here's the restaurant object you'll be needing". That's ALL THE CODE we have for navigation in our first VC.

The third class, that "navigation event handler", is what conforms to the protocol (the code below refers to this class as the "DefaultCoordinator"). This class is what will act as our delegate and actually execute our navigation code. VC #2 doesn't do anything in terms of navigation - it just exists here. The flow of things is VC #1 -> call method in navigation event handler and pass it restaurant object -> navigation event handler creates instance of VC #2, passes restaurant object over to it and pushes it onto the nav stack. Let's check out the code: 

class Restaurant: NSObject {
    var restoName: String
    var restoPic: UIImageView
    
    init (name: String, picture: UIImageView) {
        self.restoName = name
        self.restoPic = picture
        super.init()
    }
}

protocol RestaurantTableViewControllerDelegate:class {
    func RestoTableViewDidSelectRestaurant(restaurant: Restaurant)
}

class RestaurantListVC: UITableViewController {
    weak var navigationDelegate: RestaurantTableViewControllerDelegate?
    private var restaurants = [Restaurant] ()
    
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let selectedResto = restaurants[indexPath.row]
        navigationDelegate?.RestoTableViewDidSelectRestaurant(selectedResto)
    }
}

class DefaultCoordinator: RestaurantTableViewControllerDelegate {
    weak var navigationController: UINavigationController?
    
    func RestoTableViewDidSelectRestaurant(restaurant: Restaurant) {
        let VC = SingleRestaurantVC (restaurant: restaurant)
        navigationController?.pushViewController(VC, animated: true)
    }
}

class SingleRestaurantVC: UITableViewController {
    private var singleRestaurant: Restaurant?
    
    init (restaurant: Restaurant) {
        self.singleRestaurant = restaurant
        super.init(style: .Plain)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

As you can see, the navigation code for the VC we are navigating away from is minimal. The DefaultCoordinator (which is acting as our navigation event handler) can conform to multiple protocols declared by multiple VCs - all navigation logic can be abstracted away and handled by the DefaultCoordinator. The (potential) downside is that once an app gets very complex with dozens of navigation options, it may be nicer to see the destination VC right there in the "from" VC (instead of tucked away in this coordinating class).

There are so many ways to reduce the amount of code in a VC - it's worth experimenting with. I find that it's easier for me to look at view controllers very conceptually instead of worrying about code involved - this lets me make up a sort of "map" in my head that allows for me to be more free-thinking in my architecture choices.