Wednesday, June 19, 2019

Ways to create React Native hybrid project

Option 1:
The most basic way to create React Native project is using react native cli. React Native CLI can be installed with below command
npm i -g react-native-cli
you can also check the version of installed React Native CLI with below command
react-native -v 

You can create a react native project with the below command
react-native init myReactNativeProj

Once the command is finished, you can see the generated React Native project includes the subfolders containing xcode project and android studio project, so the xcode and android studio projects will be used to run and test the project on device in the same way as ordinary native projects. That is way the React Native project created by react-native command requires the native developing environment installed on your local box.

Option 2:
The second option to create React Native project is using create-react-native-app tool. When using this tool, it is assumed that the project will be implemented with pure javascript without the need to link to any custom native libraries. So there is no need to install xcode or android studio on your development box to developing the react native project. In order to run and test the project,  the device must install a client side application Expo. The javascript code will be sent to Expo, and Expo will load and parse the javascript code and translate them into native UI controls as well as the related logic. Create-react-native-app tool is the result of the joint effort between React Native and Expo teams


The create-react-native-app tool can be installed with the below command
npm i -g create-react-native-app

You can check the installed create-react-native-app version with the below command

create-react-native-app --version

You can create a react native with this option using the below command. The command may ask you to install Expo CLI, when asked, select Yes to install id.
create-react-native-app mySecondApp

After the project is created, you can see the project root directory only contains javascript code, and does not contain the ios or android subfolders as option 1 does.

The advantage of this option is, you do not need to install platform specific development environment in your local box for developing and testing react native project on real device. But the limitation is, you cannot link any native libraries into the project. Note, the local development environment is still needed if you want to test the app on simulator.

You can test the project by running the below command from project's root folder. This will show a QR code and related instruction on both console window and a new browser window.
npm start

To run the app on real device, first install Expo client app on device. Then, for iOS, open camera app to scan the QR code.  For android, open Expo app to scan the QR code. The QR code contains the custom scheme of Expo app as below sample
exp://192.168.2.20/19000

To run the app on simulator, you will need have xcode or android studio from the desktop browser, click the related link, it will automatically start the simulator and install Expo client app.

Actually, as an extension of option 2, Expo SDK can also be used to create react native app.  Expo Framework provides addition mobile features to React Native, so these features can be easily integrated in React Native projects. 


Debug javascript code on real device
In order to debug the javascript code on real device, just shake the device to bring in the debug menu on device, and then clicks "Debug Remote JS" menu item. You can also click "Toggle Element Inspect" to check element information on device.  However, to check the element information, it is better to install react dev tool with below command
npm install -g react-devtools

After installing, run the below command will start a React's Electron debugger.

Difference between struct and class in swift

In Swift, similar to C++, struct instance is created on stack, and class instance is created on heap. When assigning a struct variable to another, a new struct instance is created and all properties are copied. When assigning a class variable to another, the two variable points to the same class instance. === and !== can be used to check whether two variable refer to the same class instance

Struct method cannot change its property unless mutating is specified.
Class method can change its property.

Struct variable defined with let, its properties cannot be changed by assigning to different value
Class variable defined with let,  its properties can be assigned to different value.

Struct cannot inherit from base struct, only protocol and extension are available for struct
Class can inherit from base class.


Note similar types in swift and objective C can have different behavior. For example, Array in swift is struct type, but NSArray in objective c is class type. So when assigning a swift Array variable to a different variable, a new array object is created. On the contrary, when assigning a Objective C NSArray variable to a different variable, two variables points to the same instance and === operator will return true.

Actually operator === cannot be used to compare Swift struct variables, as they always point to different instance.

Wednesday, June 5, 2019

Handle Edit Mode in iOS UITableView

iOS UITableView in iOS has build-in support for editing on both tableview level and tableview cell level, many common operation can be handled in TableView's edit mode.

1. Set Edit Mode in TableView level
UITableView has a isEditing property, just setting the property to true will put tableview into edit mode. By default, a delete button shows on left side of each rows as shown below.


The edit icon can be customized as delete,  insert, or nothing on the left side of the row in edit mode. This can be set for each row by calling UITableViewDelegate editingStyleForRowAt method as shown below

override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        if indexPath.row % 3 == 0 {
            return .delete
        }
        else if indexPath.row % 3 == 1 {
            return .insert
        }

        else {
            return .none
        }
   }



In case, a particular row cannot be edited, then it can be customized by UITableViewDataSource canEditRowAt method as below
verride func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        if indexPath.row  == 3 {
            return false
        }
        else {
            return true
        }

    }

After user clicks the delete button or insert icon, UITableViewDataSource.commiteditingStyle method will be called to let application handle the action. After the action is handled, the tableview is still staying in the edit mode.

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // Delete the row from the data source
           print("delete button clicked at \(indexPath.section)\\\(indexPath.row)")
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
            print("insert button clicked at \(indexPath.section)\\\(indexPath.row)")
        }
    }

If the TableView is embedded in navigation viewcontroller, then there is build-in edit button that shows on top right side of the navigation bar, use the below code to enabled it in viewDidLoad method.

   self.navigationItem.rightBarButtonItem = self.editButtonItem
When user clicks the edit button, the TableView.isEditing is set to true automatically, and the button
title is changed to Done. Clicking Done button will set isEditing to false and show Edit title for the button again.

 

2. Show Reorder control in TableView Edit mode
In addition to delete or insert, UITableView also show a reorder icon (two horizontal lines on right side) in Edit mode as below


In order to show the reorder button in table view's edit mode, UITableViewDataSource.moveRowAt method must be implemented. (It is not needed to check the checkbox of "Shows Re-order Controls" on tableview cell settings from storyboard editor).
After user moves a row to a different position, UITableViewDataSource.moveRowAt method will be called to let application handle the move result as below.

  override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        print("moveRowAt called from \(sourceIndexPath) to \(destinationIndexPath)")
    }


3. Edit mode for single row
In addition to set the edit mode at TableView level, user can also set a single tableview row into Edit mode by swiping left to right, or right to left on a single row.

By default, the delete edit style is used when user swipes from right to left on a cell. This can be enabled by implementing UITableViewDataSource.commitEditingStyle method, so UITableView knows how to handle the delete operation, the method is called when user clicks the delete button.

  // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        print("commitEditingStyle called at \(indexPath)")
        if editingStyle == .delete {
            // Delete the row from the data source
           //tableView.deleteRows(at: [indexPath], with: .none)
           print("delete button clicked at \(indexPath.section)\\\(indexPath.row)")
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
            print("insert button clicked at \(indexPath.section)\\\(indexPath.row)")
        }

    }

If you do not want to show the delete button on a particular row, you can customize it by editingStyleForRowAt method, and return .none for the row. Note for single row edit mode, .insert is not supported.
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        print("editingStyleForRowAt called for \(indexPath)")
        
        if indexPath.row % 3 == 0 {
            return .delete
        }
        else if indexPath.row % 3 == 1 {
            return .insert
        }
        else {
            return .none
        }

   }


Starting from iOS 11, two new API were added in UITableViewDelegate to allow developer to customize the button when user swipes a tableview row from left to right, or from right to left. 
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?

func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?

The below code shows two buttons when user swipes left to right. These two method works without the need to customize any other UIWebView method. The below code shows two button on left side of the tableview row.

override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        print("leadingSwipeActionsConfigurationForRow called, is Editing \(tableView.isEditing)")
        
            let add = UIContextualAction(style: .normal, title: "add2") { (action, view, completion ) in
            print("add2 called, table is Editing \(tableView.isEditing)")
            tableView.isEditing = false
            
        }
        
        let delete = UIContextualAction(style: .destructive, title: "delete2") { (action, view, completion ) in
            print("delete button clicked, is Editing \(tableView.isEditing)")
            tableView.isEditing = false
        }
        let config = UISwipeActionsConfiguration(actions: [add, delete])
        return config
    }

If trailingSwipeActionsConfigurationForRowAt is not implemented, then the default delete button will show when user swipes from right to left.