Thursday, July 18, 2019

Three dot operator in Swift

You may see three dot ... operator in swift for two different cases:

1. ... as variadic parameter in function definition
A variadic parameter accepts zero or more values of a specified type. The parameters are separated with comma. The value of a variadic parameter in the function’s body is an array with the specified element type. A function can only have at most one variadic parameter.

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}

arithmeticMean(1, 2, 3, 4, 5)
arithmeticMean()

2. ... as a closed range operator in statement
The closed range operator a...b creates a ClosedRange<T> object that contains elements from a to b inclusive. The value of a must not be greater than b. It is most used to enumerate all elements in a for loop

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

Wednesday, July 17, 2019

Property in Swift

1.  Stored or Computed property

In Swift, both struct and class can define properties. There are two types of property, stored property and computed property.

In property definition, if it only contains property name and type, or it is followed by assignment '=', then it is a stored property, as shown below
var stored2 : Int
var stored1 : Int = 0
var stored3 : String = { return "hi"}()

If after the variable name and type,  and immediately followed with a curly bracket, then within the curly bracket, if it only contains willSet and/or didSet, then it is still a stored property.
     var m :Int  {
         willSet {
             print("The value is changed from \(m) to \(newValue)")
         }
         didSet {
            print("The value is changed from \(oldValue) to \(m)")
         }
    }

But if within the curly bracket it contains get and/or set, or it only contains a closure expression (for the shorthand get accessor), then it is a computed property.
   var computed :Int {
        get {
             return m
        }
        set (newValue) {
            m = newValue
       }
  }

  var computed2: Int {
      return m
 }

2. Initialize stored property with closure
Usually, stored property can be initialized by assigning it to a new object as shown below
var myComplex : UIViewController = UIViewController()

But it can also be initialized by using a closure expression with parameters in the ending parenthesis as shown below 
var myComplex2 : [Int] = { return Array(repeating: $0, count: $1)}( 5, 6)

Note this is quite similar to readonly computed property, although the assignment operator indicates it is a stored property

3. Stored property only attribute
willSet, willGet accessor can only applied to stored properties, and cannot apply to computed property, as for computed property, developer can add any logic in the get/set accessors, and there is no need to use willSet/willGet accessors.

For the similar reason, lazy property can only apply to stored properties. In addition, lazy property should be defined as no-optional type properties, instead of optional type, and it must be defined as var instead of let.

The reason to use lazy property is, usually in the init method, all no-option stored properties should be initialized. However, if a property is defined as lazy, it is not required to be initialize in init method. The lazy property's value will be set when it is used first time.
lazy var lazyBoy : ComplexData = ComplexData()

In multiple thread situation, if multi threads access the lazy property at the same time, the property may be initialized more than once.

Lazy property should not be applied to struct, as it will requires the struct can only be used by var instead of let.
 

Sunday, July 14, 2019

Default compare operator on swift tuple type

Swift standard library defineds tuple comparison operators for tuple with fewer than seven elements.
In order to compare two tuples, they must have the same type and the same number of values. Tuples are compared from left to right, one value at a time. If the comparison finds two values are equal, then it move to the next element to decide the overall compare result. If all elements are equal, then the two tuples are equal.

  1. (1, "zebra") < (2, "apple")
  2. // true because 1 is less than 2; "zebra" and "apple" are not compared

  3. (3, "apple") < (3, "bird")
  4. // true because 3 is equal to 3, and "apple" is less than "bird"

  5. (4, "dog") == (4, "dog")
  6. // true because 4 is equal to 4, and "dog" is equal to "dog"


As a result, the common comparison operators can be directly applied to tuple array as shown below

var numberStrings = [(2, "two"), (1, "one"), (3, "three")]
numberStrings.sort(by: <)
numberStrings // [(1, "one"), (2, "two"), (3, "three")]

Sunday, July 7, 2019

Difference between swift map and flatMap

In swift, Array's map method has below signature
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

The flapMap method has the below signature
func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

Both map and flatMap methods have a transform method to convert the original array element to a different array. In map, the transform takes a single element as input parameter and generate a single output element, then all output elements are appended in an array.

In flatMap, the transform method takes a single element as input parameter, however, the output is an array of ouput element type, and then all return arrays from the transform method are appended together into a single array and returns to caller.

Samples from Swift Array document
let numbers = [1, 2, 3, 4]

let mapped = numbers.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]

let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

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.