Native FTW: Integrating native views in Titanium apps
- 6. TICONFEU,AMSTERDAM,29/06/2014
Not in This Talk
• Basics of native module development
★ module creation
★ proxies, methods, properties, events,
callbacks
• Check out http://www.slideshare.net/
omorandi/ticonf
6
- 7. TICONFEU,AMSTERDAM,29/06/2014
Why Native Views
• UX/Performance
★ Stock Ti UI components not suitable for
the specific UX requirements
• Integration of native UI components and
libraries
★ Leverage existing solutions from the
Android and iOS OSS communities
★ Integrate third party SDKs
7
- 10. TICONFEU,AMSTERDAM,29/06/2014
Official Appcelerator Guides
• http://docs.appcelerator.com/titanium/latest/#!/guide/
Extending_Titanium_Mobile
• http://docs.appcelerator.com/titanium/latest/#!/guide/
iOS_Module_Development_Guide
• http://docs.appcelerator.com/titanium/latest/#!/guide/
Android_Module_Development_Guide
10
- 13. TICONFEU,AMSTERDAM,29/06/2014
Follow these people (and more)
• Aaron K. Saunders: https://github.com/aaronksaunders
• Adam Paxton: https://github.com/adampax/
• Ben Bahrenburg: https://github.com/benbahrenburg
• David Bankier: https://github.com/dbankier
• Jordi Domenec: https://github.com/iamyellow
• Mads Møller: https://github.com/viezel
• Marcel Pociot: https://github.com/mpociot
• Matt Apperson: https://github.com/mattapperson
• Paul Mietz Egli: https://github.com/pegli
• Ruben Fonseca: https://github.com/rubenfonseca
• Many more… find them on http://gitt.io/
13
- 21. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
- 22. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
module
object
- 23. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
factory
method
- 24. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
creation
properties
- 25. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
proxy
object
- 26. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
view proxy
object
- 27. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
proxy
property
- 28. TICONFEU,AMSTERDAM,29/06/2014
Terminology
18
var win1 = Titanium.UI.createWindow({
title:'Hello World',
backgroundColor:'white'
});
!
var label1 = Titanium.UI.createLabel({
color:'black',
textAlign:'center',
width: 100
});
!
label1.text = 'howdy?';
win1.add(label1);
!
win1.open();
proxy
method
- 31. TICONFEU,AMSTERDAM,29/06/2014
Proxies & Modules
Proxy
ViewProxy ViewModule
extends
has a
creates
19
manages
NativeView Type
iOS UIView
AndroidView
State:
properties
Actions:
methods
Events:
addEventListener(), fireEvent()
Interface
Methods for the integration within the
application lifecycle
•startup() (iOS)
•shutdown() (iOS)
•onAppCreate() (Android)
extends
- 32. TICONFEU,AMSTERDAM,29/06/2014
Proxies & Modules
Proxy
ViewProxy ViewModule
extends
has a
creates
19
manages
NativeView Type
iOS UIView
AndroidView
State:
properties
Actions:
methods
Events:
addEventListener(), fireEvent()
Interface
Additional members for the integration
within the UI layout system:
•add()
•remove()
•height
•width
•backgroundColor
•...
Methods for the integration within the
application lifecycle
•startup() (iOS)
•shutdown() (iOS)
•onAppCreate() (Android)
extends
- 41. TICONFEU,AMSTERDAM,29/06/2014
View Class
25
#import "TiUIView.h"
!
@interface TiConfBasicView : TiUIView
{
UIView *theView;
}
@end
TiConfBasicView.h
TiConfBasicView.m
#import "TiConfBasicView.h"
!
@implementation TiConfBasicView
!
-(void)initializeState
{
theView = [[UIView alloc] initWithFrame:self.bounds];
theView.backgroundColor = [UIColor redColor];
[self addSubview:theView];
}
!
@end
- 46. TICONFEU,AMSTERDAM,29/06/2014
Managing the subview frame
27
TiConfBasicView.m
#import "TiConfBasicView.h"
!
@implementation TiConfBasicView
!
!
-(void)initializeState
{
theView = [[UIView alloc] initWithFrame:self.bounds];
theView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
theView.backgroundColor = [UIColor redColor];
[self addSubview:theView];
}
!
@end
- 47. TICONFEU,AMSTERDAM,29/06/2014
Managing the subview frame
27
TiConfBasicView.m
#import "TiConfBasicView.h"
!
@implementation TiConfBasicView
!
!
-(void)initializeState
{
theView = [[UIView alloc] initWithFrame:self.bounds];
theView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
theView.backgroundColor = [UIColor redColor];
[self addSubview:theView];
}
!
@end
The Ti View frame is managed by the Ti
layout system.
Bounds are not valid until the view takes
part to an on-screen Ti view hierarchy
- 48. TICONFEU,AMSTERDAM,29/06/2014
Managing the subview frame
27
TiConfBasicView.m
#import "TiConfBasicView.h"
!
@implementation TiConfBasicView
!
!
-(void)initializeState
{
theView = [[UIView alloc] initWithFrame:self.bounds];
theView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
theView.backgroundColor = [UIColor redColor];
[self addSubview:theView];
}
!
@end
The Ti View frame is managed by the Ti
layout system.
Bounds are not valid until the view takes
part to an on-screen Ti view hierarchy
- 52. TICONFEU,AMSTERDAM,29/06/2014
View Class
30
public class BasicView extends TiUIView
{
View theView;
public BasicView(TiViewProxy proxy) {
super(proxy);
!
Activity context = proxy.getActivity();
theView = new View(context);
theView.setBackgroundColor(Color.RED);
getLayoutParams().autoFillsHeight = true;
getLayoutParams().autoFillsWidth = true;
setNativeView(theView);
}
!
}
BasicView.java
- 57. TICONFEU,AMSTERDAM,29/06/2014
Explicit Setter in the ViewProxy
35
-(void)setColor:(UIColor*)color
{
theView.backgroundColor = color;
}
TiConfBasicView.m
-(void)setColor:(id)args
{
//expect 1 argument
ENSURE_ARG_COUNT(args, 1);
UIColor *color = [[TiUtils colorValue:args] color];
//dispatch to the view on UI thread
//(create it if needed, don't wait for completion)
[self makeViewPerformSelector:@selector(setColor:)
withObject:color createIfNeeded:YES waitUntilDone:NO];
}
TiConfBasicViewProxy.m
- 58. TICONFEU,AMSTERDAM,29/06/2014
Explicit Setter in the ViewProxy
35
-(void)setColor:(UIColor*)color
{
theView.backgroundColor = color;
}
TiConfBasicView.m
-(void)setColor:(id)args
{
//expect 1 argument
ENSURE_ARG_COUNT(args, 1);
UIColor *color = [[TiUtils colorValue:args] color];
//dispatch to the view on UI thread
//(create it if needed, don't wait for completion)
[self makeViewPerformSelector:@selector(setColor:)
withObject:color createIfNeeded:YES waitUntilDone:NO];
}
TiConfBasicViewProxy.m
JS
THREAD
- 59. TICONFEU,AMSTERDAM,29/06/2014
Explicit Setter in the ViewProxy
35
-(void)setColor:(UIColor*)color
{
theView.backgroundColor = color;
}
TiConfBasicView.m
-(void)setColor:(id)args
{
//expect 1 argument
ENSURE_ARG_COUNT(args, 1);
UIColor *color = [[TiUtils colorValue:args] color];
//dispatch to the view on UI thread
//(create it if needed, don't wait for completion)
[self makeViewPerformSelector:@selector(setColor:)
withObject:color createIfNeeded:YES waitUntilDone:NO];
}
TiConfBasicViewProxy.m
JS
THREAD
UI
THREAD
- 64. TICONFEU,AMSTERDAM,29/06/2014
Property Changed Listener
39
BasicView.java
@Override
public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy)
{
if (key.equals("color")) {
theView.setBackgroundColor(TiConvert.toColor((String)newValue));
} else if () {
//do something else
}
}
public abstract class TiUIView implements KrollProxyListener
titanium_mobile/android/titanium/src/java/org/appcelerator/titanium/view/TiUIView.java:73
- 72. TICONFEU,AMSTERDAM,29/06/2014
View Method Dispatching
45
public void makeRounded() {
GradientDrawable shape = new GradientDrawable();
shape.setColor(Color.RED);
shape.setCornerRadius(50);
theView.setBackgroundDrawable(shape);
}
BasicView.java
@Kroll.method
public void makeRounded() {
TiMessenger.postOnMain(new Runnable() {
!
@Override
public void run() {
BasicView view = (BasicView)getOrCreateView();
view.makeRounded();
}
});
}
BasicViewProxy.java
- 73. TICONFEU,AMSTERDAM,29/06/2014
View Method Dispatching
45
public void makeRounded() {
GradientDrawable shape = new GradientDrawable();
shape.setColor(Color.RED);
shape.setCornerRadius(50);
theView.setBackgroundDrawable(shape);
}
BasicView.java
@Kroll.method
public void makeRounded() {
TiMessenger.postOnMain(new Runnable() {
!
@Override
public void run() {
BasicView view = (BasicView)getOrCreateView();
view.makeRounded();
}
});
}
BasicViewProxy.java
JS
THREAD
- 74. TICONFEU,AMSTERDAM,29/06/2014
View Method Dispatching
45
public void makeRounded() {
GradientDrawable shape = new GradientDrawable();
shape.setColor(Color.RED);
shape.setCornerRadius(50);
theView.setBackgroundDrawable(shape);
}
BasicView.java
@Kroll.method
public void makeRounded() {
TiMessenger.postOnMain(new Runnable() {
!
@Override
public void run() {
BasicView view = (BasicView)getOrCreateView();
view.makeRounded();
}
});
}
BasicViewProxy.java
JS
THREAD
UI
THREAD
- 75. TICONFEU,AMSTERDAM,29/06/2014
Message Dispatching
46
private static final int MSG_MAKE_ROUNDED = TiViewProxy.MSG_LAST_ID + 4001;
protected static final int MSG_LAST_ID = MSG_MAKE_ROUNDED;
!
private void handleMakeRounded() {
BasicView view = (BasicView)getOrCreateView();
view.makeRounded();
}
@Override
public boolean handleMessage(Message msg){
if (msg.what == MSG_MAKE_ROUNDED) {
handleMakeRounded();
return true;
}
return super.handleMessage(msg);
}
@Kroll.method
public void makeRounded() {
if (TiApplication.isUIThread()) {
handleMakeRounded();
} else {
TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_MAKE_ROUNDED));
}
}
BasicViewProxy.java
- 77. TICONFEU,AMSTERDAM,29/06/2014
Firing Events
48
-(void)initializeState
{
theView = [[UIView alloc] initWithFrame:self.bounds];
// other initialisation operations
!
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(basicViewTapped:)];
// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;
// Add the tap gesture recognizer to the view
[theView addGestureRecognizer:tapRecognizer];
}
!
- (void)basicViewTapped:(UITapGestureRecognizer *)recognizer {
NSDictionary *event = @{@"color": @"red"};
[self.proxy fireEvent:@"viewTapped" withObject:event];
}
TiConfBasicView.m
- 78. TICONFEU,AMSTERDAM,29/06/2014
Firing Events
48
-(void)initializeState
{
theView = [[UIView alloc] initWithFrame:self.bounds];
// other initialisation operations
!
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(basicViewTapped:)];
// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;
// Add the tap gesture recognizer to the view
[theView addGestureRecognizer:tapRecognizer];
}
!
- (void)basicViewTapped:(UITapGestureRecognizer *)recognizer {
NSDictionary *event = @{@"color": @"red"};
[self.proxy fireEvent:@"viewTapped" withObject:event];
}
TiConfBasicView.m
- 79. TICONFEU,AMSTERDAM,29/06/2014
Firing Events
49
public BasicView(final TiViewProxy proxy) {
super(proxy);
!
Activity context = proxy.getActivity();
theView = new View(context);
!
//other initialisation operations
View.OnTouchListener gestureListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent e) {
KrollDict event = new KrollDict();
event.put("color", "red");
proxy.fireEvent("viewTapped", event);
return true;
}
};
theView.setOnTouchListener(gestureListener);
!
setNativeView(theView);
}
BasicView.java
- 80. TICONFEU,AMSTERDAM,29/06/2014
Firing Events
49
public BasicView(final TiViewProxy proxy) {
super(proxy);
!
Activity context = proxy.getActivity();
theView = new View(context);
!
//other initialisation operations
View.OnTouchListener gestureListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent e) {
KrollDict event = new KrollDict();
event.put("color", "red");
proxy.fireEvent("viewTapped", event);
return true;
}
};
theView.setOnTouchListener(gestureListener);
!
setNativeView(theView);
}
BasicView.java
- 88. TICONFEU,AMSTERDAM,29/06/2014
iOS Frameworks
54
TITANIUM_SDK_VERSION = 3.2.3.GA
!
!
TITANIUM_SDK = ~/Library/Application Support/Titanium/mobilesdk/osx/$(TITANIUM_SDK_VERSION)
TITANIUM_BASE_SDK = "$(TITANIUM_SDK)/iphone/include"
TITANIUM_BASE_SDK2 = "$(TITANIUM_SDK)/iphone/include/TiCore"
HEADER_SEARCH_PATHS= $(TITANIUM_BASE_SDK) $(TITANIUM_BASE_SDK2) /PATH/TO/YOUR/FRAMEWORK/HEADERS
titanium.xcconfig
- 89. TICONFEU,AMSTERDAM,29/06/2014
iOS Frameworks
54
TITANIUM_SDK_VERSION = 3.2.3.GA
!
!
TITANIUM_SDK = ~/Library/Application Support/Titanium/mobilesdk/osx/$(TITANIUM_SDK_VERSION)
TITANIUM_BASE_SDK = "$(TITANIUM_SDK)/iphone/include"
TITANIUM_BASE_SDK2 = "$(TITANIUM_SDK)/iphone/include/TiCore"
HEADER_SEARCH_PATHS= $(TITANIUM_BASE_SDK) $(TITANIUM_BASE_SDK2) /PATH/TO/YOUR/FRAMEWORK/HEADERS
titanium.xcconfig
- 90. TICONFEU,AMSTERDAM,29/06/2014
iOS Frameworks
54
TITANIUM_SDK_VERSION = 3.2.3.GA
!
!
TITANIUM_SDK = ~/Library/Application Support/Titanium/mobilesdk/osx/$(TITANIUM_SDK_VERSION)
TITANIUM_BASE_SDK = "$(TITANIUM_SDK)/iphone/include"
TITANIUM_BASE_SDK2 = "$(TITANIUM_SDK)/iphone/include/TiCore"
HEADER_SEARCH_PATHS= $(TITANIUM_BASE_SDK) $(TITANIUM_BASE_SDK2) /PATH/TO/YOUR/FRAMEWORK/HEADERS
titanium.xcconfig
Used ad module build time
- 93. TICONFEU,AMSTERDAM,29/06/2014
ARC vs. NON-ARC
• ARC = Automatic Reference Counting
★ Retain/Release handled automatically by
the Clang compiler
• Ti module template project is still NON-
ARC
• You can use ARC code inside of a Ti
module project
56
- 98. TICONFEU,AMSTERDAM,29/06/2014
Fix build.xml
61
<project name="android" default="dist">
<description>
Ant build script for Titanium Android module android
</description>
!
<property name="ti.module.root" location="${basedir}"/>
<property file="build.properties" />
!
<import file="${titanium.platform}/../module/android/build.xml"/>
<target name="post.jar">
<copy todir="${libs}">
<fileset dir="lib">
<include name="**/*.so"/>
</fileset>
</copy>
</target>
</project>
build.xml
Copy the .so files from lib to
libs before packaging
Paul Mietz Egli: http://developer.appcelerator.com/question/121573/how-do-i-use-so-library-in-module#answer-228134
- 99. TICONFEU,AMSTERDAM,29/06/2014
Fix build.xml
61
<project name="android" default="dist">
<description>
Ant build script for Titanium Android module android
</description>
!
<property name="ti.module.root" location="${basedir}"/>
<property file="build.properties" />
!
<import file="${titanium.platform}/../module/android/build.xml"/>
<target name="post.jar">
<copy todir="${libs}">
<fileset dir="lib">
<include name="**/*.so"/>
</fileset>
</copy>
</target>
</project>
build.xml
Copy the .so files from lib to
libs before packaging
Paul Mietz Egli: http://developer.appcelerator.com/question/121573/how-do-i-use-so-library-in-module#answer-228134