Chapter    5

Recap with a Case Study

Thus far in this book, you have covered many discrete Android-to-iOS mapping topics and created more than 10 Xcode projects. Those mapping topics were purposely implemented in individual Xcode projects with very few classes. In Chapter 3, you learned the top-down development approach using the storyboard to break the whole app into MVC-oriented content view and View Controller pairs. In Chapter 4, you learned how to port smaller individual components from the counterpart Android app, piece by piece. However, all those topics were designed to be self-contained without dependencies so they can 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 more than one mapping guideline to complete a meaningful app. In this chapter, you are going to port an existing Android app from start to finish using the mapping topics from Chapters 3 and 4. Nothing will be new; you are still going to 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 go first, and then what goes next. For any app, including the Android apps from which you are porting, you must go through the same thinking process. If you remember how you created the Android apps you are porting, that will be a great start because it will make your porting tasks to iOS more efficient. Otherwise, you will use the same thinking process that you normally use: decide the dependencies among the pieces and try to reduce the dependencies along the way. After all, there is really no absolutely right or wrong way. This is off our porting topic, but I think you will begin to understand my thinking process in this final exercise.

Again, to show and learn by example, your goal is to port an Android app, RentalROI, to iOS. Figure 5-1 shows the Android app you’re porting.

9781484204375_Fig05-01.jpg

Figure 5-1. Android RentalROI screens

This Android 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 Android 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.

You will port this Android app to iOS and preserve the design decisions already made.

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.

You can download the ADT project from http://pdachoice.com/bookassets/RentalROI-adt.zip.

First, you’ll create a new Xcode project using the Single View Application template, and give it the same name as the Android app: RentalROI. You will be following the same porting approaches you used in Chapters 3 (“Structure Your App”) and 4 (“Implement Piece by Piece”).

Structure Your App

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 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. You should use the counterpart ADT project as your live wireframe to create the iOS storyboard:

  1. In no particular order, let’s start with the simplest one, the EditTextViewFragment in the counterpart ADT project. The content view layout has only one EditText in the counterpart ADT project. You want to add a UITextField to the storyboard scene (see Chapter 4, “UITextField” for the detailed instructions).
    1. Drag a UITextField from Object Library to the View, and update its attributes in the Attributes Inspector as shown in Figure 5-2.

      9781484204375_Fig05-02.jpg

      Figure 5-2. EditTextView storyboard scene

    2. Center it vertically and add some space to the leading and trailing spaces (e.g., 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.

    Listing 5-1. EditTextViewController

    import UIKit

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

    Continue drawing the next content view in no particular order, (e.g., the Property screen). In the Android counterpart RentalPropertyViewFragment class, I used ListFragment to achieve the L&F. In iOS, your natural choice is UITableViewController (see Chapter 3, “UITableViewController” for the detailed instructions).

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

      9781484204375_Fig05-03.jpg

      Figure 5-3. Creating a Table View scene

    3. Select the Table View Cell and update the attributes in Attributes Inspector (see Figure 5-4):
      • Style: Select Right Detail.
      • Identifier: Enter aCell.

        9781484204375_Fig05-04.jpg

        Figure 5-4. Right Detail, Table View Cell

    4. Create a bare-bones Swift RentalPropertyViewController class that extends from UITableViewController (see Listing 5-2). To pair with the storyboard scene, enter the custom class name in the Identity Inspector.

    Listing 5-2. RentalPropertyViewController

    import UIKit
    class RentalPropertyViewController: UITableViewController {

    }
  2. Continue drawing the next content view in no particular order (e.g., the Amortization screen). The counterpart Android AmortizationViewFragment is a standard ListFragment. Again, in iOS your natural choice for this is UITableViewController.
    1. Drag a UITableViewController from 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:
      • Style: Select Subtitle.
      • Identifier: Enter aCell.
    3. Create a bare-bones Swift AmortizationViewController class that extends from UITableViewController (see Listing 5-3).
    4. To pair with the storyboard scene, enter the custom class name in the Identity Inspector.

      Listing 5-3. AmortizationViewController

      import UIKit
      class AmortizationViewController: UITableViewController {

      }
  3. Move on to the draw the last content view, Monthly Details. The counterpart Android MonthlyTermViewFragment layout looks like a ListView but actually was implemented with two TextViews and a decorated divider View for each line. You can translate these Android widgets piece by piece to iOS, or you can choose to use UITableView as in Step 2. In iOS, there is actually a better choice: using UITableViewController with static cells, each static cell for each line.
    1. Drag a UITableViewController from 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.
      • Content: Select Static Cells.
      • Sections: 2
      • Style: Select Grouped.

      9781484204375_Fig05-05.jpg

      Figure 5-5. Static Cells Table View

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

    9781484204375_Fig05-06.jpg

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

    1. To update the section title, select the Table View Section and update the Header attributes in Attributes Inspector:
      • Section 1: Mortgage Payment
      • Section 2: Investment
    2. Since all of 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.
    3. Select the Table View Cell and update the Style to Right Detail.
    4. Select the Mortgage Payment section and update number of Rows to 6.
    5. You need three Table View Cells for Section 2. You may repeat the preceding steps, or copy and paste in the storyboard editor.
    6. Update all the Table View Cell titles as shown in the counterpart Android content view.
    7. 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.
    8. 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 storyboard scenes translated from the Android counterparts.

9781484204375_Fig05-07.jpg

Figure 5-7. Four RentalROI scenes

Choose a Screen Navigation Pattern

When choosing appropriate navigation pattern(s), you will naturally get a very good idea by playing with the Android app from which you’re porting. Sometimes you may 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 the Monthly Details to Amortization List to Property Detail screens. The popular Navigation Stack navigation pattern is prefect for this intended behavior (see Chapter 3, “Navigation Stack”). 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 Popover is the choice (see Chapter 3, “UIPopoverController”).

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.

9781484204375_Fig05-08.jpg

Figure 5-8. RentalROI connected scenes

Continue with the storyboard tasks by doing the following (see Chapter 3, “Storyboard Segue” for step-by-step instructions):

  1. Select RentalPropertyViewController in the storyboard editor and embed it in a UINavigationController (see Figure 3-33 in Chapter 3, “UINavigationController” for detailed instructions).
    1. Make sure Is Initial View Controller is checked in the Navigation Controller Attributes Inspector.
    2. Select the Navigation Item in the RentalPropertyViewController to update the Title attribute to Property in Attributes Inspector.
    3. Add a right BarButtonItem to the Property Navigation Item in RentalPropertyViewController. Also update the button Title attribute to Schedule in the BarButtonItem Attributes Inspector.
    4. Connect the Schedule BarButtonItem action outlet in Connections Inspector to your code, such as doSchedule(...).
  2. Connect a Manual Segue from RentalPropertyViewController to AmortizationViewController.
    1. Segue: Show (e.g., 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 (e.g., Push).
    2. Identifier: MonthlyTerm.
  5. Add Navigation Item to AmortizationViewController and MonthlyTermViewController as shown in Figure 5-8.
    1. Drag Navigation Item from Object Libraries and drop it onto the controller in the storyboard document outline.
    2. Update the Navigation Item Title respectively (e.g., 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 may either draw a UINavigationBar from Object Libraries, 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. Update the Title attribute to be 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.

Implement Piece by Piece

Let’s take a look at the pieces you have now. Figure 5-9 shows the structures of both projects side by side.

9781484204375_Fig05-09.jpg

Figure 5-9. Android and iOS RentalROI project structures

The iOS ViewController classes are in place and mapped one-to-one with the Android counterpart Fragments. There is one model class, RentalProperty, not in the iOS project yet. Let’s create a Swift class skeleton for the RentalProperty model class (see Listing 5-5) first.

Listing 5-5. RentalProperty.swift Skeleton

import Foundation
public class RentalProperty {

}

Application Resources

Same as Android apps, most iOS apps need images or digital assets to dress up the whole app. Also, you definitely want to port the externalized text to iOS. Do the following to port the application resources (see Chapter 4, “Application Resources”) from the Android counterpart to iOS:

  1. Translate the Android strings.xml to iOS:
    1. In Xcode, select the Supporting Files folder to create a new file (image+N) in it, and follow the on-screen instructions to select iOS image Resource image Strings File. Name it: Localizable.strings.
    2. Copy the content in Android res/values/strings.xml to the iOS Localizable.strings file. The translation is straightforward, as shown in Listing 5-6.

    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. Generally, you reuse or recreate digital assets from your Android project. In this app, you only have one: the application icon, ic_launcher.png.
    1. Create ic_launcher120.png, ic_launcher180.png, ic_launcher76.png, and ic_launcher152.png from the Android project res/xxhdpi/ic_launcher.png file.
    2. Select Images.xcassets and AppIcon in the Xcode asset catalog and drag the four files you just made as shown in Figure 5-10: ic_launcher120.png for iPhone 2x, ic_launcher180.png for iPhone 3x, ic_launcher76.png for iPad 1x and ic_launcher152.png for iPad 2x. The image resolution must match exactly or Xcode will give you warnings.

9781484204375_Fig05-10.jpg

Figure 5-10. Xcode project AppIcon

If the Android app contains resources for I18N, you want to port them to the iOS app as well.

Java Class to Swift Class

Now, you have all the matching classes in your Xcode iOS project. Your next step is to break each class into more pieces, porting Android methods to iOS methods. Again, I use a top-down approach in each class: port the member signatures first, and try to defer the internal implementations as late as possible. For your convenience, you may use Table 5-1 as a step-by-step guide. You will find that the information in the table is common sense after one or more exercises, but I think it makes translation efforts more systematic.

Table 5-1. Class Porting Steps

Step

Instructions

1.

For each class, copy the contents of the Java counterpart to Swift.

2.

Translate Java fields to Swift Stored Properties. For Java static constants, translate them to Swift inner struct static variables.

3.

Translate the method declarations to Swift. Preserve signatures as much as possible except those that are life cycle methods.

a. Keep the Java impl as Swift comments. They are perfect and tested logics.

b. Translate Android life cycle methods signature to iOS counterparts including constructors.

c. Preserve utility methods signatures.

Steps 1 and 2 are straightforward. Step 3 is the key to break the class into smaller pieces: methods. After you run down all the classes with Table 5-1, all the classes should have callable method skeletons. You can start to translate the commented Java code to Swift in each method one by one (see “Java Methods to Swift Methods” later in this chapter).

For your convenience, Table 5-2 recaps the member declarations mappings that you will surely encounter in this step.

Table 5-2. Member declarations in Java and Swift

Languages

Java

Swift

Variables

String aName

var aName: String

Class variable

static ...

static var in inner struct

Method declaration

String aMethod(int a)

func aMethod(a: Int) -> String

Class method

static ...

class func ...

Constructor

ClassName(...)

init(...)

Android Context

Activity, Context

Remove them.

Model Class: RentalProperty

A great deal of the translation effort involves converting the general Java-to-Swift language programming rules (see Table 2-1). Your immediate goal is to port the counterpart Java class RentalProperty members to Swift without renaming them. For methods, focus on signatures by commenting all the implementation code. Follow the class porting steps in Table 5-1 to port RentalProperty.java to Swift:

  1. To start, copy the whole RentalProperty.java class to the RentalProperty.swift file. You will get a lot of errors. These compilations errors are your free guidance.
  2. Next, define the classes. A general rule of thumb: keep any signature including class name. The caller and callee will just connect without glitches in later steps. Delete or comment out pure Java things, i.e., implements Serializable (see Listing 5-7).

    Listing 5-7. RentalProperty.swift Class Declaration

    public class RentalProperty /* implements Serializable */ {
    // private static final long serialVersionUID = 1L;
      ...
    }
  3. Translate Java fields to Swift:

    Tip  Java: String mProperty; => Swift: var mProperty: String

    Or, var mProperty = ""  // use type inference when possible

    1. Java fields to Swift Stored Properties (see Listing 5-8).
    2. Java static variables to Swift inner struct static variables.

    Listing 5-8. RentalProperty.swift Stored Properties

    public class RentalProperty /* implements Serializable */ {
      ...
    // private double purchasePrice;
    // private double loanAmt;
    // private double interestRate;
    // private int numOfTerms;
    // private double escrow;
    // private double extra;
    // private double expenses;
    // private double rent;
      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;

    // public static final String KEY_AMO_SAVED = "KEY_AMO_SAVED";
    // public static final String KEY_PROPERTY = "KEY_PROPERTY";
    // private static final String PREFS_NAME = "MyPrefs";
    // private static final int MODE = Context.MODE_PRIVATE;
       // MODE_WORLD_WRITEABLE

    // private static RentalProperty _sharedInstance = null;
      struct MyStatic {
        static let KEY_AMO_SAVED = "KEY_AMO_SAVED";
        static let KEY_PROPERTY = "KEY_PROPERTY";
        private static let PREFS_NAME = "MyPrefs";
        private static let MODE = 0; // probably Android thing
        private static var _sharedInstance = RentalProperty()
      }
      ...
    }
  4. Translate the method declarations to Swift, as shown in Listing 5-9. Preserve signatures as much as possible except those that are life cycle methods.

    Tip  Java: String doWork(int param); => Swift: func doWork(Int: Type) -> String

    1. Keep the Java impl as Swift comments. They are perfect/tested logics.
    2. Translate Android life cycle methods signatures to iOS counterparts including constructors.
    3. Preserve utility methods signatures.
    4. You can safely delete the Android Context (or any pure Android or Java specifics).

Note  Conventionally, you don’t need to port the Java field accessors. I choose to port them now just because they are used by Java callers a lot. I normally remove/refactor these Java-ish accessors after the app is working.

Listing 5-9. Porting RentalProperty Methods

class RentalProperty {
  ...
// private RentalProperty() {
  private init() {
    // Commented Java code omitted
  }

// public static RentalProperty sharedInstance() {
  class func sharedInstance() -> RentalProperty {
    // Commented Java code omitted
    return RentalProperty()
  }

// public String getAmortizationPersistentKey() {
  func getAmortizationPersistentKey() -> String {
    // Commented Java code omitted
    return ""
  }

// public JSONArray getSavedAmortization(Context activity) {
  func getSavedAmortization() -> NSArray? {
    // Commented Java code omitted
    return nil
  }

// public boolean saveAmortization(String data, Context activity){
  func saveAmortization(data: NSArray) -> Bool {
    // Commented Java code omitted
    return false
  }

// public boolean load(Context activity) {
  func load() -> Bool {
    // Commented Java code omitted
    return true
  }

// public boolean save(Context activity) {
  func save() -> Bool {
    // Commented Java code omitted
    return true
  }

/////////// SharedPreferences usage /////////////////
// public boolean saveSharedPref(String key, String data, Context activity) {
  func saveSharedPref(key:String,data:AnyObject)->Bool{
    // Commented Java code omitted
    return true
  }

// public String retrieveSharedPref(String key, Context activity) {
  func retrieveSharedPref(key: String) -> AnyObject? {
    // Commented Java code omitted
    return nil
  }

// public void deleteSharedPref(String key, Context activity) {
  func deleteSharedPref(key: String) {
    // Commented Java code omitted
  }

  // JavaBean accessors
  func getPurchasePrice()-> Double {
    return self.purchasePrice;
  }

  func setPurchasePrice(purchasePrice: Double) {
    self.purchasePrice = purchasePrice;
  }

  func getLoanAmt()-> Double {
    return self.loanAmt;
  }

  func setLoanAmt(loanAmt: Double) {
    self.loanAmt = loanAmt;
  }

  func getInterestRate()-> Double {
    return self.interestRate;
  }

  func setInterestRate(interestRate: Double) {
    self.interestRate = interestRate;
  }

  func getNumOfTerms()-> Int {
    return self.numOfTerms;
  }

  func setNumOfTerms(numOfTerms: Int) {
    self.numOfTerms = numOfTerms;
  }

  func getEscrow()-> Double {
    return self.escrow;
  }

  func setEscrow(escrow: Double) {
    self.escrow = escrow;
  }

  func getExtra()-> Double {
    return self.extra;
  }

  func setExtra(extra: Double) {
    self.extra = extra;
  }

  func getExpenses()-> Double {
    return self.expenses;
  }

  func setExpenses(expenses: Double) {
    self.expenses = expenses;
  }

  func getRent()-> Double {
    return self.rent;
  }

  func setRent(rent: Double) {
    self.rent = rent;
  }
  ...

You have achieved the immediate goal.

Note  You really cannot get any better method comments than these, because they are actually code that has been proven to work.

EditTextViewController

ANDROID ANALOGY

The iOS counterpart is EditTextViewFragment.

Let’s start on the first view controller, EditTextViewController.

Note  I would’ve chosen RentalPropertyController first if it didn’t need EditTextViewController. The dependencies can be easily seen in the counterpart Java packages’ import statements. You may choose any one to start with and bear with those temporary compilation errors.

Your immediate goal is to translate the Android EditTextViewFragment Java class definitions and member signatures to iOS Swift classes. Again, copy the whole EditTextViewFragment.java class onto the existing EditTextViewController.swift class to start with:

  1. Start with the class-level definition. There is an inner interface, but Swift doesn’t have inner protocol. You can safely create the protocol in the same file outside of the class definition as shown in Listing 5-10.

    Listing 5-10. Java Interface to Swift Protocol

    // Java interface to Swift protocol
    protocol EditTextViewControllerDelegate {
      func onTextEditSaved(tag: Int, text: String);
      func onTextEditCanceled();
    }

    class EditTextViewController : UIViewController, UITextFieldDelegate {
      ...
    //// inner interface
    //  interface EditTextViewControllerDelegate {
    //    public void onTextEditSaved(int tag, String text);
    //    public void onTextEditCanceled();
    //  }
      ...
  2. Translate Java fields to Swift (see Listing 5-11):
    1. Java fields to Swift Stored Properties.
    2. Most likely, the Java fields related to UI widgets are the existing IBOutlet properties.

    Listing 5-11. Java Fields to Swift Stored Properties

    class EditTextViewController : ... {
      ...
    // private int editTextTag;
    // private String header;
    // private String text;
    // private EditTextViewControllerDelegate delegate;
    // private View contentView; => in super.view already
    // private EditText mEditText; => existing IBOutlet
      var editTextTag = 0
      var header = ""
      var text = ""
      var delegate: EditTextViewControllerDelegate!
      ...
  3. Translate the method declarations to Swift (see Listing 5-12). Preserve signatures except those that are life cycle methods.
    1. Keep the Java impl as Swift comments. They are perfect/tested logics.
    2. Translate Android Fragment life cycle method signatures to iOS counterpart View life cycle methods.
    3. Preserve utility method signatures.
    4. You can safely delete the Android Context (or any pure Android or Java specifics).
    5. You don’t need those conventional Java bean accessors in Swift.

Listing 5-12. EditTextViewController Life Cycle Callbacks

class EditTextViewController : ... {
  ...
// @Override public View onCreateView(...) {
  override func viewDidLoad() {
    // Commented Java code omitted
  }

// @Override public void onResume() {
  override func viewDidAppear(animated: Bool) {
    // Commented Java code omitted
  }

// @Override public void onPause() {
  override func viewWillDisappear(animated: Bool) {
    // Commented Java code omitted
  }

// @Override public void onCreateOptionsMenu(...) {
  // the navigationBar already drawn in storyboard

// @Override public boolean onOptionsItemSelected(...) {
  // the IBActions: doSave and doCancel

// private void showKeyboard() {
  func showKeyboard(){
    // Commented Java code omitted
  }

// private void hideKeyboard() {
  func hideKeyboard() {
    // Commented Java code omitted
  }
  // public accessors, not needed in Swift
  ...

RentalPropertyViewController

IOS ANALOGY

The ADT counterpart is RentalPropertyViewFragment.

Move on to the next view controller: RentalPropertyViewController. Copy the RentalPropertyViewFragment Java class onto the existing RentalPropertyViewController.swift class. Use the porting steps in Table 5-1 and do the following:

  1. Start with the class-level definition. ListFragment naturally maps to iOS UITableViewController (see Listing 5-13).

    Listing 5-13. Java Interface to Swift Protocol

    // public class RentalPropertyViewFragment extends ListFragment
    // implements EditTextViewControllerDelegate
    class RentalPropertyViewController: UITableViewController, EditTextViewControllerDelegate {
      ...
  2. Translate Java fields to Swift (see Listing 5-14):
    1. Java fields to Swift Stored Properties.
    2. Most likely, the UI widgets’ related Java fields are already the existing IBOutlet properties.
    3. Swift doesn’t support class-type variables yet. Create an inner struct for those Java final constants.

    Listing 5-14. Java Fields to Swift Stored Properties

    class RentalPropertyViewController: ... {
      ...
    ///// from Java counterpart
    //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 final String KEY_DATA = "data";
    // private static final String KEY_RC = "rc";
    // private static final String KEY_ERROR = "error";
      struct MyStatic {
        private static let URL_service_tmpl = "http://www.pdachoice.    com/ras/service/amortization?loan=%.2f&rate=%.3f&terms=%d&ext    ra=%.2f&escrow=%.2f"
        private static let KEY_DATA = "data"
        private static let KEY_RC = "rc"
        private static let KEY_ERROR = "error"
      }

    // private RentalProperty _property;
    // private JSONArray _savedAmortization;
    // private BaseAdapter mAdapter; // pure Android
      var _property = RentalProperty.sharedInstance()
      var _savedAmortization: NSArray?
      ...
  3. Translate the method declarations to Swift as shown in Listing 5-15:
    1. Keep the Java impl as Swift comments. They are perfect/tested logics.
    2. Translate Android Fragment life cycle method signatures to iOS counterpart View life cycle methods.
    3. Preserve utility methods signatures.
    4. Translate Android BaseAdapter to iOS DataSource impl.

Listing 5-15. EditTextViewController Life Cycle Callbacks

class RentalPropertyViewController: ... {
  ...
///// from Java counterpart
// @Override public void onCreate(Bundle savedInstanceState) {
  override func viewDidLoad() {
    // Commented Java code omitted
  }

// @Override public void onResume() {
  override func viewDidAppear(animated: Bool) {
    // Commented Java code omitted
  }

//  @Override public void onCreateOptionsMenu(...) {
  // UINavigationBar already drawn in storyboard

//  @Override public boolean onOptionsItemSelected(...) {
  @IBAction func doSchedule(sender: AnyObject) {
    // Commented Java code omitted
    self.performSegueWithIdentifier("AmortizationTable", sender: sender)
  }

//// callback method when list item is selected.
// @Override public void onListItemClick(...) {
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    // Commented Java code omitted
  }
//private BaseAdapter createListAdapter() {
// Commented Java code omitted  }
  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // TODO: Android Adapter to iOS DataSource impl later
    return 0
  }

  override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // TODO: Android Adapter to iOS DataSource impl later
    return 0
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // TODO: Android Adapter to iOS DataSource impl later
    return UITableViewCell()
  }

//// delegate interface
// public void onTextEditSaved(int tag, String text) {
  func onTextEditSaved(tag: Int, text: String) {
    // Commented Java code omitted
  }

// public void onTextEditCanceled() {
  func onTextEditCanceled() {
    // Commented Java code omitted
  }

// public void doAmortization(Object sender) {
  func doAmortization() {
    // Commented Java code omitted
  }

//// GET data from url
// private JSONObject httpGet(String myurl) {
  private func httpGet(myurl: String) -> NSDictionary? {
        // Commented Java code omitted
return [:]
  }

// private String readStream(InputStream stream) {
  func readStream(stream: NSInputStream) -> String {
    // Commented Java code omitted
    return ""
  }
  ...

Note  It is all about the same idea: move the code from the Android counterpart; translation will be done in a top-down fashion. In other words, get the Swift classes in place first, and then get the Swift method stubs in place with precise comments written in the tested Android code.

AmortizationViewController

IOS ANALOGY

The ADT counterpart is AmortizationViewFragment.

Move on to the next view controller: AmortizationViewController. Follow the porting steps in Table 5-1; Listing 5-16 shows the intermediate results.

Listing 5-16. AmortizationViewController Properties and Method Signatures

class AmortizationViewController : UITableViewController {

///// from Java counterpart
// private JSONArray monthlyTerms;
// private BaseAdapter mAdapter;
  var monthlyTerms = NSArray()

// @Override public void onCreate(Bundle savedInstanceState) {
  override func viewDidLoad() {
    // Commented Java code omitted
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // TODO: Android Adapter to iOS DataSource impl later
    return 0
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // TODO Android Adapter to iOS DataSource impl later
    return UITableViewCell()
  }

// @Override public void onResume() {
  override func viewDidAppear(animated: Bool) {
    // Commented Java code omitted
  }

// public void onListItemClick(...) {
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    // Commented Java code omitted
  }
}

MonthlyTermViewFragment

IOS ANALOGY

The ADT counterpart is MonthlyTermViewFragment.

The MonthlyTermViewController is the last view controller. Follow the porting steps in Table 5-1; Listing 5-17 shows the intermediate results.

Listing 5-17. MonthlyTermViewController Properties and Method Signatures

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!

///// from Java counterpart
// private JSONObject monthlyTerm;
  var monthlyTerm = NSDictionary()

//// IBOutlets above, and super.view
// private TextView mPaymentNo;
// private TextView mTotalPmt;
// private TextView mPrincipal;
// private TextView mInterest;
// private TextView mEscrow;
// private TextView mAddlPmt;
// private TextView mBalance;
// private TextView mEquity;
// private TextView mCashInvested;
// private TextView mRoi;
// private View contentView;

// @Override public View onCreateView(...) {
  override func viewDidLoad() {
    // Commented Java code omitted
  }

// @Override public void onResume() {
  override func viewDidAppear(animated: Bool) {
    // Commented Java code omitted
  }

// JavaBean accessors => not needed with Swift properties
}

Unlike UITableViewController with dynamic cells, you don’t need to provide the Data Source. This is the last class. You should have all the classes that contain properties and methods that can be called from other classes. In other words, the programming interface is in place.

Java Methods to Swift Methods

You already broke each class into more pieces—that is, methods. The last step to complete your iOS project is to fully implement each method by translating from the commented Java code. The language syntax invoking methods in Swift is the same as Java. Except for the code using the Android-specific API, most of the Java code should work in Swift! For the code using Android-specific APIs, use Chapter 4 to guide you through your translating efforts.

Programming languages naturally embed rules. For your convenience, Table 5-3 lists some common types or syntax in both languages.

Table 5-3. Replaceable Java to Swift Syntax or Symbols

Item

Java

Swift

Self

this.aMember

self.aMember

Variables

String aName

var aName: String

Boolean

boolean

Bool

Integer

Integer or int

Int, UInt

Null value

null

nil

Array

ArrayList or JSONArray if string serialization is required

Array or NSArray

Hash table

HashMap or JSONObject if string serialization is required

Dictionary or NSDictionary

Note that you can always use Find and Replace (imageF) in the Xcode editor, or the Find Navigator to replace repeatable patterns in the whole project. Figure 5-11 depicts an example.

9781484204375_Fig05-11.jpg

Figure 5-11. Find and Replace in Xcode editor

EditTextViewController

Now move on to the first view controller, EditTextViewController. In the ADT project, this counterpart Android Fragment only has a EditText that presents the text for editing from the presenting Fragment. It also displays the name of the text on the title. When the user saves or cancels the edit operations, the modified text is returned to the presenting Fragment. Do the following to port the same functionalities to iOS:

  1. Translate the Android life cycle methods to iOS counterparts, IBAction methods, and other matching methods (see Listing 5-18). The Android action items in Options Menu are translated to iOS barButtonItems on the Navigation Bar. Connect the Save and Cancel buttons to your IBAction methods; for example, doSave() and doCancel().

    Listing 5-18. EditTextViewController Life Cycle Callbacks

    class EditTextViewController : UIViewController, UITextFieldDelegate {
      ...
      override func viewDidLoad() {
    //  contentView = inflater.inflate(...);
    //  setHasOptionsMenu(true); // enable Option Menu.
    //  mEditText = (EditText) contentView.findViewById(...);
    //  this.mEditText.setText(this.text);
    //  getActivity().setTitle(header);
    //  return contentView;
        super.viewDidLoad()
        mEditText.text = self.text
        self.navigationItem.title = self.header
      }

      override func viewDidAppear(animated: Bool) {
    //  super.onResume();
    //  ((MainActivity) getActivity()).slideIn(...);
    //  showKeyboard();
        super.viewDidAppear(animated)
        showKeyboard()
      }

      override func viewWillDisappear(animated: Bool) {
    //  super.onPause();
    //  hideKeyboard();
        super.viewWillDisappear(animated)
        hideKeyboard()
      }

      override func viewDidDisappear(animated: Bool) {
    //  super.onPause();
    //  hideKeyboard();
        super.viewDidDisappear(animated)
      }

    //  @Override
    //  public boolean onOptionsItemSelected(...) {
    //  String returnText = this.mEditText.getText().toString();
    //  if(delegate != null) {
    //  this.delegate.onTextEditSaved(this.getEditTextTag(),returnText);
    //  }
    //  return true;
    //  }
      @IBAction func doSave(sender: AnyObject) {
        var returnText = self.mEditText.text
        if(delegate != nil) {
          delegate.onTextEditSaved(self.editTextTag, text: returnText)
        }
      }

      @IBAction func doCancel(sender: AnyObject) {
        if(delegate != nil) {
          delegate.onTextEditCanceled()
        }
      }

      private func showKeyboard() {
    //  InputMethodManager imm = (InputMethodManager) ... ;
    //  imm.showSoftInput(...);
    //  mEditText.selectAll();
        self.mEditText.becomeFirstResponder()
      }

      private func hideKeyboard() {
    //  InputMethodManager imm = (InputMethodManager) ... ;
    //  imm.hideSoftInputFromWindow(...);
        self.mEditText.endEditing(true)
      }
      ...
  2. The soft keyboard does not behave the same. Keyboard implementation is very platform dependent. Unlike Android, which shifts the view up automatically when the keyboard appears, you need to write code to mimic the same behavior, as shown in Listing 5-19.

Listing 5-19. Keyboard Implementation

class EditTextViewController : ... {
  ...
  override func viewDidLoad() {
    ...
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
      NSNotificationCenter.defaultCenter().addObserver(self,
           selector: "keyboardAppeared:", name: UIKeyboardDidShowNotification, object: nil)
    }
  }

  override func viewDidDisappear(animated: Bool) {
    ...
    NSNotificationCenter.defaultCenter().removeObserver(self)
  }

  func keyboardAppeared(notification: NSNotification) {
    var keyboardInfo = notification.userInfo as NSDictionary!
    var kbFrame = keyboardInfo.valueForKey(UIKeyboardFrameBeginUs erInfoKey) as NSValue
    var kbFrameRect: CGRect = kbFrame.CGRectValue()
    var keyboardH = min(kbFrameRect.size.width, kbFrameRect.size.height)
    var screenRect: CGRect = UIScreen.mainScreen().bounds;

    var tfRect: CGRect = self.mEditText.frame
    var y = screenRect.size.height - keyboardH - mEditText.frame.    size.height - 20
    var x = (screenRect.size.width - tfRect.size.width) / 2

    UIView.animateWithDuration(0.1, animations: { () -> Void in
      var newRect = CGRectMake(x, y, tfRect.size.width, tfRect.size.height);
      self.mEditText.frame = newRect
    })
  }
  ...

RentalPropertyViewController

When the app is launched, this is the first content view. The purpose of this view controller is to collect user input. Do the following to port the implementation from Android to iOS:

  1. Translate the life cycle methods, as shown in Listing 5-20.
    1. Same as in Android, always call super.viewXXX.
    2. Remove the Android Options Menu code. You have an iOS-drawn NavigationController/NavigationBar in storyboard already (see Chapter 3).

    Listing 5-20. Life Cycle Methods Implementation

    class RentalPropertyViewController : UITableViewController {
      ...
      override func viewDidLoad() {
        super.viewDidLoad() // super.onCreate(savedInstanceState);
        _property = RentalProperty.sharedInstance();
        _property.load(/*getActivity()*/);
    //    setHasOptionsMenu(true); // enable Option Menu.
    //    mAdapter = createListAdapter();
    //    this.setListAdapter(mAdapter);
      }

      override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated) // super.onResume();
    // getActivity().setTitle(getText(R.string.label_property));
        self.navigationItem.title = "Property"
      }

      @IBAction func doSchedule(sender: AnyObject) {
    //    doAmortization();
        doAmortization()
      }
      ...
    }
  2. Translate the Android Adapter for ListFragment to iOS UITableViewDataSource as shown in Listing 5-21. Just for demonstrating how to reuse the Android BaseAdapter code more effectively, I chose to flatten the iOS cell section-row indexPath to the Android list view item position. For example, the UITableViewCell at the first row of the second section is the original Android list item at position 9.

    Listing 5-21. Implement TableView DataSource

    class RentalPropertyViewController : UITableViewController {
    ...
    // private BaseAdapter createListAdapter() {
    //  return new BaseAdapter() {
    //
    //    @Override
    //    public int getItemViewType(int pos) {
    //      if (pos == 0 || pos == 8) {
    //        return 0;
    //      } else {
    //        return 1;
    //      }
    //    }
    //
    //    @Override
    //    public int getViewTypeCount() {
    //      return 2;
    //    }
    //
    //    @Override
    //    public View getView(int pos, ...) {
    //
    //      if (view == null) {
    //        LayoutInflater inflater = getActivity().getLayoutInflater();
    //        if (pos == 0 || pos == 8) {
    //          // header list item
    //          view = inflater.inflate(android.R.layout.simple_list_item_1, null);
    //        } else {
    //          // right detail list item
    //          view = inflater.inflate(R.layout.rightdetail_listitem, null);
    //        }
    //      }
    //
    //      if (pos == 0 || pos == 8) {
    //        // header list item
    //        view.setBackgroundColor(Color.argb(32, 0, 128, 128));
    //        TextView text1 = (TextView) view.
              findViewById(android.R.id.text1);

    //
    //        if (pos == 0) {
    //          text1.setText(getResources().getString(R.string.mortgage));
    //        } else {
    //          text1.setText(getResources().getString(R.string.operations));
    //        }
    //      } else {
    //        // right detail list item
    //        view.setBackgroundColor(Color.argb(0, 0, 0, 0));
    //        TextView textLabel = (TextView) view.findViewById (R.id.textLabel);
    //        TextView detailTextLabel = (TextView) view.
              findViewById(R.id.detailTextLabel);

    //
    //        switch (pos) {
    //        case 1:
    //          textLabel.setText(R.string.purchasePrice);
    //          detailTextLabel.setText(String.format("%.0f",
                _property.getPurchasePrice()));

    //          break;
    //        case 2:
    //          textLabel.setText(R.string.downPayment);
    //          if (_property.getPurchasePrice() > 0) {
    //          double down = (1 - _property.getLoanAmt() /
                 _property.getPurchasePrice()) * 100.0f;

    //            detailTextLabel.setText(String.format("%.0f",
                  down));//

    //            if (_property.getLoanAmt() == 0 && down > 0) {
    //              _property.setLoanAmt(_property.getPurchasePrice()
                     * (1 - down / 100.0f));

    //            }
    //          } else {
    //            detailTextLabel.setText("0");
    //          }
    //          break;
    //        case 3:
    //          textLabel.setText(R.string.loanAmount);
    //          detailTextLabel.setText(String.format("%.2f",
                _property.getLoanAmt()));

    //          break;
    //        case 4:
    //          textLabel.setText(R.string.interestRate);
    //          detailTextLabel.setText(String.format("%.3f",
                _property.getInterestRate()));

    //          break;
    //        case 5:
    //          textLabel.setText(R.string.mortgageTerm);
    //          detailTextLabel.setText(String.format("%d",
                _property.getNumOfTerms()));

    //          break;
    //        case 6:
    //          textLabel.setText(R.string.escrowAmount);
    //          detailTextLabel.setText(String.format("%.0f",
                _property.getEscrow()));

    //          break;
    //        case 7:
    //          textLabel.setText(R.string.extraPayment);
    //          detailTextLabel.setText(String.format("%.0f",
                _property.getExtra()));

    //          break;
    //        case 9:
    //          textLabel.setText(R.string.expenses);
    //          detailTextLabel.setText(String.format("%.0f",
                _property.getExpenses()));

    //          break;
    //        case 10:
    //          textLabel.setText(R.string.rent);
    //          detailTextLabel.setText(String.format("%.0f",
                _property.getRent()));

    //          break;
    //
    //        default:
    //          break;
    //        }
    //      }
    //
    //      return view;
    //    }
    //
    //    @Override
    //    public int getCount() {
    //      return 11; // 2 section + 9 fields
    //    }
    //
    //    @Override
    //    public long getItemId(int pos) {
    //      return pos; // not used
    //    }
    //
    //    @Override
    //    public Object getItem(int pos) {
    //      TextView textLabel = (TextView) getView(pos, null, null).
            findViewById(R.id.textLabel);

    //      if (textLabel == null) {
    //        return null;
    //      } else {
    //        TextView detailTextLabel = (TextView) getView(pos,
               null, null).findViewById(R.id.detailTextLabel);

    //        NameValuePair nvp = new BasicNameValuePair(textLabel.
              getText().toString(), detailTextLabel.getText().
              toString());

    //        return nvp;
    //      }
    //    }
    //  };
    //}
      // android adapter to iOS datasource
      override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 2
      }

      override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
          return NSLocalizedString("mortgage", comment: "")
        } else {
          return NSLocalizedString("operations", comment: "")
        }
      }

      override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
          return 7
        } else {
          return 2
        }
      }

      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.row
        var section = indexPath.section

        if section == 0 {
          pos = indexPath.row + 1
        } else { // 1
          pos = indexPath.row + 9
        }

        switch (pos) {
        case 1:
          textLabel.text = NSLocalizedString("purchasePrice",
          comment: "")

          detailTextLabel.text = NSString(format: "%.0f", _property.
          getPurchasePrice());

        case 2:
          textLabel.text = NSLocalizedString("downPayment", comment:
           "")


          if (_property.getPurchasePrice() > 0) {
            var down = (1 - _property.getLoanAmt() / _property.
            getPurchasePrice()) * 100.0;

            detailTextLabel.text = NSString(format: "%.0f", down);

            if (_property.getLoanAmt() == 0 && down > 0) {
              _property.setLoanAmt(_property.getPurchasePrice() *
              (1 - down / 100.0));

            }
          } else {
            detailTextLabel.text = "0";
          }
        case 3:
          textLabel.text = NSLocalizedString("loanAmount", comment: "")
          detailTextLabel.text = NSString(format: "%.2f", _property.
          getLoanAmt())

        case 4:
          textLabel.text = NSLocalizedString("interestRate", comment:
          "")

          detailTextLabel.text = NSString(format: "%.3f", _property.
          getInterestRate())

        case 5:
          textLabel.text = NSLocalizedString("mortgageTerm",
          comment: "")

          detailTextLabel.text = NSString(format: "%d", _property.
          getNumOfTerms())

        case 6:
          textLabel.text = NSLocalizedString("escrowAmount",
          comment: "")

          detailTextLabel.text = NSString(format: "%.0f",
          _property.getEscrow())

        case 7:
          textLabel.text = NSLocalizedString("extraPayment",
          comment: "")

          detailTextLabel.text = NSString(format: "%.0f",
          _property.getExtra());

        case 9:
          textLabel.text = NSLocalizedString("expenses", comment: "")
          detailTextLabel.text = NSString(format: "%.0f",
          _property.getExpenses());

        case 10:
          textLabel.text = NSLocalizedString("rent", comment: "")
          detailTextLabel.text = NSString(format: "%.0f",
          _property.getRent());


        default:
          break;
        }

        return cell
      }
      ...
    }
  3. RentalPropertyViewController presents EditTextViewController with text for editing (see Listing 5-22):
    1. Use performSegueWithIdentifier(...) and prepareForSegue(...) for screen transition and passing data to EditTextViewController (see Chapter 3, “Pass Data with a Segue,” for detailed instructions).
    2. To return data to the presented view controller, the conventional delegate pattern works both in Android and iOS.

Listing 5-22. Present EditTextViewController

class RentalPropertyViewController : UITableViewController {
  ...
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// ((MainActivity) getActivity()).pushViewController(toFrag, true);
    self.performSegueWithIdentifier("EditText", sender: indexPath)
  }

  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var identifier = segue.identifier
    if identifier == "EditText" {
     var indexPath = sender as NSIndexPath

   // if (position == 0 || position == 8) {
   //   return; // position 0 and 8 are header
   // }
   // EditTextViewFragment toFrag = new EditTextViewFragment();
      var toFrag =  (segue.destinationViewController as       UINavigationController).topViewController as EditTextViewController
   // NameValuePair data = (NameValuePair) mAdapter.getItem(position);
      var cell = tableView.cellForRowAtIndexPath(indexPath)!
      var row = indexPath.row
      var section = indexPath.section
   // toFrag.setEditTextTag(position);
   // toFrag.setHeader(data.getName());
   // toFrag.setText(data.getValue());
   // toFrag.setDelegate(this);
      toFrag.editTextTag = (section == 0) ? row + 1 : row + 9
      toFrag.header = cell.textLabel!.text!
      toFrag.text = cell.detailTextLabel!.text!
      toFrag.delegate = self
    }
  }

//// delegate interface
  func onTextEditSaved(tag: Int, text: String) {
//  ((MainActivity) getActivity()).popViewController();
    self.dismissViewControllerAnimated(true, completion: nil)

    switch (tag) {
    case 1:
      _property.setPurchasePrice((text as NSString).doubleValue);
//    String percent = ((NameValuePair) mAdapter.getItem(2)).getValue();
      var indexPath = (tag < 9) ? NSIndexPath(forRow: tag - 1, inSection: 0) : NSIndexPath(forRow: tag - 9, inSection: 1)
      var percent = tableView.cellForRowAtIndexPath(indexPath)!.
      detailTextLabel!.text!

      var down = (percent as NSString).doubleValue
      if (_property.getPurchasePrice() > 0 && _property.getLoanAmt() == 0 &&
      down > 0) {

        _property.setLoanAmt(_property.getPurchasePrice() * (1 - down /
         100.0));

      }

      break;
    case 2:
      var percentage = (text as NSString).doubleValue / 100.0;
      _property.setLoanAmt(_property.getPurchasePrice() * (1 - percentage));
      break;
    case 3:
      _property.setLoanAmt((text as NSString).doubleValue);
      break;
    case 4:
      _property.setInterestRate((text as NSString).doubleValue);
      break;
    case 5:
      _property.setNumOfTerms((text as NSString).integerValue);
      break;
    case 6:
      _property.setEscrow((text as NSString).doubleValue);
      break;
    case 7:
      _property.setExtra((text as NSString).doubleValue);
      break;
    case 9:
      _property.setExpenses((text as NSString).doubleValue);
      break;
    case 10:
      _property.setRent((text as NSString).doubleValue);
      break;

    default:
      break;
    }
    tableView.reloadData() // mAdapter.notifyDataSetChanged();
    _property.save(/* getActivity() */);
  }

  func onTextEditCanceled() {
//  ((MainActivity) getActivity()).popViewController();
    self.dismissViewControllerAnimated(true, completion: nil)
  }
  ...

Every method is translated except doAmortization(). This method touches two common topics: RESTful Service and Saving Data (see Chapter 4). You will do this later.

Build and run the Swift project to test your code. When the Table View Cell is selected, it presents the EditTextViewController with the title and the text of the selected field for editing. After editing is done, the modified text is sent into the presenting RentalPropertyViewController via delegate and the new text is updated on the TableViewCell.

AmortizationViewController

Move on to the AmortizationViewController. It needs to render the amortization items. Do the following to port the Java implementation from Android to iOS Swift:

  1. Translate the commented Java code to Swift (see Listing 5-23).
    1. Port Android Fragment life cycles to iOS View life cycles.
    2. Convert the Android BaseAdapter to iOS DataSource and delegate methods.
    3. Present MonthlyTermViewController.

Listing 5-23. EditTextViewController Life Cycle Callbacks

class AmortizationViewController : UITableViewController {

  var monthlyTerms: NSArray!

  override func viewDidLoad() {
//  super.onCreate(savedInstanceState);
//  mAdapter = new BaseAdapter() {
//    ...
//  };
//  this.setListAdapter(mAdapter);
    super.viewDidLoad()
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection   section: Int) -> Int {
// @Override public int getCount() {
//   return monthlyTerms.length();
// }
    return monthlyTerms.count
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath   indexPath: NSIndexPath) -> UITableViewCell {
// @Override public View getView(int pos, View view, ViewGroup parent) {
//   if (view == null) {
//     view = getActivity().getLayoutInflater().inflate(...);
//   }
//   TextView textLabel = (TextView) view.findViewById(...);
//   TextView detailTextLabel = (TextView) view.findViewById(...);
//
//   JSONObject monthlyTerm =(JSONObject)monthlyTerms.opt(pos);
//   int pmtNo = monthlyTerm.optInt("pmtNo");
//   double balance0 = monthlyTerm.optDouble("balance0");
//   textLabel.setText(String.format("%d $%.2f", pmtNo, balance0));
//
//   double interest = monthlyTerm.optDouble("interest");
//   double principal = monthlyTerm.optDouble("principal");
//   detailTextLabel.setText(String.format("Interest: %.2f Principal:
%.2f", interest, principal));

//   return view;
// }
    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
    detailTextLabel.text = NSString(format: "Interest: %.2f Principal:
%.2f", interest, principal);


    return cell
  }

  override func viewDidAppear(animated: Bool) {
// super.onResume();
// ((MainActivity) getActivity()).slideIn(...);
// getActivity().setTitle(getText(...));
    super.viewDidAppear(animated)
    self.navigationItem.title = NSLocalizedString("label_Amortization",
comment: "")

  }

// public void onListItemClick(...) {
//   MonthlyTermViewFragment toFrag = new MonthlyTermViewFragment();
//   JSONObject jo = (JSONObject) mAdapter.getItem(position);
//   toFrag.setMonthlyTerm(jo);
//   ((MainActivity)getActivity()).pushViewController(toFrag);
// }
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath   indexPath: NSIndexPath) {
    self.performSegueWithIdentifier("MonthlyTerm", sender: indexPath)
  }

  override func prepareForSegue(segue: UIStoryboardSegue, sender:   AnyObject?) {
    var vc = segue.destinationViewController as MonthlyTermViewController
    var row = (sender! as NSIndexPath).row
    vc.monthlyTerm = monthlyTerms[row] as NSDictionary
  }
}

This completes the whole AmortizationViewController Swift class implementation.

MonthlyTermViewController

Move on to the MonthlyTermViewController. It needs to render the detailed info for the selected month. Do the following to port the Java implementation from Android to iOS Swift as shown in Listing 5-24:

  1. Port Android Fragment life cycles to iOS View life cycles.
  2. Convert the Android BaseAdapter to iOS DataSource and implement the delegate methods.

Listing 5-24. MonthlyTermViewController Life Cycle Callbacks

class MonthlyTermViewController : UITableViewController {
  ...
// @Override public View onCreateView(...) {
//   contentView = inflater.inflate(...e);
//
//   mPaymentNo = (TextView)contentView.findViewById(...);
//   mTotalPmt = (TextView)contentView.findViewById(...);
//   mPrincipal = (TextView)contentView.findViewById(...);
//   mInterest = (TextView)contentView.findViewById(...);
//   mEscrow = (TextView)contentView.findViewById(...);
//   mAddlPmt = (TextView)contentView.findViewById(...);
//   mBalance = (TextView)contentView.findViewById(...);
//   mEquity = (TextView)contentView.findViewById(...);
//   mCashInvested = (TextView)contentView.findViewById(...);
//   mRoi = (TextView)contentView.findViewById(...);
//
//   double principal = this.monthlyTerm["principal");
//   double interest = this.monthlyTerm["interest");
//   double escrow = this.monthlyTerm["escrow");
//   double extra = this.monthlyTerm["extra");
//   double balance = this.monthlyTerm["balance0") - principal;
//   int paymentPeriod = this.monthlyTerm.optInt("pmtNo");
//   double totalPmt = principal + interest + escrow + extra;
//   this.mTotalPmt.setText(String.format("$%.2f", totalPmt));
//   this.mPaymentNo.setText(String.format("No. %d", paymentPeriod));
//   this.mPrincipal.setText(String.format("$%.2f", principal));
//   this.mInterest.setText(String.format("$%.2f", interest));
//   this.mEscrow.setText(String.format("$%.2f", escrow));
//   this.mAddlPmt.setText(String.format("$%.2f", extra));
//   this.mBalance.setText(String.format("$%.2f", balance));
//
//   RentalProperty property = RentalProperty.sharedInstance();
//   double invested = property.getPurchasePrice() - property.getLoanAmt() +
     property.getExtra() * paymentPeriod;

//   double net = property.getRent() - escrow - interest - property.getExpenses();
//   double roi = net * 12 / invested;
//
//   this.mEquity.setText(String.format("$%.2f", property.getPurchasePrice()
     - balance));

//   this.mCashInvested.setText(String.format("$%.2f", invested));
//   this.mRoi.setText(String.format("%.2f%% ($%.2f/mo)", roi * 100, net));
//   return contentView;
// }
  override func viewDidLoad() {
    super.viewDidLoad()
    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.getPurchasePrice() - property.getLoanAmt() +
    (property.getExtra() * Double(paymentPeriod))

    var net = property.getRent() - escrow - interest - property.
    getExpenses();

    var roi = net * 12 / invested

    self.mEquity.text = NSString(format: "$%.2f", property.
    getPurchasePrice() - balance)

    self.mCashInvested.text = NSString(format: "$%.2f", invested)
    self.mRoi.text = NSString(format: "%.2f%% ($%.2f/mo)", roi * 100, net)
  }

  override func viewDidAppear(animated: Bool) {
//  super.onResume();
//  ((MainActivity) getActivity()).slideIn(contentView, MainActivity.SLIDE_LEFT);
//  getActivity().setTitle(getText(R.string...));

    super.viewDidAppear(animated)
  }
  ...

This completes the whole MonthlyTermViewController Swift class implementation.

RESTful Service and Saving Data

Back to the RentalPropertyViewController—when the “Schedule” UIBarButtonItem is selected, the iOS app does the following:

  1. Checks if the amortization schedule is already saved locally.
  2. If there is no schedule found in local storage, it calls a remote RESTful service to get the schedules and save them in local storage.
  3. Presents the AmortizationViewController, which renders the schedules in it in content view.

The preceding Android code was previously copied into your iOS doAmortization() method. Your mission is to translate this method to Java code, as shown in Listing 5-25.

Listing 5-25. doAmortization(...)

  private func doAmortization() {
// _savedAmortization = _property.getSavedAmortization(getActivity());
// if (_savedAmortization != null) {
//   AmortizationViewFragment toFrag = new AmortizationViewFragment();
//   toFrag.setMonthlyTerms(_savedAmortization);
//   ((MainActivity) getActivity()).pushViewController(toFrag, true);
// } else {
//   String url = String.format(URL_service_tmpl, _property.getLoanAmt(),
     _property.getInterestRate(), _property.getNumOfTerms(), _property.
    getExtra(), _property.getEscrow());

//   getActivity().setProgressBarIndeterminate(true);
//   getActivity().setProgressBarVisibility(true);
//
//   AsyncTask<String, Float, JSONObject> task = new AsyncTask<String,
     Float, JSONObject>() {

//     @Override
//     protected JSONObject doInBackground(String... params) {
//       String getUrl = params[0];
//       InputStream in = null;
//       HttpURLConnection conn = null;
//
//       JSONObject jo = new JSONObject();
//       try {
//         URL url = new URL(getUrl);
//         // create an HttpURLConnection by openConnection
//         conn = (HttpURLConnection) url.openConnection();
//         conn.setRequestMethod("GET");
//         conn.setRequestProperty("accept", "application/json");
//
//         int rc = conn.getResponseCode(); // HTTP status code
//         String rm = conn.getResponseMessage(); // HTTP response message.
//         Log.d("d", String.format("HTTP GET: %d %s", rc, rm));
//
//         // read message body from connection InputStream
//         in = conn.getInputStream();
//         StringBuilder builder = new StringBuilder();
//         InputStreamReader reader = new InputStreamReader(in);
//         char[] buffer = new char[1024];
//         int length;
//         while ((length = reader.read(buffer)) != -1) {
//             builder.append(buffer, 0, length);
//         }
//         in.close();
//
//         String httpBody = builder.toString();
//         jo.put(KEY_DATA, httpBody);
//
//       } catch (Exception e) {
//         e.printStackTrace();
//         try {
//           jo.putOpt(KEY_ERROR, e);
//         } catch (JSONException e1) {
//           e1.printStackTrace();
//         }
//       } finally {
//         conn.disconnect();
//       }
//       return jo;
//     }
//
//     @Override
//     protected void onPostExecute(JSONObject jo) {
//       getActivity().setProgressBarVisibility(false);
//       Exception error = (Exception) jo.opt(KEY_ERROR);
//       String errMsg = null;
//       if (error == null) {
//         AmortizationViewFragment toFrag = new AmortizationViewFragment();
//         String data = jo.optString(KEY_DATA);
//         _property.saveAmortization(data, getActivity());
//
//         try {
//           toFrag.setMonthlyTerms(new JSONArray(data));
//           ((MainActivity) getActivity()).pushViewController(toFrag, true);
//           return;
//         } catch (JSONException e) {
//           e.printStackTrace();
//           errMsg = e.getMessage();
//         }
//       } else {
//         errMsg = error.getMessage();
//       }
//       Toast.makeText(getActivity(), errMsg, Toast.LENGTH_LONG).show();
//     }
//   };
//   task.execute(url);
// }
...

Saving Data

In the Android RentalPropertyViewFragment.doAmortization(...), the code for saving and retrieving data is delegated to the RentalProperty model class. This uses SharedPreferences, which should be translated to iOS NSUserDefaults (see Chapter 4, “NSUserDefaults”). Do the following:

  1. Use NSUserDefaults to create the following utility methods in RentalProperty.swift methods as shown in Listing 5-26.

    Listing 5-26. Porting RentalProperty Save Data Utility Methods

    class RentalProperty {
      ...
      let userDefaults = NSUserDefaults.standardUserDefaults()
      func saveUserdefault(data:AnyObject, forKey:String) -> Bool{
        userDefaults.setObject(data, forKey: forKey)
        return userDefaults.synchronize()
      }

      func retrieveUserdefault(key: String) -> AnyObject? {
        var obj: AnyObject? = userDefaults.objectForKey(key)
        return obj
      }

      func deleteUserDefault(key: String) {
        self.userDefaults.removeObjectForKey(key)
      }
      ...
  2. Translate the load() method that loads the saved RentalProperty object from storage, as shown in Listing 5-27.

    Listing 5-27. Loading RentalProperty Object from Storage

    class RentalProperty {
      ...
    // public boolean load(Context activity) {
    //   String jostr = retrieveSharedPref(KEY_PROPERTY, activity);
    //   if(jostr == null) {
    //     return false;
    //   }
    //
    //   try {
    //     JSONObject jo = new JSONObject(jostr);
    //     this.purchasePrice = jo.getDouble("purchasePrice");
    //     this.loanAmt = jo.getDouble("loanAmt");
    //     this.interestRate = jo.getDouble("interestRate");
    //     this.numOfTerms = jo.getInt("numOfTerms");
    //     this.escrow = jo.getDouble("escrow");
    //     this.extra = jo.getDouble("extra");
    //     this.expenses = jo.getDouble("expenses");
    //     this.rent = jo.getDouble("rent");
    //     return true;
    //   } catch (JSONException e) {
    //     e.printStackTrace();
    //     return false;
    //   }
    // }
      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
        }
      }
      ...
  3. Translate the save() method that persists the RentalProperty instance in storage as shown in Listing 5-28.

    Listing 5-28. Saving RentalProperty Object

    class RentalProperty {
      ...
    // public boolean save(Context activity) {
    //   JSONObject jo = new JSONObject();
    //   try {
    //     jo.put("purchasePrice", purchasePrice);
    //     jo.put("loanAmt", loanAmt);
    //     jo.put("interestRate", interestRate);
    //     jo.put("numOfTerms", numOfTerms);
    //     jo.put("escrow", escrow);
    //     jo.put("extra", extra);
    //     jo.put("expenses", expenses);
    //     jo.put("rent", rent);
    //   } catch (JSONException e) {
    //     e.printStackTrace();
    //   }
    //   return this.saveSharedPref(KEY_PROPERTY, jo.toString(),
         activity);

    // }
      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)

      }
      ...
  4. Translate the getSavedAmortization() method that retrieves the amortization schedule array from storage as shown in Listing 5-29.

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

    class RentalProperty {
      ...
    // public JSONArray getSavedAmortization(Context activity) {
    //   String savedKey = retrieveSharedPref(KEY_AMO_SAVED, activity);
    //   String aKey = this.getAmortizationPersistentKey();
    //   if(savedKey.length() > 0 && savedKey.equals(aKey)) {
    //     String jsonArrayString = retrieveSharedPref(savedKey, activity);
    //     try {
    //       return new JSONArray(jsonArrayString);
    //     } catch (JSONException e) {
    //       return null;
    //     }
    //   } else {
    //     return null;
    //   }
    // }
      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
      }
      ...
  5. Translate the saveAmortization() method that persists the amortization schedule array as shown in Listing 5-30.

    Listing 5-30. Persist Amortization Schedule Array

    class RentalProperty {
      ...
    // public boolean saveAmortization(String data, Context activity) {
    //   String aKey = this.getAmortizationPersistentKey();
    //   saveSharedPref(KEY_AMO_SAVED, aKey, activity);
    //   saveSharedPref(aKey, data, activity);
    // }
      func saveAmortization(data: NSArray) -> Bool {
        var aKey = self.getAmortizationPersistentKey()
        saveUserdefault(aKey, forKey: MyStatic.KEY_AMO_SAVED)
        return saveUserdefault(data, forKey: aKey)
      }
      ...
  6. Translate the rest of the commented Java code to Swift as shown in Listing 5-31. Swift static variables are always lazily initialized. Unlike in Java, you don’t need to do null check in the sharedInstance() method.

Listing 5-31. Miscellaneous Methods in RentalProperty Model

class RentalProperty {
  ...
// public static RentalProperty sharedInstance() {
//   if (_sharedInstance == null) {
//     _sharedInstance = new RentalProperty();
//   }
//   return _sharedInstance;
// }
  class func sharedInstance() -> RentalProperty {
    return MyStatic._sharedInstance
  }

// public String getAmortizationPersistentKey() {
//   String aKey = String.format("%.2f-%.3f-%d-%.2f", this.loanAmt, this.interestRate, this.numOfTerms, this.extra);
//   return aKey;
// }
  func getAmortizationPersistentKey() -> String {
    var aKey = String(format: "%.2f-%.3f-%d-%.2f", self.loanAmt, self.interestRate, self.numOfTerms, self.extra);
    return aKey;
  }
  ...

You have ported the “save data” code and the whole RentalProperty model class from the Android app.

Use RESTful Service

Recall the doAmortization() method—it performs remote operations in the background, then updates the UI when data is returned. The Android counterpart uses AsyncTask and HttpURLConnection to accomplish this task (see Listing 5-25). In iOS, use the instructions in the “NSURLConnection” section in Chapter 4 to do the following:

  1. To pass data to the presented AmortizationViewController from the presenting RentalPropertyViewController, call performSegueWithIdentifier(...) and prepareForSegue(...).
  2. To get data from a remote RESTFul service, use iOS’s NSURLConnection.sendAsynchronousRequest to replace the Android AsyncTask+ HttpURLConnection as shown in Listing 5-32.

Listing 5-32. Using RentalPropertyViewController to Pass Data to the Presented AmortizationViewController

class RentalProperty {
  ...
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var identifier = segue.identifier
    if identifier == "EditText" {
    ...
    } else { // AmortizationTable segue
      // AmortizationViewFragment toFrag = new AmortizationViewFragment();
      // toFrag.setMonthlyTerms(_savedAmortization);
      var toFrag = segue.destinationViewController as AmortizationViewController
      toFrag.monthlyTerms = sender as NSArray
    }
  }

  private func doAmortization() {
    _savedAmortization = _property.getSavedAmortization();
    if (_savedAmortization != nil) {
      performSegueWithIdentifier("AmortizationTable", sender: _savedAmortization!)
    } else {
      var url = NSString(format: MyStatic.URL_service_tmpl, _property.
      getLoanAmt(), _property.getInterestRate(), _property.getNumOfTerms(),
      _property.getExtra(), _property.getEscrow())

      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 {
                    self._property.saveAmortization(json!)
                    self.performSegueWithIdentifier("AmortizationTable",
                    sender: json!)

                    return
                  } else {
                    errMsg = parseErr?.debugDescription
                  }
                } else {
                  errMsg = "HTTP RC: (statusCode)"
                }
              } else {
                errMsg = error.debugDescription
              }

              // show error
              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)

          })
      })
    }
  }
  ...

All the class translations are completed. Build and run the iOS RentalROI app to make sure it behaves the same as the Android app. I normally put the iOS and Android apps side by side for testing. Even for testing activities, it takes less time to test iOS and Android apps in parallel. Figure 5-12 shows the iOS version in action.

9781484204375_Fig05-12.jpg

Figure 5-12. The completed iOS RentalROI app

Summary

This chapter intended to show how to port the whole app, end to end, by applying the individual mapping topics introduced in Chapters 3 and 4, such as the master list details drill-down, navigation patterns, basic UI widgets, saving data, and using remote services.

You started by using the storyboard to create MVC components and using storyboard segues to connect the View Controller together. The result was a set of connected View Controllers.

You continued to drill down into each class, one by one, starting by translating all the members’ declarations for all the classes first. Then, you drilled down into each method. Translating expressions in each method was generally straightforward. Using a global find-and-replace makes this type of translation quick and fun. When you encounter platform-specific SDK or topics, use this book’s Table of Contents to find the instructions that will guide you through your porting efforts.

As you port an app, you will start to see more searchable and replaceable patterns. I use the Xcode editor’s Find and Replace All one click at a time, so that I can have a quick read on the code being replaced. Learning is the main objective in your early iOS journey. Reading, typing, and debugging the code seems the best way to learn a new programming language.

Although the RentalROI app is not complicated enough to show you more advanced topics that are not included in this book, the porting steps remain the same: you always break the app down into the smallest porting components possible—a single line of expression, a method, or sometimes an entire class or even a common use case. This porting strategy always works for me.

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

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