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.

Thursday, May 30, 2019

xcode compile error after generating CoreDate NSManagedObject subclass using wizard

In xcode, after generating the subclass of NSManagedObject on a data model, the xcode will get some compile error due to duplicated class name.

This is because xcode will automatically generate a swift class for any CoreDate data model objects added in the project. So once you use the Xcode Editor->Create . NSManagedObject Subclass ... wizard to generate the swift class again, they will have the same class name and cause a compile error.

To avoid the error,
1. first select the xcdatamodeld item, and then select the data model entity.
2. open the data mobel inspector, and change teh Codegen to "Manual/None"
3. Clean and rebuild the project.

 

Thursday, May 16, 2019

Swift: invocation of extension method defined in Protocol calls the static linked method

Polymorphism is a basic feature for object oriented design. Basically, it means when calling a method on an object, no matter the variable type that represents the object (base class, derived sub class, or protocol), the actual method implementation that will be invoked will always be the last override method available in the subclass tree. This is also called dynamic binding, that is why when calling objective c object's method, we never need to cast the object to a particular type, as polymorphism will also end up calling the last override method.

However, unlike objective c, which always uses dynamic binding, Swift uses both dynamic and static binding.

In swift, if a method is defined in protocol, or a class, then all struct, class, or subclass, which implements the protocol or inherits from the class will promise they will abided by the method signature, and may also provide an override implementation. In this aspect, the invocation on methods defined in protocol, or class use dynamic binding as objective c does.

However, things are different when a method is defined in extension for a protocol or class. as extension does not belong to the contract between  base class (or protocol) and sub class, so if same method is provided in a protocol extension and a subclass, the two methods do not have any connections. So in this case, Swift uses static binding for invocation of extension method, and the actual method that will be invoked depends on the variable type at compile time, instead of the actual object type at runtime.

The behavior can be demonstrated with the below code


protocol Movable {
    func move()
}

extension Movable {
    func move() {
       print("move implemented by Movable Protocol extension")
    }
    
    func pause() {
       print("pause implemented by Movable Protocol extension")
    }
}

struct Animal: Movable {
    func move() {
        print("move implemented by Animal structure")
    }
    
    func pause(){
         print("pause implemented by Animal structure")
    }
}

class Car: Movable {
    func move() {
        print("move implemented by Car class")
    }
    
    func pause(){
         print("pause implemented by Car class")
    }
}

extension Car {
    @objc func stop() {
        print("stop implemented by Class extension")
    }
}

class BMW: Car {
    override func move() {
        print("move implemented by BMW class")
    }
    
    override func pause(){
         print("pause implemented by BMW class")
    }
    
    override func stop() {
        print("stop implemented by BMW class")
    }
}


When calling the below code
      let a : Animal = Animal()
        a.move()
        a.pause()
        
        let ap : Movable = a
        ap.move()
        ap.pause()
        
        let c : Car = Car()
        c.move()
        c.pause()
        c.stop()
        
        let cp : Movable = c
        cp.move()
        cp.pause()
        
        
        let b : BMW = BMW()
        b.move()
        b.pause()
        b.stop()
        
        let bc : Car = b
        bc.move()
        bc.pause()
        bc.stop()
        
        let bcp : Movable = bc
        bcp.move()

        bcp.pause()


the output is shown below, and even if the object instance stays the same, the actual method invoked depends on the variable type it casts. The output shows few method calling into Movable protocol's implementation instead of actual Animal, Car or BMW's implementation.


move implemented by Animal structure
pause implemented by Animal structure
move implemented by Animal structure
pause implemented by Movable Protocol extension
move implemented by Car class
pause implemented by Car class
stop implemented by Class extension
move implemented by Car class
pause implemented by Movable Protocol extension
move implemented by BMW class
pause implemented by BMW class
stop implemented by BMW class
move implemented by BMW class
pause implemented by BMW class
stop implemented by BMW class
move implemented by BMW class
pause implemented by Movable Protocol extension


Note, this is an issue only for protocol extension, but not for class extension. As swift will report a compile error if a subclass (or subclass' extension) tries to add a method which has the same signature as defined in base class' extension. In that case, the base class extension method must be defined as @objc, which effectively change it to dynamic binding as objective c does.

Monday, May 13, 2019

Associated Value for Swift Enum type

  1. Usually for simple enum type, it can defined as below. Each enum value can be used as constant definition. For example, if you define enum Barcode { case upc case qrCode } Then in your code, you can set var m = Barcode.upc or check whether a variable equals Barcode.upc or Barcode.grCode in a switch statement. In swift, enum type can also define associated type. When associated type is defined for enum type, then each enum instance must specify the value of the associated types. In this sense, the enum variable with associated type is more like an object with properties. Associated type can be easily accessed from switch statement as shown below enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String) } var productBarcode = Barcode.upc(8, 85909, 51226, 3) var productBarcode2 = .qrCode("ABCDEFGHIJKLMNOP") switch productBarcode { case .upc(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") case .qrCode(let productCode): print("QR code: \(productCode).") }

Note, in the below code, the type of v and vv are different

        let v = Barcode.upc . //type of v is (Int, Int, Int, Int) -> Barcode
        let vv = Barcode.upc(1, 2, 3, 4) . //type of vv is upc(1, 2, 3, 4)



Optional type is a special usage of enum. In swift, Optional is defined as
enum Optional<Wrapped> {
case none
  case some(Wrapped)
}
nil is just a shortened form of Optional.none.
SomeType? is a shorted form of Optional<SomeType>.
Sometime, it is needed to use the original Optional type to set the nested optional value, such as, setting String??'s value to a String? object whose value is nil as shown below

let s1: String?? = Optional.some(nil)
let s2 :String?? = .none
print(String(describing: s1))
if let s3 = s2 {
    print("s3 is \(String(describing: s3))")
    if let s4 = s3 {
        print("sssnil is \(String(describing: s4))")
    }
    else {
        print("s3's value is nil")
    }
}


Closure and function in swift

In swift, closure and function are same type as they are defined with the same format as below

      var closureargu : (Int, String)->String 

The above variable can be set either to a function by assigning it to a function name, or it can be set to a closure block.

A function is a particular form of closure, which includes the parameter name information, similar as closure's body implementation.

Even if a function has specified argument name, when the function is assigned to a closure variable, invoking the function by the closure variable does not need to specify the argument name as the closure variable only knows the type information without the argument name information. So once a function or closure body is assigned to a closure variable, it does not know whether the body implementation is from a function or closure.

Actually, when function or signaure used as a type, it cannot include argument label information, otherwise xcode will generate a compile error of "Function types cannot have argument labels" error. For example, the below code does not compile as (sumValue : Result, item : Element) -> Result) defines a function type with argument labels in it. 

func reduce<Result>(initial : Result, sum : (sumValue : Result, item : Element) -> Result) -> Result {
        var total = initial
        for it in self {
            total = sum(total, it)
        }
        return total
    }

To fix the compile error, the code should be changed as
func reduce<Result>(initial : Result, sum : (Result, Element) -> Result) -> Result {
        var total = initial
        for it in self {
            total = sum(total, it)
        }
        return total
    }
}


The below sample shows this behavior

var handlerWithParamAndValue : (Int, String) -> String  =  {pi, ps in
        return pi.description + " " + ps

}


func functionSample(a1 a: Int,  b1 b : String) -> String {
        return a.description + b.description
}
    
(Theoretically, the below code should not need @escaping keyword, as closure cannot be called after the function returns. However, without specifying @escaping, a compile error happens in xcode)

func someFunction(_ p1: Int, _ p2: String,  clo: @escaping (Int, String)->String) -> String {

       //assign function to variable
        var closureargu : (Int, String)->String = functionSample
        var re = closureargu(2, "def")
        
        //assign closure variable 
        closureargu = handlerWithParamAndValue
        re = closureargu(3, "mnq")
    
        //assign closure body
        closureargu = {p1, p2 in
           return p1.description + p2.description
        }
        re = closureargu(4, "me")
        
        //assign input closure parameter
        closureargu = clo
        re = clo(p1, p2)
        re = closureargu(p1, p2)
        
        //direct invoke function
        re = functioinSample(a1: 1, b1: "hihi")
        print(re)
        return re
}


//call the testing method
let r = someFunction( 1, "string", clo: {(a1 , a2) in

            let m = a1 + a1
            return a2.description + m.description          
        })


One unique feature to Closure is it allows specifying a capture list  before parameter list to change the self or other caller scope variables to weak or unowned, as shown below
  1. var asHTML: () -> String = {
  2. [unowned self] in
  3. if let text = self.text {
  4. return "<\(self.name)>\(text)</\(self.name)>"
  5. } else {
  6. return "<\(self.name) />"
  7. }
  8. }
Closure can update the value of the captured variables in outer scope, as long as the variable is mutable (defined by var, not by let).