Category: Uncategorized

espoCRM customization: allow non-sysadmin user edit users data

problem: be default, espoCRM only expose users data to users with sysadmin role. in role access management, user entity is not in the manage entity list.


1. override acl of user entity at custom/Espo/Custom/Resources/metadata/scopes/User.json. add edit in aclActionList, add “team”, “all” in aclActionLevelListMap.

    "aclActionList": ["read", "edit"],
    "aclActionLevelListMap": {
        "edit": ["own", "no", "team", "all"]

2. override acl user.php at custom/Espo/Custom/Acl/User.php. ensure to remove or change line of

        if (!$user->isAdmin()) {
            if ($user->id !== $entity->id) {
                return false; // change this logic

ref: how to 

Represent NSData in String in compact way supports local database, use case is to save file locally first then later upload to server when internet is available.

Offline time maybe weeks.

Native solution as saving data as PFFile (s3) doesn’t work in this case.

Need to find a way to save NSData in String then save as a regular parse db column.

There is a size limit of 128kb for each column.

Good news is my files aren’t too large, one docx is only 86kb.

The problem is if I use NSData.hexstring representation, (same as data.description), the string representation size is quite big, up to 176kb, almost doubled from original 86kb.

Found 2 github lib, ( and (, compress ratio aren’t good enough.

original file size: 86069
original string representation size: 172152
size compressedDataLZ4: 163114
size compressedDataLZFSE: 163538
size compressedDataLZMA: 162296
size compressedDataZLIB: 161740

compress size using gzip: 161712

Must be a better way to represent nsdata in string.

For now, this works. At least the docx file can be open after conversion.

let str = NSString(data: self.docData!, encoding: NSWindowsCP1254StringEncoding)
print("original size: \((str as! String).characters.count)");

let data = str?.dataUsingEncoding(NSWindowsCP1254StringEncoding, allowLossyConversion:false )

let tmpPath = NSString(string:"~/tmp/")  .stringByExpandingTildeInPath

let result = data?.writeToFile(tmpPath+"/1.docx", atomically: true)

In fact the base64encoding for NSData is pretty small already. no need to waste time to compress by yourself.

Learning RxSwift – rewrite Variable

Code example of how to implement prepend and append newer/older items to an existing(already bounded) list.

import Foundation
import RxSwift

func fetchEarlierItemsThan(latestItem: Int?) -> Observable<[Int]> {
    if let latestItem = latestItem {
        return just([latestItem - 1])
    return just([0])

func fetchLaterItemsThan(latestItem: Int?) -> Observable<[Int]> {
    if let latestItem = latestItem {
        return just([latestItem + 1])
    return just([0])

let prependStream = PublishSubject<Void>()

let appendStream = PublishSubject<Void>()

let currentPostList = Variable([Int]())

let prependItems = prependStream
    .withLatestFrom(currentPostList.asObservable()) { _, prependItems -> Observable<[Int]> in
        return fetchEarlierItemsThan(prependItems.first)
            .map { newItemsToPrepend in newItemsToPrepend + prependItems }
    .subscribeNext {
        currentPostList.value = $0

let appendItems = appendStream
    .withLatestFrom(currentPostList.asObservable()) { _, appendItems -> Observable<[Int]> in
        return fetchLaterItemsThan(appendItems.last)
            .map { newItemsToAppend in appendItems + newItemsToAppend }
    .subscribeNext {
        currentPostList.value = $0

currentPostList.subscribeNext {


playground output:
2016-01-08 11:46:23.031 Introduction[60454:5285520] []
2016-01-08 11:46:23.035 Introduction[60454:5285520] [0]
2016-01-08 11:46:23.039 Introduction[60454:5285520] [-1, 0]
2016-01-08 11:46:23.042 Introduction[60454:5285520] [-1, 0, 1]
2016-01-08 11:46:23.045 Introduction[60454:5285520] [-2, -1, 0, 1]
2016-01-08 11:46:23.049 Introduction[60454:5285520] [-3, -2, -1, 0, 1]
2016-01-08 11:46:23.052 Introduction[60454:5285520] [-4, -3, -2, -1, 0, 1]

SignIn with GooglePlus in iOS App without flipping between Safari

If your app has been using Google+ login for years, and suddenly the recent update got rejected with message like this:

Specifically, the app opens a web page in mobile Safari for Google+ authentication, then returns the user to the app. The user should be able to sign in to Google+ without opening Safari.

It’s time to upgrade google sign-in SDK, here is the online guide:

Unfortunately, there are some confusion in that guide, hope the following tips can help you:


  1. Download Google Sign-in SDK (2.2.0 as is now), add GoogleSignIn.bundle and GoogleSignIn.framework  to project. I am using manually instead of coacapods. (Recently pod caused enough problems to my other projects.)
  2. Get a Google Sign-In configuration file, from which you will find client-id, revised client-id which are needed later. This file itself is not in use.
  3. Add 2 more entry to project target info.plist file, I left facebook setup here so we can compare
  4. Code implementation, following online guide:
    Note: You might encounter 2 difficulties here:

    a) Can’t add GIDSignInButton,
    Workaround: create your own IBAction method:

    - (IBAction)signInWithGooglePlus:(id)sender
    [[GIDSignIn sharedInstance] signIn];

    b) Runtime Error:

    When |allowsSignInWithWebView| is enabled, uiDelegate must either be a |UIViewController| or implement the |signIn:presentViewController:| and |signIn:dismissViewController:| methods from |GIDSignInUIDelegate|

    Solution: assign uiDelegate.

    [GIDSignIn sharedInstance].clientID = kClientID;
    [GIDSignIn sharedInstance].delegate = self;
    [GIDSignIn sharedInstance].uiDelegate = self;
    //the following line is optional, default value is YES anyway
    [GIDSignIn sharedInstance].allowsSignInWithWebView = YES; 

Observation: application:openURL: method is not called, as described as step 3 in doc:
The main purpose of using new SDK is to remove flipping between Safari, but if you do what to use flipping Safari to login google+, set allowsSignInWithWebView to NO, wala! you are back to old times, and get ready to see your app being rejected by Apple.

How to draw a wavy line in iOS app?

My client reached me asking me to add a new line type to the existing iOS app I wrote to them:

I did some research, drawing a curve line is easy, like this post pointed out, all I need is just code like this:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextMoveToPoint(context, 100, 100);

Now the challenge is, how to calculate the waypoint on the fly when user touched the screen? Time to polish my high school Math skills….

Here is my solution:

wavy line


  1. Get start and end points (x1,y1) and (x2, y2)
  2. Calculating  one of the quart point. (x3, y3)
  3. those 2 angles alpha are the same, and alpha = atan((x2-x1)/(y1-y2))
  4. the waypoint (x4,y4) is possible to get now, x4 = x3 – sin(alpha) * wave_height, y4 = y4 – cos(alpha) * wave_height.

Here is the end result:

Next Step:

As video shows, when moving speed is not steady, the wavy line looks funny, that’s because I always start from negative sin first, to make it transiting smoother I think I should record the last stopped calculation for (x4,y4), so the continuing calculation will just do the other way. Anyway, this is not an issue when moving speed is steady.

Useful marcos for iOS dev

#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

e.g.: view.backgroundColor = UIColorFromRGB(0xF4F4F4);

Remote logging in iOS app

Testflight used to have this Remote Logging feature, even it’s not very stable, log occasionally failed to record, but it’s still better than nothing.

Unfortunately, Apple didn’t keep it after bought TestFlight.

I have played a while BugFender, very great service!

Free registration, download SDK.

Add this line somewhere in marco of your app:

#define NSLog(...) BFLog(__VA_ARGS__)

All existing NSLog will redirect to Bugfender!

Auto increase build number in XCode

Apple demand the build number on each app version must be higher than previous one. I found this script to auto increase build number based on git log history entry number, and have been using it for quite a long time, very handy.

#Update build number with number of git commits if in release mode
if[ ${CONFIGURATION} == "Release"] || [ ${CONFIGURATION} == "AdHoc"]; then
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber""${PROJECT_DIR}/${INFOPLIST_FILE}"

the only ting to remember is always do a push before build.

There is another way to increase build number by creating a local cfg file to save this number and increase by perl script. Found on hockeyapp.

Watch out the MapKitView

Found a location service problem in one of the app I’ve built for my client.

The problem, even after calling locationManager stopUpdatingLocation, the location service indicator still shows app is using location service!

Debug around, then found out it might be caused by the MapkitView in the details view. That details view has been already pop out from the navigation stack, the some how the MapKitView is still stay in memory sucking the location service!

Using Instrument tool to debug this, it proofs that even after the details view dismissed, the process list still shows the MapKit and geo services.

Great! Now try to do the clean up.

Tried self.mapView.delegate = nil; and self.mapView = nil; in viewWillDisapper, didn’t help.

Finally the trick is to call [self.mapView removeFromSuperView];

Some other posts pointed out the good practice is to create MapKitView on demand instead of dragging to view. Same solution.

Here is the proper code to clean up mapview:

[self.mapView removeFromSuperView]; //very important to completely clean up memory used by location service 
self.mapView.delegate = nil;
self.mapView = nil;

Why do we need $scope while ‘this’ just simply work?

Update: It turns out that ‘as’ syntax is the new style since v1.1.5, and it suppose take over all the old style $scope syntax. The nice outcome is that you will never leave a field name or method name in view without any scope prefix which is caused by $scope syntax. Way to go, ‘as’ as ‘this’! It also indicates that AngularJS changes so fast and so many training materials quickly become obsolete even they are old half year old.

Learning AngularJS, still can’t feel the need of using $scope.

The training video on codeschool doesn’t cover $scope concept, just simple use this.

When I came into the code example in controller section on guide, I rewrite the $scope style example using this style, it still works:

(function(angular) {
  'use strict';
var myApp = angular.module('spicyApp1', []);

myApp.controller('SpicyController', ['$scope', function($scope) {
    $scope.spice = 'very';

    $scope.chiliSpicy = function() {
        $scope.spice = 'chili';

    $scope.jalapenoSpicy = function() {
        $scope.spice = 'jalapeño';

myApp.controller('Spicy2Controller',   function() {
    this.spice = 'very';

    this.chiliSpicy = function() {
        this.spice = 'chili';

    this.jalapenoSpicy = function() {
        this.spice = 'jalapeño';


&lt;div ng-controller=&quot;SpicyController&quot;&gt;
 &lt;button ng-click=&quot;chiliSpicy()&quot;&gt;Chili&lt;/button&gt;
 &lt;button ng-click=&quot;jalapenoSpicy()&quot;&gt;Jalapeño&lt;/button&gt;
 &lt;p&gt;The food is {{spice}} spicy!&lt;/p&gt;

&lt;div ng-controller=&quot;Spicy2Controller as spicy2&quot;&gt;
 &lt;button ng-click=&quot;spicy2.chiliSpicy()&quot;&gt;Chili&lt;/button&gt;
 &lt;button ng-click=&quot;spicy2.jalapenoSpicy()&quot;&gt;Jalapeño&lt;/button&gt;
 &lt;p&gt;The food is {{spicy2.spice}} spicy!&lt;/p&gt;

I remember a post on stackoverfow talking about the difference of $scope and this, something about page loading, basically function defined on $scope won’t trigger on page load, so far I haven’t got any story to support this yet.

Will revisit this post after I understand more.