Chapter    5

Pulling It All Together

Thus far in this book, you have learned about many discrete mobile topics and created more than ten Xcode projects. Those topics were purposely implemented in individual Xcode projects with few classes. In Chapter 3, you learned the top-down development approach of using the storyboard to break the whole app into model-view-controller (MVC) content view and view controller pairs. In Chapter 4, you learned how to port smaller individual components, including user interface (UI), create read update delete (CRUD), and remote operations, piece by piece. However, all those topics were designed to be self-contained without dependencies so they could serve as independent instructions.

In the real programming world, it is the combination of features and use cases that makes your app useful and entertaining. You will surely need to understand more than one topic to complete a meaningful app. In this chapter, you will implement a simple but meaningful Swift app from start to finish using the mapping topics from Chapters 3 and 4. Nothing will be new; you will repeat the same top-down development approach as you have been using and implement one piece at a time.

Perhaps there is one thing new that I have not yet mentioned explicitly: which piece should be developed first and then what comes next. For any app, including web apps, you should go through the same thought process: you should decide the dependencies of the pieces and try to reduce the dependencies along the way. After all, there is really no right or wrong way to do it.

Again, to learn by example, your goal is to implement a RentalROI iOS app. Figure 5-1 shows the completed iOS app you will be creating.

9781484209325_Fig05-01a.jpg

9781484209325_Fig05-01b.jpg

9781484209325_Fig05-01c.jpg

9781484209325_Fig05-01d.jpg

Figure 5-1. RentalROI screens

This RentalROI app performs the following tasks:

  • Every time a user enters new rental property parameters, user input is saved using SharedPreferences.
  • The amortization schedule is calculated on the remote server. The iOS client simply calls the remote service to get the amortization schedules and stores the data locally.
  • If a saved amortization schedule exists, the app uses it instead of making a remote service call.

 Note  This is just for exercise purposes. It would be better to calculate the amortization schedules locally without using the remote service—then you wouldn’t need to persist the result.

First, you’ll create a new Xcode project using the Single View Application template; name it RentalROI. You will be following the same porting approaches you used in Chapters 3 and 4.

Structure Your App

As usual, use top-down approach starting with storyboard tasks. Your first step is to create the Xcode storyboard as instructed in Chapter 3.

  • Draw the storyboard scenes for each content view and connect UI widgets to the custom UIViewController that pairs with the storyboard scenes.
  • Choose a navigation pattern and connect the storyboard scenes together with segues.

This will result in a runnable iOS app with all the content views and the view controller classes’ skeletons connected using the appropriate screen navigation pattern.

Draw Storyboard Scenes

You can clearly see four content views in Figure 5-1, and you will need to draw four storyboard scenes in Main.storyboard, covered next.

Edit Text Screen

In no particular order, let’s start with the simplest one, the second screen in Figure 5-1. You want to add a UITextField to the storyboard scene (see Chapter 4 for the detailed instructions).

  1. Drag a UITextField from the Object Library to the view and update its attributes in the Attributes Inspector, as shown in Figure 5-2.

    9781484209325_Fig05-02.jpg

    Figure 5-2. EditTextView storyboard scene

  2. Center it vertically and add some space to the leading and trailing spaces (for example, enter 20).
  3. Create a bare-bones Swift EditTextViewController class that extends from UIViewController (see Listing 5-1).
  4. To pair with the storyboard scene, enter the custom class name in the Identity Inspector.
  5. In the Connections Inspector, connect the delegate outlet and New Referencing Outlet to your code (see Chapter 4 for step-by-step operations).

Listing 5-1. EditTextViewController

import UIKit

class EditTextViewController : UIViewController, UITextFieldDelegate {
  @IBOutlet weak var mEditText: UITextField!
}

Rental Property Info Screen

Continue drawing the next content view in no particular order (for example, draw the Property screen, shown in Figure 5-1). To achieve the list view L&F, you should use UITableViewController (see Chapter 3 for the detailed instructions).

  1. Drag a Table View Controller from the Object Library and drop it onto the storyboard editor to create a storyboard scene.
  2. Select the Table View object and set the Style attribute to Grouped in the Attributes Inspector (see Figure 5-3).

    9781484209325_Fig05-03.jpg

    Figure 5-3. Creating a Table View scene

  3. Select the Table View Cell object and update the attributes in the Attributes Inspector (see Figure 5-4).

    9781484209325_Fig05-04.jpg

    Figure 5-4. Right Detail, Table View Cell

    1. Style: Select Right Detail.
    2. Identifier: Enter aCell.
    3. Create a bare-bones Swift RentalPropertyViewController class that extends from UITableViewController (see Listing 5-3). To pair with the storyboard scene, enter the custom class name in the Identity Inspector.

Listing 5-2. RentalPropertyViewController

import UIKit
class RentalPropertyViewController : UITableViewController {
  
}

Amortization Screen

Continue drawing the next content view in no particular order (for example, the Amortization screen, shown in Figure 5-1). It shows a list of amortization items, and a natural choice for this is UITableViewController.

  1. Drag a Table View Controller from the Object Library and drop it onto the storyboard editor to create a storyboard scene.
  2. Select the Table View Cell. In the Attributes Inspector, update the following attributes:
    1. Style: Select Subtitle.
    2. Identifier: Enter aCell.
  3. Create a bare-bones Swift AmortizationViewController class that extends from UITableViewController (see Listing 5-3). To pair with the storyboard scene, enter the custom class name in the Identity Inspector.

Listing 5-3. AmortizationViewController

import UIKit
class AmortizationViewController: UITableViewController {
  
}

Monthly Details Screen

Move on to drawing the last content view, Monthly Details (shown in Figure 5-1). It has a listlike look and feel but does not really show list items. You can choose to implement this screen with labels for each line items in iOS, or you can choose to use a UITableView, as in RentalPropertyViewController. There is actually a better choice, though: using UITableViewController with static cells, with one static cell for each line.

  1. Drag a Table View Controller from the Object Library and drop it onto the storyboard editor to create a storyboard scene.
  2. Select the Table View to update the attributes in the Attributes Inspector, as shown in Figure 5-5.
    1. Content: Select Static Cells.
    2. Sections: Enter 2.
    3. Style: Select Grouped.

      9781484209325_Fig05-05.jpg

      Figure 5-5. Static Cells Table View

  3. The Monthly Details screen contains Mortgage Payment and Investment sections. You need to update the section title and add Table View Cell objects to both sections, as shown in Figure 5-6.

    9781484209325_Fig05-06.jpg

    Figure 5-6. Two sections in the Monthly Details screen

  4. To update the section title, select the Table View section and update the Header attributes in the Attributes Inspector:
    1. Section 1: Enter Mortgage Payment.
    2. Section 2: Enter Investment.
  5. Since all the Table View Cells in this view are designed to have the same style, it is easier just to create one and duplicate it. You may keep the first Table View Cell and delete the rest.
    1. Select the Table View Cell and update the Style value to Right Detail.
    2. Select the Mortgage Payment section and update the number of rows to 6.
    3. You need three Table View Cells for Section 2. You may repeat the preceding steps or copy and paste in the storyboard editor.
    4. Update all the Table View Cell titles, as shown in Figure 5-6.
  6. Create a bare-bones Swift MonthlyTermViewController class that extends from UITableViewController. To pair with the storyboard scene, enter the custom class name in the Identity Inspector.
  7. Open the Assistant Editor and connect the first Table View Cell left text label and each Table View Cell right detail label, respectively, to your code’s IBOutlet properties, as shown in Listing 5-4.

Listing 5-4. MonthlyTermViewController IBOutlet Properties

import UIKit
class MonthlyTermViewController : UITableViewController {
  
  @IBOutlet weak var mPaymentNo: UILabel!
  @IBOutlet weak var mTotalPmt: UILabel!
  @IBOutlet weak var mPrincipal: UILabel!
  @IBOutlet weak var mInterest: UILabel!
  @IBOutlet weak var mEscrow: UILabel!
  @IBOutlet weak var mAddlPmt: UILabel!
  @IBOutlet weak var mBalance: UILabel!
  @IBOutlet weak var mEquity: UILabel!
  @IBOutlet weak var mCashInvested: UILabel!
  @IBOutlet weak var mRoi: UILabel!
}

Figure 5-7 depicts the results of the storyboard scenes’ tasks.

9781484209325_Fig05-07.jpg

Figure 5-7. Four RentalROI scenes

Choose a Screen Navigation Pattern

When choosing appropriate navigation patterns, you will get good ideas from using wireframes. You normally need more than one pattern, such as a navigation stack plus navigation tabs. In this RentalROI app, you want to be able go back to a previous scene from Monthly Details to Amortization List to Property. The popular navigation stack navigation pattern is perfect for this intended behavior (see Chapter 3 for more information). For going to and from the Edit Text scene, you can choose a different navigation pattern that shows a stronger relationship to the originating context. Dialog or the iOS pop-over is the best choice (see Chapter 3).

Your immediate mission is to add the navigation patterns and draw storyboard segues to connect all the storyboard scenes to one another. Figure 5-8 shows the final storyboard with all the scenes connected.

9781484209325_Fig05-08.jpg

Figure 5-8. RentalROI-connected scenes

Continue with the storyboard tasks by doing the following (see Chapter 3 for more detailed instructions):

  1. Select RentalPropertyViewController in the storyboard editor and embed it in a UINavigationController (see Figure 3-33 in Chapter 3 for step-by-step instructions).
    1. Make sure Is Initial View Controller is checked in the Navigation Controller’s Attributes Inspector.
    2. Select the Navigation Item in RentalPropertyViewController to update the Title attribute to Property in the Attributes Inspector.
    3. Add a right BarButtonItem to the Property Navigation Item in RentalPropertyViewController. Also, update the button’s Title attribute to Schedule in the BarButtonItem Attributes Inspector.
    4. Connect the Schedule BarButtonItem action outlet in the Connections Inspector to your code, such as doSchedule(...).
  2. Connect a Manual Segue from RentalPropertyViewController to AmortizationViewController.
    1. Segue: Show (for example, Push)
    2. Identifier: AmortizationTable
  3. Connect a Manual Segue from RentalPropertyViewController to EditTextViewController.
    1. Segue: Present As Popover
    2. Identifier: EditText
    3. Anchor: Table View
    4. Directions: none (uncheck all)
  4. Connect a Manual Segue from AmortizationViewController to MonthlyTermViewController.
    1. Segue: Show (for example, Push)
    2. Identifier: MonthlyTerm
  5. Add Navigation Item to AmortizationViewController and MonthlyTermViewController, as shown in Figure 5-8.
    1. Drag a Navigation Item from the Object Library and drop it onto the controller in the storyboard document outline.
    2. Update the Navigation Item titles, respectively (for example, Amortization and Payment).
  6. Since you are not showing the EditTextViewController with the navigation pattern, EditTextViewController doesn’t have a Navigation Item for Title or BarButtonItem like the rest of the storyboard scenes. You can draw a UINavigationBar as usual by dragging one from the Object Library, or, more commonly, you can simply embed it in another UINavigationController.
    1. Select the view controller and select Editor image Embedded In image Navigation Controller from the Xcode Editor menu.
    2. Add a right BarButtonItem. Set the Title attribute to Save and connect the action outlet to your code, such as doSave(...).
    3. Add a left BarButtonItem. Update the Title attribute to be Cancel and connect the action outlet to your code, such as doCancel(...).

You should have a storyboard with all scenes connected with segues, as shown in Figure 5-8.

Business Object

In object oriented programming, you create classes to present the business domain. You define properties to encapsulate the object attributes and methods for behaviors in the classes. After finishing the storyboard tasks, you naturally gain a better understanding of the business domain and the attributes you need to deal with. You don’t have to discover all of them at once. You simply create the business object when you discover any. For this RentalROI app, you only need a class that presents the rental property. Let’s create a Swift class skeleton for the RentalProperty business object class first (see Listing 5-5).

Listing 5-5. RentalProperty.swift Skeleton

import Foundation
public class RentalProperty {
  
}

For its attributes and behaviors, you might see some from the storyboard immediately, but you normally would not know them all in the beginning. You will discover them along the way and just fill in the blanks when you discover any.

Application Resources

Like with any GUI apps, you need images or digital assets to dress up the app. You also want to externalize strings to avoid coding these display texts directly. Do the following to implement the application resources (see Chapter 4) in this Xcode project:

  1. All the display text needs to be externalized, as shown in Listing 5-6.
    1. In Xcode, select the Supporting Files folder to create a new file (flower.jpg+N) in it, and follow the on-screen instructions to select iOS image Resource image Strings File. Name the file Localizable.strings.
    2. Define key-value pairs. You will access the value by the key from your Swift code.

      Listing 5-6. Externalized Text Translation

      "app_name" = "RentalROI";
      "label_schedule" = "Schedule";
      "label_property" = "Property";
      "label_Amortization" = "Amortization";
      "label_monthlydetails" = "Monthly Details";
      "button_next" = "Next";

      /* RentalPropertyView */
      "mortgage" = "MORTGAGE";
      "operations" = "OPERATIONS";
      "purchasePrice" = "Purchase Price";
      "downPayment" = "Down Payment %";
      "loanAmount" = "Loan Amount";
      "interestRate" = "Interest Rate %";
      "mortgageTerm" = "Mortgage Term (Yr.)";
      "escrowAmount" = "Escrow Amount";
      "extraPayment" = "Extra Payment";
      "expenses" = "Expenses";
      "rent" = "Rent";

      /* EditTextView */
      "save" = "Save";
      "editTextSize" = "15";

      /* Monthly Details */
      "MortgagePayment" = "MORTGAGE PAYMENT";
      "no" = "No.";
      "Principal" = "Principal";
      "interest" = "Interest";
      "escrow" = "Escrow";
      "addlPayment" = "Add'l Payment";
      "mortgageBalance" = "Mortgage Balance";
      "equity" = "Equity";
      "cashInvest" = "Cash Investment";
      "roi" = "ROI";
  2. You should use the Xcode assets catalog to manage images. In this app, you need only one image: the application icon, ic_launcher.png. You can do it in the same way for any other images.
    1. Create ic120.png, ic180.png, ic76.png, and ic152.png.
    2. Select Images.xcassets and AppIcon in the Xcode assets catalog and drag the four files you just made into the appropriate slots, as shown in Figure 5-9: ic120.png for iPhone 2x, ic180.png for iPhone 3x, ic76.png for iPad 1x, and ic152.png for iPad 2x.The image resolution must match exactly or Xcode will give you warnings.

9781484209325_Fig05-09.jpg

Figure 5-9. Xcode project AppIcon

Implement Piece by Piece

You have all the Swift class files, storyboards, and external assets in place. Your next step is to fill in the class by implementing methods. You will start to see how your code works piece by piece!

Normally, you would discover the methods by analyzing the intended behaviors per use cases. For the purposes of this book, I will go over the existing classes one at a time, focusing on iOS SDK topics instead.

RentalProperty

RentalProperty is the model class that encapsulates the business knowledge in terms of property and mortgage attributes, and so on. Since we going to persist these attributes, I chose to implement the persistent storage logic directly inside the model class. Let’s start implementing the RentalProperty Swift class.

  1. The Swift stored properties represent the application states (see Listing 5-7). All the attributes can be visualized in the Rental Property scene in the storyboard.
  2. This app deals with one rental property at a time. The singleton pattern is commonly used in object-oriented code. This is how you do it in Swift:
    1. Declare a shared instance as a private static type property in the inner struct.
    2. Make the initializer private to prevent multiple instances from being created.
    3. Create a public class method to access the shared instance inside the inner strut.

      Listing 5-7. The RentalProperty Class Singleton Implementation

      import Foundation

      public class RentalProperty {

        // 1. Swift stored properties to keep the application states
        var purchasePrice = 0.0;
        var loanAmt = 0.0;
        var interestRate = 5.0;
        var numOfTerms = 30;
        var escrow = 0.0;
        var extra = 0.0;
        var expenses = 0.0;
        var rent = 0.0;
        
        struct MyStatic {
          static let KEY_AMO_SAVED = "KEY_AMO_SAVED";
          static let KEY_PROPERTY = "KEY_PROPERTY";
          // 2a. singleton impl
          private static var _sharedInstance = RentalProperty()
        }

        // 2b. singleton impl, private initializer
        private init() {

        }
        
        // 2c. singleton accessor
        class func sharedInstance() -> RentalProperty {
          return MyStatic._sharedInstance
        }
        ...
      }
  3. Use NSUserDefaults to create the following utility methods in the RentalProperty.swift methods, as shown in Listing 5-8 (see Chapter 4).

    Listing 5-8. Create Save Data Utility Methods

    class RentalProperty {
      ...
      let userDefaults = NSUserDefaults.standardUserDefaults()
      private func saveUserdefault(data:AnyObject, forKey:String) -> Bool{
        userDefaults.setObject(data, forKey: forKey)
        return userDefaults.synchronize()
      }
      
      private func retrieveUserdefault(key: String) -> AnyObject? {
        var obj: AnyObject? = userDefaults.objectForKey(key)
        return obj
      }
      
      private func deleteUserDefault(key: String) {
        self.userDefaults.removeObjectForKey(key)
      }
      ...
  4. Implement the load() method that loads the saved RentalProperty object from storage, as shown in Listing 5-9.

    Listing 5-9. Loading RentalProperty Object from Storage

    class RentalProperty {
      ...
      func load() -> Bool {
        var data = retrieveUserdefault(MyStatic.KEY_PROPERTY) as NSDictionary?
        if var jo = data {
          self.purchasePrice = jo["purchasePrice"] as Double
          self.loanAmt = jo["loanAmt"] as Double
          self.interestRate = jo["interestRate"] as Double
          self.numOfTerms = jo["numOfTerms"]  as Int
          self.escrow = jo["escrow"] as Double
          self.extra = jo["extra"] as Double
          self.expenses = jo["expenses"] as Double
          self.rent = jo["rent"] as Double
          return true;
          
        } else {
          return false
        }
      }
      ...
  5. Implement the save() method that persists the RentalProperty instance in storage, as shown in Listing 5-10.

    Listing 5-10. Saving RentalProperty Object

    class RentalProperty {
      ...
      func save() -> Bool {
        var jo : [NSObject : AnyObject] = [
        "purchasePrice": purchasePrice,
        "loanAmt" : loanAmt,
        "interestRate" : interestRate,
        "numOfTerms" : Double(numOfTerms),
        "escrow" : escrow,
        "extra" : extra,
        "expenses" : expenses,
        "rent" : rent]
        
        return self.saveUserdefault(jo, forKey: MyStatic.KEY_PROPERTY)
      }
      ...
  6. Implement the getSavedAmortization() and saveAmortization() methods to retrieve and save the amortization schedule array, as shown in Listing 5-11.

Listing 5-11. Retrieve Amortization Schedule Array from Persistent Storage

class RentalProperty {
  ...
  private func getAmortizationPersistentKey() -> String {
    var aKey = String(format: "%.2f-%.3f-%d-%.2f-%.2f", self.loanAmt, self.interestRate, self.numOfTerms, self.escrow, self.extra);
    return aKey;
  }
  
  func getSavedAmortization() -> NSArray? {
    var savedKey = retrieveUserdefault(MyStatic.KEY_AMO_SAVED) as String?
    var aKey = self.getAmortizationPersistentKey()
    if let str = savedKey {
      if(str.utf16Count > 0 && str == aKey) {
        var jo = retrieveUserdefault(str) as NSArray?
        return jo
      }
    }
    return nil
  }
  
  func saveAmortization(data: NSArray) -> Bool {
    var aKey = self.getAmortizationPersistentKey()
    saveUserdefault(aKey, forKey: MyStatic.KEY_AMO_SAVED)
    return saveUserdefault(data, forKey: aKey)
  }
  ...

EditTextViewController

The purpose of this controller is to prompt users to enter text and return the user input to its presenting view controller. Although this EditTextView storyboard scene looks simple, it demonstrates a frequent usage in iOS apps: the presenting view controller passes data to the presented view controller (see Chapter 3). Also, frequently, the presented view controller returns data to the presenting view controller, introduced next.

Return Data to the Presenting View Controller

To return data to the presenting view controller, the iOS SDK uses a callback to the delegate pattern in many system classes. You will implement the same design pattern for your purposes. Do the following:

  1. Define the EditTextViewControllerDelegate delegate protocol, as shown in Listing 5-12.
    1. Conventionally, you declare this protocol in the same Swift file where the presented view controller is declared.
    2. Conventionally, the first argument in the callback methods are the presented view controller.

      Listing 5-12. The Callback Protocol in EditTextViewController.swift File

      // Callback protocol
      protocol EditTextViewControllerDelegate {
        func onTextEditSaved(vc: EditTextViewController, data: String);
        func onTextEditCanceled(vc: EditTextViewController);
      }

      class EditTextViewController : UIViewController, UITextFieldDelegate {
        ...
  2. Implement the presented view controller, as shown in Listing 5-13.
    1. Implement stored properties to receive data from the presenting view controller including the delegate.
    2. To return the data to the presenting view controller, use the delegate stored property to deliver the return data via the callback method. The doSave() code returns the text entered in the UITextField when the Save button is clicked.

      Listing 5-13. Java Fields to Swift Stored Properties

      class EditTextViewController : ... {
        ...
        // a.
        var tag: NSIndexPath!
        var header = ""
        var text = ""
        var delegate: EditTextViewControllerDelegate?
        ...
        @IBAction func doSave(sender: AnyObject) {
          var returnText = self.mEditText.text
          if(delegate != nil) {
            // b. return data to the delegate
            delegate!.onTextEditSaved(self, data: returnText)
          }
        }
        
        @IBAction func doCancel(sender: AnyObject) {
          if(delegate != nil) {
            delegate!.onTextEditCanceled(self)
          }
          ...
  3. Listing 5-14 depicts the code in RentalPropertyViewController using EditTextViewController to get user inputs.
    1. Declare that you are implementing the EditTextViewControllerDelegate protocol.
    2. Assign itself to be the callback delegate, the same way as you pass data to the presented view controller. (See Chapter 3.)
    3. Implement the EditTextViewControllerDelegate callback methods to receive the data returned from the presented view controller.

Listing 5-14. Presenting ViewController

// 3a implement protocol
class RentalPropertyViewController : UITableViewController, EditTextViewControllerDelegate  {
  ...
  // You will often want to do a little preparation before segue navigation
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var identifier = segue.identifier
    if identifier == "EditText" {
      var indexPath = sender as NSIndexPath
      
      var presentedVc =  (segue.destinationViewController as UINavigationController).topViewController as EditTextViewController
      var cell = tableView.cellForRowAtIndexPath(indexPath)!

      presentedVc.tag = indexPath
      presentedVc.header = cell.textLabel!.text!
      presentedVc.text = cell.detailTextLabel!.text!

      // 3b. assign self to the callback
      presentedVc.delegate = self
    } else { // AmortizationTable segue
      var toFrag = segue.destinationViewController as AmortizationViewController
      toFrag.monthlyTerms = sender as NSArray // TODO: a temp compilation error
    }
  }
  
  // 3c. delegate protocol to receive data via callback from presented VC
  func onTextEditSaved(vc: EditTextViewController, data: String) {
    self.dismissViewControllerAnimated(true, completion: nil)
    // TODO: use data
  }
  
  func onTextEditCanceled(vc: EditTextViewController) {
    self.dismissViewControllerAnimated(true, completion: nil)
  }
  ...
}

Handle Soft Keyboard

The rest of the code in EditTextViewController primarily handles a soft keyboard, aka on-screen keyboard. On iOS, you should use NSNotificationCenter to detect the keyboard shown or hidden and update the widget positions accordingly to accommodate the soft keyboard size. Listing 5-15 shows the completed EditTextViewController class implementation, including how to handle the soft keyboard.

  1. In viewDidLoad(), register the keyboard notification callback.
  2. Remove the notification callback before the view disappears.
  3. Shift the UITextField up or down according to the keyboard shown or hidden by updating NSLayoutConstraint.
  4. Use UITextField.becomeFirstResponder to show a soft keyboard programmatically.

Listing 5-15. EditTextViewController Completed Code

class EditTextViewController : UIViewController, UITextFieldDelegate {
  
  @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
  @IBOutlet weak var mEditText: UITextField!

  var tag : NSIndexPath!
  var header = ""
  var text = ""
  var delegate: EditTextViewControllerDelegate!
    
  override func viewDidLoad() {
    super.viewDidLoad()
    self.navigationItem.title = self.header
    mEditText.text = self.text
    
  // 1. Register keyboard shown notification
    NSNotificationCenter.defaultCenter().addObserver(self,
      selector: "keyboardAppeared:",
      name: UIKeyboardDidShowNotification, object: nil)
  }
  
  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    showKeyboard()
  }
  
  override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
  }
  
  override func viewDidDisappear(animated: Bool) {
    super.viewDidDisappear(animated)
    // 2. Remove keyboard notification
    NSNotificationCenter.defaultCenter().removeObserver(self,
      name: UIKeyboardDidShowNotification, object: nil)
  }
  
  @IBAction func doSave(sender: AnyObject) {
    var returnText = self.mEditText.text
    if(delegate != nil) {
      delegate.onTextEditSaved(self, data: returnText)
    }
  }
  
  @IBAction func doCancel(sender: AnyObject) {
    if(delegate != nil) {
      delegate.onTextEditCanceled(self)
    }
  }
  
  // 3. shift the text field up by changing the autolayout constraint
  func keyboardAppeared(notification: NSNotification) {
    println(">>keyboardAppeared")
    var keyboardInfo = notification.userInfo as NSDictionary!
    var kbFrame = keyboardInfo.valueForKey(UIKeyboardFrameBeginUserInfoKey) as NSValue
    var kbFrameRect: CGRect = kbFrame.CGRectValue()
    var keyboardH = min(kbFrameRect.size.width, kbFrameRect.size.height)
    UIView.animateWithDuration(0, animations: { () -> Void in
      
      if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
        self.bottomConstraint.constant = self.bottomConstraint.constant + keyboardH/self.bottomConstraint.multiplier
      }
      
      }, completion: {(b) -> Void in
        self.mEditText.selectAll(self);
    })
  }
  
  // 4. show keyboard
  private func showKeyboard() {
    self.mEditText.becomeFirstResponder()
  }
  
  private func hideKeyboard() {
    self.mEditText.endEditing(true)
  }
}

RentalPropertyViewController

When the app is launched, this is the first content view. The purpose of this view controller is to collect user input and present the amortization schedule.

Listing 5-16 shows the typical ViewController life-cycle methods and UI code.

  1. You don’t have to implement all the view life-cycle methods. (See Chapter 3 for details.)
    1. Call viewDidLoad() only once. Use it for initialization code.
    2. The viewDidAppear() method is called every time the view appears.
    3. Don’t forget call super().
  2. Implement the TableView data source for rendering the TableView (see Chapter 3 for details).
  3. When user selects each table row, present EditTextViewController for editing the text.
    1. Use a Manual Segue to present the EditTextViewController.
    2. Pass the current value to the presented view controller (see Chapter 3).
    3. To use the data from EditTextViewController, implement the EditTextViewControllerDelegate.onTextEditSaved(...) method.

      Listing 5-16. RentalPropertyViewController Completed Implementation

      import UIKit

      class RentalPropertyViewController : UITableViewController, EditTextViewControllerDelegate  {
        ...
        var _property = RentalProperty.sharedInstance()
        var _savedAmortization: NSArray?
        
        // 1a. Lifecycle callback
        override func viewDidLoad() {
          super.viewDidLoad()
          _property.load();
        }
        
        // 1b. Lifecycle callback
        override func viewDidAppear(animated: Bool) {
          super.viewDidAppear(animated)
          self.navigationItem.title = "Property"
        }
        
        // button action handle
        @IBAction func doSchedule(sender: AnyObject) {
            // TODO
        }
        
        // 2. implement tableview datasource
        override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
          return 2   // 2a.
        }
        
        // 2b.
        override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
          if section == 0 {
            return NSLocalizedString("mortgage", comment: "")
          } else {
            return NSLocalizedString("operations", comment: "")
          }
        }
        
        // 2c
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
          if section == 0 {
            return 7
          } else {
            return 2
          }
        }
        
        // 2d
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
          var cell = tableView.dequeueReusableCellWithIdentifier("aCell", forIndexPath: indexPath) as UITableViewCell
          var textLabel = cell.textLabel!
          var detailTextLabel = cell.detailTextLabel!
          
          var pos = (indexPath.section, indexPath.row)
          
          switch (pos) {
          case (0, 0):
            textLabel.text = NSLocalizedString("purchasePrice", comment: "")
            detailTextLabel.text = NSString(format: "%.0f", _property.purchasePrice);
          case (0, 1):
            textLabel.text = NSLocalizedString("downPayment", comment: "")
            
            if (_property.purchasePrice > 0) {
              var down = (1 - _property.loanAmt / _property.purchasePrice) * 100.0;
              detailTextLabel.text = NSString(format: "%.0f", down);
              
              if (_property.loanAmt == 0 && down > 0) {
                _property.loanAmt = _property.purchasePrice * (1 - down / 100.0);
              }
            } else {
              detailTextLabel.text = "0";
            }
          case (0, 2):
            textLabel.text = NSLocalizedString("loanAmount", comment: "")
            detailTextLabel.text = NSString(format: "%.2f", _property.loanAmt)
          case (0, 3):
            textLabel.text = NSLocalizedString("interestRate", comment: "")
            detailTextLabel.text = NSString(format: "%.3f", _property.interestRate)
          case (0, 4):
            textLabel.text = NSLocalizedString("mortgageTerm", comment: "")
            detailTextLabel.text = NSString(format: "%d", _property.numOfTerms)
          case (0, 5):
            textLabel.text = NSLocalizedString("escrowAmount", comment: "")
            detailTextLabel.text = NSString(format: "%.0f", _property.escrow)
          case (0, 6):
            textLabel.text = NSLocalizedString("extraPayment", comment: "")
            detailTextLabel.text = NSString(format: "%.0f", _property.escrow);
          case (1, 0):
            textLabel.text = NSLocalizedString("expenses", comment: "")
            detailTextLabel.text = NSString(format: "%.0f", _property.expenses);
          case (1, 1):
            textLabel.text = NSLocalizedString("rent", comment: "")
            detailTextLabel.text = NSString(format: "%.0f", _property.rent);
            
          default:
            break;
          }
          
          return cell
        }

        // tableView delegate
        override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
          // 3a.
          self.performSegueWithIdentifier("EditText", sender: indexPath)
        }
        
        // You will often want to do a little preparation before segue navigation
      override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
          var identifier = segue.identifier
          if identifier == "EditText" {
            var indexPath = sender as NSIndexPath
            
            var presentedVc =  (segue.destinationViewController as UINavigationController).topViewController as EditTextViewController
            var cell = tableView.cellForRowAtIndexPath(indexPath)!

            // 3b.
            presentedVc.tag = indexPath
            presentedVc.header = cell.textLabel!.text!
            presentedVc.text = cell.detailTextLabel!.text!
            presentedVc.delegate = self
          } else { // AmortizationTable segue
            // 4e.
            // TODO: coming next
          }
        }
        
        // 3c. delegate protocol to receive data from presented VC
        func onTextEditSaved(vc: EditTextViewController, data: String) {
          self.dismissViewControllerAnimated(true, completion: nil)
          
          switch (vc.tag.section, vc.tag.row) {
          case (0,0):
            _property.purchasePrice = (data as NSString).doubleValue;
            var indexPath = NSIndexPath(forRow: 0, inSection: 0)
            var percent = tableView.cellForRowAtIndexPath(indexPath)!.detailTextLabel!.text!
            var down = (percent as NSString).doubleValue
            if (_property.purchasePrice > 0 && _property.loanAmt == 0 && down > 0) {
              _property.loanAmt = _property.purchasePrice * (1 - down / 100.0)
            }
            
            break;
          case (0,1):
            var percentage = (data as NSString).doubleValue / 100.0;
            _property.loanAmt = _property.purchasePrice * (1 - percentage);
            break;
          case (0,2):
            _property.loanAmt = (data as NSString).doubleValue;
            break;
          case (0,3):
            _property.interestRate = (data as NSString).doubleValue;
            break;
          case (0,4):
            _property.numOfTerms = (data as NSString).integerValue;
            break;
          case (0,5):
            _property.escrow = (data as NSString).doubleValue;
            break;
          case (0,6):
            _property.extra = (data as NSString).doubleValue;
            break;
          case (1,0):
            _property.expenses = (data as NSString).doubleValue;
            break;
          case (1,1):
            _property.rent = (data as NSString).doubleValue;
            break;
            
          default:
            break;
          }
          tableView.reloadData()
          _property.save();
        }
        
        func onTextEditCanceled(vc: EditTextViewController) {
          self.dismissViewControllerAnimated(true, completion: nil)
        }
      }
  4. When the Schedule UIBarButtonItem is selected, the IBAction doAmortization() method does the following (see Listing 5-17):
    1. Check whether the amortization schedule is already saved locally.
    2. To get data from a remote RESTFul service, use NSURLConnection.sendAsynchronousRequest.
    3. Save the results from the remote service.
    4. Use a Manual Segue to present AmortizationViewController, which will render the schedules in it in content view.
    5. Pass the amortized monthly terms to the presented view controller (see Chapter 3).
    6. Handle errors for remote service calls (see UIAlertController in Chapter 3 for details).

Listing 5-17. doAmortization(...)

class RentalPropertyViewController : ... {
  ...
  // define constants
  struct MyStatic {
    private static let URL_service_tmpl = "http://www.pdachoice.com/ras/service/amortization?loan=%.2f&rate=%.3f&terms=%d&extra=%.2f&escrow=%.2f"
    private static let KEY_DATA = "data"
    private static let KEY_RC = "rc"
    private static let KEY_ERROR = "error"
  }
  
  // button action handle
  @IBAction func doSchedule(sender: AnyObject) {

    // 4a
    _savedAmortization = _property.getSavedAmortization();
    if (_savedAmortization != nil) {
      performSegueWithIdentifier("AmortizationTable", sender: _savedAmortization!)
    } else {
      // 4b
      var url = NSString(format: MyStatic.URL_service_tmpl, _property.loanAmt, _property.interestRate, _property.numOfTerms, _property.extra, _property.escrow)
      UIApplication.sharedApplication().networkActivityIndicatorVisible = true
      
      var urlRequest = NSMutableURLRequest(URL: NSURL(string: url)!)
      urlRequest.HTTPMethod = "GET"
      urlRequest.setValue("text/html",forHTTPHeaderField: "accept")
      NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(),
        completionHandler: {(resp: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
          NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(),
            completionHandler: {(resp: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
              UIApplication.sharedApplication().networkActivityIndicatorVisible = false
              var errMsg: String?
              if error == nil {
                var statusCode = (resp as NSHTTPURLResponse).statusCode
                if(statusCode == 200) {
                  var str = NSString(data: data, encoding: NSUTF8StringEncoding)
                  var parseErr: NSError?
                  var json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &parseErr) as NSArray?
                  if parseErr == nil {
                    // 4c. save
                    self._property.saveAmortization(json!)
                    // 4d. segue navigation
                    self.performSegueWithIdentifier("AmortizationTable", sender: json!)
                    return
                  } else {
                    errMsg = parseErr?.debugDescription
                  }
                } else {
                  errMsg = "HTTP RC: (statusCode)"
                }
              } else {
                errMsg = error.debugDescription
              }
              
              // 4f. simple error handling
              var alert = UIAlertController(title: "Error", message: errMsg, preferredStyle: UIAlertControllerStyle.Alert)
              var actionCancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel,
                handler: {action in
                  // do nothing
              })
              alert.addAction(actionCancel)
              self.presentViewController(alert, animated: true, completion: nil)
          })
      })
    }
  }
  
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var identifier = segue.identifier
    if identifier == "EditText" {
      // 3b.
      ...
    } else { // AmortizationTable segue
      // 4e.
      var toVc = segue.destinationViewController as AmortizationViewController
      toVc.monthlyTerms = sender as NSArray // a temp error to be fixed next
    }
  }
...

You should get a temporary compilation error in 4e because you haven’t defined the monthlyTerms stored properties yet. You will do that next.

AmortizationViewController

Let’s move on to AmortizationViewController. The sole purpose of this view is to render the amortization items in a TableView. Listing 5-18 shows the completed implementation.

  1. The amortization schedule results are obtained and passed by the presenting view controller, RentalPropertyViewController (Listing 5-17 step 4b and 4f).
  2. Implement the UITableViewDataSource methods (see Chapter 3).
  3. When a particular month is selected, present the monthly details in MonthlyTermViewController using a segue navigation.
  4. Pass the monthly term data to the presented view controller.

Listing 5-18. AmortizationViewController Class, Completed Code

import UIKit
class AmortizationViewController: UITableViewController {

  // 1. From the presenting view controller
  var monthlyTerms: NSArray!
  
  override func viewDidLoad() {
    super.viewDidLoad()
  }
  
  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return monthlyTerms.count
  }
  
  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    var cell = tableView.dequeueReusableCellWithIdentifier("aCell") as UITableViewCell!
    var textLabel = cell.textLabel!
    var detailTextLabel = cell.detailTextLabel!
    var pos = indexPath.row
    var monthlyTerm = monthlyTerms[pos] as NSDictionary
    var pmtNo = monthlyTerm["pmtNo"] as Int
    var balance0 = monthlyTerm["balance0"] as Double
    textLabel.text = NSString(format: "%d $%.2f", pmtNo, balance0)
    
    var interest = monthlyTerm["interest"] as Double
    var principal = monthlyTerm["principal"] as Double
    
    var property = RentalProperty.sharedInstance();
    var invested = property.purchasePrice - property.loanAmt + (property.extra * Double(pmtNo))
    var net = property.rent - property.escrow - interest - property.expenses
    var roi = net * 12 / invested
    
    detailTextLabel.text = NSString(format: "Interest: %.2f Principal: %.2f ROI: %.2f", interest, principal, roi * 100);
    
    return cell
  }
  
  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    self.navigationItem.title = NSLocalizedString("label_Amortization", comment: "")
  }
  
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
     // 3. Present MonthlyTerm view controller
     self.performSegueWithIdentifier("MonthlyTerm", sender: indexPath)
  }
  
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var vc = segue.destinationViewController as MonthlyTermViewController
    // 4. Pass data to the presented view controller
    var row = (sender! as NSIndexPath).row
    vc.monthlyTerm = monthlyTerms[row] as NSDictionary // TODO: a temp error
  }
}

This completes the whole AmortizationViewController Swift class implementation, with a temporarily compilation error in step 4. You will define the monthlyTerm stored property next.

MonthlyTermViewController

Let’s move on to MonthlyTermViewController. It renders the detailed information for the selected month, as shown in Listing 5-19.

  1. The monthlyTerm data is obtained from the presenting view controller, AmortizationViewController (Listing 5-18 step 4).
  2. Fill the IBOutlet UI widgets with data in the selected monthlyTerm stored property.

Listing 5-19. AmortizationViewController Class Completed Code

import UIKit
class MonthlyTermViewController : UITableViewController {
  
  @IBOutlet weak var mPaymentNo: UILabel!
  @IBOutlet weak var mTotalPmt: UILabel!
  @IBOutlet weak var mPrincipal: UILabel!
  @IBOutlet weak var mInterest: UILabel!
  @IBOutlet weak var mEscrow: UILabel!
  @IBOutlet weak var mAddlPmt: UILabel!
  @IBOutlet weak var mBalance: UILabel!
  @IBOutlet weak var mEquity: UILabel!
  @IBOutlet weak var mCashInvested: UILabel!
  @IBOutlet weak var mRoi: UILabel!

  // 1. From the presenting view controller
  var monthlyTerm: NSDictionary!
  
  override func viewDidLoad() {
    super.viewDidLoad()

    // 3. Fill the widget with data before view appears.
    var principal = self.monthlyTerm["principal"] as Double
    var interest = self.monthlyTerm["interest"] as Double
    var escrow = self.monthlyTerm["escrow"] as Double
    var extra = self.monthlyTerm["extra"] as Double
    var balance = (self.monthlyTerm["balance0"] as Double) - principal
    var paymentPeriod = self.monthlyTerm["pmtNo"] as Int
    var totalPmt = principal + interest + escrow + extra
    self.mTotalPmt.text = NSString(format: "$%.2f", totalPmt)
    self.mPaymentNo.text = NSString(format: "No. %d", paymentPeriod)
    self.mPrincipal.text = NSString(format: "$%.2f", principal)
    self.mInterest.text = NSString(format: "$%.2f", interest)
    self.mEscrow.text = NSString(format: "$%.2f", escrow)
    self.mAddlPmt.text = NSString(format: "$%.2f", extra)
    self.mBalance.text = NSString(format: "$%.2f", balance)
    
    var property = RentalProperty.sharedInstance();
    var invested = property.purchasePrice - property.loanAmt + (property.extra * Double(paymentPeriod))
    var net = property.rent - escrow - interest - property.expenses;
    var roi = net * 12 / invested
    
    self.mEquity.text = NSString(format: "$%.2f", property.purchasePrice - balance)
    self.mCashInvested.text = NSString(format: "$%.2f", invested)
    self.mRoi.text = NSString(format: "%.2f%% ($%.2f/mo)", roi * 100, net)
  }
}

This completes the whole MonthlyTermViewController Swift class implementation.

All the class translations are completed. Build and run the iOS RentalROI app and do some testing. Your app should look like Figure 5-1.

Summary

This chapter showed you how to apply the individual topics introduced in Chapters 3 and 4—such as creating master list details, creating drill-down navigation, exchanging data between view controllers, using basic UI widgets, saving data, and using remote services—all in one simple and meaningful app.

Although the RentalROI app is not complicated enough to show you all the topics in this book, the general iOS app development steps remain the same. You started with the Xcode storyboard and created view controllers to pair with the storyboard scenes. Then, you implemented storyboard segues to connect the view controller together. The result was a set of connected view controllers. With the storyboard in place, you started following the use case paths and discovering the business objects and methods, and the dots started connecting to each other.

When you encounter platform-specific SDK or topics, use this book’s table of contents to find the instructions that will guide you through the iOS SDK.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset