Noodlecode parody of spaghetti code

Tag: ios (page 1 of 2)

Creating [object] in a different context than the calling function.

Creating [object] in a different context than the calling function.

This warning occured when a JS callback function is passed to Objective-C as KrollCallback and then executed on non-JS thread.

KrollCallback *callback = [args objectForKey@"callback"];

[OneSignal initWithLaunchOptions:[TiApp app].launchOptions appId:appId handleNotificationAction:^(OSNotificationResult *result) {
    [callback call:@[] thisObject:nil];
}];

Problem with this warning is, if the JS callback create Ti.Network.HTTPClient instance, then that instance is null.

function callback() {
    var http = Ti.Network.createHTTPClient();
    http.open('GET', getUrl()); // JS error! http is null
    http.send();
}

To fix, get krollContext instance of current module/proxy and execute callback inside invokeBlockOnThread

// For TiModule / TiProxy descendants
KrollContext *context = [self.executionContext krollContext];

// For TiUIView descendants
KrollContext *context = [self.proxy.executionContext krollContext];

// invoke
[context invokeBlockOnThread:^{
    [callback call:@[] thisObject:nil];
}];

Ti.Network.registerForPushNotifications no response

SDK: 3.5.1.GA, CLI: 4.0.1

Calling Ti.Network.registerForPushNotifications() has no response, whether success, error or callback, basically no response at all. Turned out that the build script can’t detect the method and add USE_TI_NETWORKREGISTERFORPUSHNOTIFICATIONS to defines.h during app compilation.

I created with a plugin that append the macro definition to *.pch file, here it is:

Create plugin folder:

cd $PROJECT_DIR
mkdir -p plugins/ti.pushnotifsymbol/hooks
touch plugins/ti.pushnotifsymbol/package.json
touch plugins/ti.pushnotifsymbol/hooks/pushnotifsymbol.js

pushnotifsymbol.js:

exports.id = 'ti.pushnotifsymbol';
exports.cliVersion = '>=3.2';
exports.init = function (logger, config, cli) {
    var path = require('path');
    var fs = require('fs');
    var util = require('util');
    var os = require('os');
    var pchFile;
    var triggered = false;
    cli.on('build.pre.compile', function(builder, next) {
        pchFile = path.join(builder.buildDir, builder.tiapp.name + '_Prefix.pch');
        next();
    });
    cli.on('build.ios.copyResource', function(builder, next) {
        if (!triggered) {
            triggered = true;
            if (fs.existsSync(pchFile)) {
                fs.appendFileSync(pchFile, os.EOL + '#define USE_TI_NETWORKREGISTERFORPUSHNOTIFICATIONS');
            }
        }
        next();
    });
};

Add <plugin>ti.pushnotifsymbol</plugin> to <plugins> section of your project tiapp.xml

Titanium iOS builder script

Location: TITANIUM_SDK/mobilesdk/osx/3.5.1.GA/iphone/cli/commands/_build.js
(TITANIUM_SDK is commonly at ~/Library/Application Support/Titanium)

1. Cannot change CFBundleVersion

  • CFBundleVersion is the build version number, available to developer and beta testers.
  • CFBundleShortVersionString is the release version number, what the users see on App Store
  • CFBundleVersion is automatically assign to value of <version> in tiapp.xml, but for beta testing purposes, need to change this value everytime we upload new build to iTunes Connect TestFlight
  • To fix this, modify iOS builder script in iOSBuilder.prototype.createInfoPlist() function, around line 2063
plist.CFBundleVersion = String(this.tiapp['build-version'] || this.tiapp.version);

2. If your Titanium SDK location is different from default (custom SDK path), the script doesn’t look up that folder when searching for modules

  • My Titanium SDK is located at ~/Applications/opt/Titanium, when running the ti build -p ios command, it doesn’t lookup for modules there
  • If my project included ti.cloud module, the build script will fail, cannot find the specified module
  • To fix this, in iOSBuilder.prototype.validate() function
// around line 1321, add:
customSDKPaths = config.get('paths.sdks'),

// around line 1331, add:
Array.isArray(customSDKPaths) && customSDKPaths.forEach(addSearchPath);

Titanium app hex color value with alpha channel

Color values are represented in hexadecimal value:

#000000

  • 0 – red, 0 – green, 0 – blue
  • color = black

#ffffff

  • 255 – red, 255 – green, 255 – blue
  • color = white

‘ff’ in hexadecimal is 255 in decimal, (2 ^ 8) – 1 = 255.

In CSS, to represent color with alpha channel, we can use the rgba() syntax:

rgba(0, 0, 0, 0.6)

  • 0 – red, 0 – green, 0 – blue
  • black with 60% opacity

In Titanium, rgba() syntax only available on iOS, but hex value also can be used to represent alpha channel, and it supported by both iOS & Android

#ff000000

  • 0 – red, 0 – green, 0 – blue
  • ff – 255 / 100% opacity

#90ffcc00

  • 255 – red, 204 – green, 0 – blue
  • 90 – 144 / 56% opacity

To easily maintain colors in app, this function is to transform rgba value into Titanium hex color format

function rgbaToHex(r, g, b, a) {
    var toHex = function(n) {
        return ('00' + (n | 0).toString(16)).slice(-2);
    };
    return '#' + toHex(((a * 100) / 100) * 255) + toHex(r) + toHex(g) + toHex (b);
}

Explanation:

  • (n | 0) is shortcut for parseInt(), to ensure the value passed in is integer. We don’t want the hex color value to have decimal point, e.g: #2.4ccc
  • .toString(16) is Number object function (not Object.toString()) to convert number into hexadecimal format
  • ('00' + value).slice(-2) is to add string padding to the left of the string, so that it will always have 2 characters. We don’t want the value to have one character, #0ccff is invalid value

Usage:

var view = Ti.UI.createView({
    backgroundColor: rgbaToHex(255, 204, 0, 0.5)
});

iOS simulator folder

Up until iOS 7
~/Library/Application Support/iPhone Simulator/<ios version>

New in iOS 8
~/Library/Developer/CoreSimulator/Devices/<simulator id>

Add custom framework to Titanium iOS module

For example, I’m creating a module com.mymod, which include MillennialMedia SDK, that has 2 custom frameworks:

  • MillennialMedia.framework
  • SpeechKit.framework

Copy the frameworks into <module folder>/platform/iphone/custom_frameworks

Open the module in Xcode & include the framework – you can choose to include into any folder, for example the root of project

Edit module.xcconfig. Choose a unique variable name to set the environment variables (e.g MYMOD)

MYMOD_ID=com.mymod
MYMOD_VER=1.0
MYMOD_DIR=$(SRCROOT)/../../modules/iphone/$(COM_MYMOD_MYMOD_ID)/$(COM_MYMOD_MYMOD_VER)/platform/iphone

OTHER_LDFLAGS=$(inherited) -F"$(COM_MYMOD_MYMOD_DIR)/custom_frameworks" -framework MillennialMedia -F SpeechKit

Notice that we’re not using the defined env vars. This is because Titanium build script rename the var based on the module id (com.mymod becomes COM_MYMOD). If we’re referring to the var name that we define, it produces distorted value, example, MYMOD_VER become ” 1.0 1.0″

Reference: https://developer.appcelerator.com/question/132459/module-dev-third-party-framework

Load nib file in Titanium module iOS

When creating Titanium module for iOS platform, sometimes we need to include xib (NeXT Interface Builder) file. But if we include the file just like that, then we will face this error

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </Users/username/Library/Application Support/iPhone Simulator/6.1/Applications/<app id>/myiosmodule.app> (loaded)' with name 'MyView''

To fix, we need to include a compiled xib file known as nib file. To compile it, you need to include it in a native iOS project & build it.

Update: Easier way to compile xib to nib file is using ibtool

$ ibtool MPAdBrowserController.xib --compile MPAdBrowserController.nib

Compile xib to nib file

For example here, I’m create mopub module for iOS

  1. Open Xcode, create new project, and choose ‘Single View Application’
    new-project-option
  2. Add xib file to the project
    add-file
    select-xib-file
  3. Make sure it is included in the ‘Copy Bundle Resource’ list, then click ‘Run’ button to launch the sample app in simulator
    copy-bundle-run
  4. After it runs on simulator, using Finder go to /Users/username/Library/Application Support/iPhone Simulator/6.1/Applications/<app id>/test.app, then see package content
    open-simulator-folder-2
    show-package-content
  5. You’ll find the nib file there. Put this nib file into /assets/ folder of TItanium module. Then you need to edit the path to load nib file in initWithNibName method.
    edit-code-init

In case you can’t edit the source Objective-C file, you have to put the nib file into /Resources/iphone/ folder of the app project

Titanium iOS “build” is an unrecognized command

Was trying to build an iOS app using sdk 3.1.2 but got this error.

[ERROR] "build" is an unrecognized command

When running titanium build command on the project folder, I realize that only Titanium CLI was updated to version 3.1.2 while its SDK still in 3.1.1

Updating the SDK solve this problem, by running this command

titanium sdk install 3.1.2.GA

Titanium ListView

Concept behind new Titanium ListView is similar to ModelViewViewModel (MVVM) pattern, where you have a set of data and you will map them to a view template

Let’s say you have this kind of data structure (model)

Person
- id {Number}
- name {String}
- photo {String} - URL
- message {String}

And you have an array of Person objects

var dataModels = [
  {
    id: 1,
    name: 'John',
    photo: 'http://example.com/img/john.jpg',
    message: 'Thank you'
  },
  {
    id: 2,
    name: 'Mary',
    photo: 'http://example.com/img/mary.jpg',
    message: 'Goodbye'
  },
  {
    id: 3,
    name: 'Chris',
    photo: 'http://example.com/img/chris.jpg',
    message: 'Hey there!'
  },
];

Now we want to map this array to a ListView, each Person object will use an ItemTemplate to produce a ListItem. First, define an ItemTemplate

var personTemplate = {
  childTemplates: [
    {
      type: 'Ti.UI.ImageView',
      bindId: 'photo',
      properties: {
        left: 5,
        top: 5,
        width: 50,
        height: Ti.UI.SIZE
      }
    },
    {
      type: 'Ti.UI.Label',
      bindId: 'name',
      properties: {
        left: 60,
        top: 5,
        height: 40,
        font: { fontSize: '18dp' },
        color: '#fff',
        wordWrap: false,
        ellipsize: true
      }
    },
    {
      type: 'Ti.UI.Label',
      bindId: 'message',
      properties: {
        left: 60,
        top: 45,
        font: { fontSize: '14dp' },
        color: '#eee'
      }
    }
  ]
};

A few things to note here. ItemTemplate defines views inside a ListItem, different from usual way we use add() method to add a view to another view. To compare how to define it with TableViewRow

var person = dataModels[i];
var row = Ti.UI.createTableViewRow({
  // to determine which row is being clicked
  itemId: person.id
});
row.add(Ti.UI.createImageView({
  image: person.photo,
  left: 5,
  top: 5,
  width: 50,
  height: Ti.UI.SIZE
}));
row.add(Ti.UI.createLabel({
  text: person.name,
  left: 60,
  top: 5,
  height: 40,
  font: { fontSize: '18dp' },
  color: '#fff',
  wordWrap: false,
  ellipsize: true
}));
row.add(Ti.UI.createLabel({
  text: person.message,
  left: 60,
  top: 45,
  font: { fontSize: '14dp' },
  color: '#eee'
}));

In childTemplates, is list of UI elements contain inside a ListItem. The way you define the element is by type & properties. bindId is the Person property (data model), which you want the UI to bind to – getting the data out and put it into the view.

Note that in Titanium documentation, there are ItemTemplate and ViewTemplate. ItemTemplate is used to generate a ListItem, while ViewTemplate is to generate other views, which specifically defined by the type property. See how to generate views in custom module, other than those defined in Ti.UI.* namespace.

However we cannot use dataModels array above directly onto ListView, we need to restructure it so it can be compatible with the UI element it is bound to. This process is called binding, which produce an array of view model (of type ListDataItem) that will be assigned to ListViewSection items property:

var viewModels = [
  {
    properties: { itemId: 1 },
    name: { text: 'John' },
    photo: { image: 'http://example.com/img/john.jpg' },
    message: { text: 'Thank you' }
  },
  {
    properties: { itemId: 2 },
    name: { text: 'Mary' },
    photo: { image: 'http://example.com/img/mary.jpg' },
    message: { text: 'Goodbye' }
  },
  {
    properties: { itemId: 3 },
    name: { text: 'Chris' },
    photo: { image: 'http://example.com/img/chris.jpg' },
    message: { text: 'Hey there!' }
  },
];

Notice that each Person properties have been changed, and by now it’s not considered data model any, but rather a view model. text and image are properties of Ti.UI.Label and Ti.UI.ImageView respectively, which you can use to bind the value to UI in the template you define earlier.

A ListView need to explicitly declare at least one ListSection, which acts as a place where you can set & append more ListItem.

var listView = Ti.UI.createListView({
  templates: {
    personTemplate: personTemplate
  },
  defaultItemTemplate: 'personTemplate'
});
var sections = [];
var section = Ti.UI.createListSection();
sections.push(section);
listView.setSections(sections);
win.add(listView);

You can add more template if you have more layout being used in a single ListView. Now load the dataSet into the ListView.

section.setItems(viewModels);

Load more items?

section.appendItems(viewModels);

Deeper visual tree of ItemTemplate

Use childTemplates on each UI element inside the template to customize it even more.

var personTemplate = {
  childTemplates: [
    {
      type: 'Ti.UI.ImageView',
      bindId: 'photo',
      properties: {
        left: 5,
        top: 5,
        width: 50,
        height: Ti.UI.SIZE
      }
    },
    {
      type: 'Ti.UI.View',
      properties: {
        layout: 'vertical',
        left: 60,
        top: 5
      },
      childTemplates: [
        {
          type: 'Ti.UI.Label',
          bindId: 'name',
          properties: {
            left: 0,
            top: 0,
            height: 40,
            font: { fontSize: '18dp' },
            color: '#fff',
            wordWrap: false,
            ellipsize: true
          }
        },
        {
          type: 'Ti.UI.Label',
          bindId: 'message',
          properties: {
            left: 0,
            top: 5,
            font: { fontSize: '14dp' },
            color: '#eee'
          }
        }
      ]
    }
  ]
};

Referring to template above, now you have this visual tree:

ListItem
 |__ ImageView
 |__ View
      |__ Label
      |__ Label

Events

For example, to add click event on image, add events property, specify an object with event name as key & callback as value

var template = {
  childTemplates: [
    {
      type: 'Ti.UI.ImageView',
      bindId: 'photo',
      properties: {
        left: 5,
        top: 5,
        width: 50,
        height: Ti.UI.SIZE
      },
      events: {
        click: function(e) {
          Ti.API.info('photo image clicked!');
        }
      }
    },{
      ...
    }
  ]
};

You can also use itemclick event of ListView to detect if a ListItem was clicked

listView.addEventListener('itemclick', function(e) {
  Ti.API.info('a ListItem was clicked!');

  // get which row is being clicked. See itemId property in viewModels above
  var person = dataModels.reduce(function(p, v) {
    if (v.id == e.itemId) {
      return v;
    }
    return p;
  });
});

Update iOS Titanium module SDK version

If you have a custom module compiled in older SDK version (3.0.0.GA and below), here’s how to update to latest version (3.1.0.GA)

  1. Edit manifest file
    minsdk: 3.1.0.GA
  2. Edit titanium.xcconfig
    TITANIUM_SDK_VERSION = 3.1.0.GA
    TITANIUM_SDK = /Library/Application Support/ ...
    (SDK path point to directory starting from root filesystem, rather than home directory)
Older posts

Copyright © 2017 Noodlecode