selectedBackgroundColor와 backgroundSelectedColor의 차이

selectedBackgroundColor 가 table row가 선택되었을 때 표시되는 색(아이폰 기본 파란색) (tableviewro의 속성)

backgroundSelectedColor는 안드로이드와 mobileweb에서만 적용되는 속성인데… view를 누르는 동안 표시되는 색, 아이폰에는 이런 속성은 없다.

테이블에서 selectedBackgroundColor를 변경할 일이 많은데 이 두 변수 이름이 비슷해서 주의필요!

Advertisements

Titanium Studio가 안켜질 때.

Titanium Studio를 실행시 위와 같이 나오면서 안켜지는 문제가 발생하기도 한다. 지금까지는 Titanium Studio를 다시 설치하거나 workspace를 새로 생성하여 해결 했다. 한번 집념(?)을 가지고 원인이 되는 파일을 찾아보니 .snap 파일이 문제였다.

숨겨져 있는 폴더인 YOUR_WORKSPACE/.meatadata/.plugins/org.eclipse.core.resources/ 에 있는 .snap 파일을 지우고 다시 실행해 보면 된다.


Titanium에서 xhr에 HTTP Basic Authentication 사용하기

Titanium에서 createHTTPClient를 이용할 때 HTTP Authentication 사용하려면 아래와 같이 하면 된다.

var xhr = Titanium.Network.createHTTPClient();
xhr.setTimeout(20000);
xhr.onload = function()
{
Ti.API.info(this.responseText);
};
// open the client
xhr.open('GET','http://your.destination.url');

authstr = 'Basic ' +Ti.Utils.base64encode("yourUserName"+':'+"yourPassword");
xhr.setRequestHeader('Authorization', authstr);
xhr.send();

참고 URL : http://developer.appcelerator.com/question/20951/how-to-do-a-http-put-with-http-basic-authentication


Titanium에서 JSS(javascript style sheet) 사용법

Titanium에서 1.5버전부터 JSS (javascript style sheet)을 지원하기 시작했다.
jss란 무엇인가? html에 스타일을 적용할 때 CSS(Cascading Style Sheets)를 쓰는 것 처럼 javascript를 기반으로 style을 다룰 때 사용하는 방식이다.

js파일에 해당하는 jss만들기
app.js에 적용할 jss파일은 동일한 이름으로 같은 디렉토리에 생성하면 된다. (app.js 와 app.jss)

jss에 스타일 정의하기
css에서는 id, class, dom element 3가지 방법으로 스타일을 정의하고 적용할 수 있다. Titanium에의 JSS도 이와 유사하다. 다음과 같은 3가지 방법을 제공한다.

ID-based Style : #id로 형태로 선언하고 ui component 생성시 id를 지정한다.

// file : app.js
var styleWindow = Titanium.UI.createWindow({
id:'styleWin',
title:'스타일 적용 할 윈도우'
});
/* file : app.jss */
#styleWin {
background-color:#2A2623;
}

Type-based Style : component type이름으로 선언만하면 해당 component에 적용된다.

// file : app.js
var styleWindow = Titanium.UI.createWindow({
title:'스타일 적용 할 윈도우'
});
/* file : app.jss */
window { //app.js의 모든 window에 기본적으로 적용된다.
background-color:#2A2623;
}

Class-based Style :  .class 형태로 ui component 생성시 className을 지정한다.
(css의 경우 하나의 dom 엘러먼트에 여러개의 class지정하여 사용하나 jss에서는 아직 지원 안하는 모양이다.^^)

// file : app.js
var styleWindow = Titanium.UI.createWindow({
className:'style_win',
title:'스타일 적용 할 윈도우'
});
/* file : app.jss */
.style_win {
background-color:#2A2623;
}

3가지를 전부 정의 했을 때는 다음과 같은 우선 순위를 갖는다.

ID-based styles > type-based styles > class-based styles

이 순서를 확인도 할겸 예제 파일을 만들어 보았다.

p.s. 모든 js파일에 자동적용되는 공통적인 jss는 global.jss에 정의하면 된다고 나와있으나 실제 해보면 적용이 되지 않았다. 하지만 기본적인 css에서 사용하는 import로 공통적으로 사용하는 jss를 불러와서 사용할 수 있다.
따라서 공통적으로 쓸 jss의 경우 base.jss와 같이 독립된 파일을 만들고 base.jss가 필요한 jss에서 import를 시켜 사용하면 편리하다.

/* file : base.jss */
window {
barColor:#2A2623;
backgroundColor :#000;
}
/* file : app.jss */
@import url('./base.jss');
app {
background-color:#000;
}

reference
http://wiki.appcelerator.org/display/guides/Designing+the+User+Interface#DesigningtheUserInterface-CrossplatformlayoutusingJSS


Android Device로 Titanium App 넣을때 R.java Error

Titanium에서 안드로이드 앱을 설정 한다는 것은 쉬운 일이 아닌 것 같다. 설정해줘야 할 것도 생각 보다 많고 설정하다가 안되는 경우도 많았다.

어렵게 설정을 다해 성공을 했는데 나중에 다시 새로운 프로젝트를 만들고 안드로이드 폰에 넣으려고 하니 R.java 에러가 날때가 있다. Resource 안의 폴더를 지웠다가 다시 실행도 해보고 새로운 프로젝트도 생성해서 해봤지만 소용 없었다.

결국, 찾은 해답은 App ID의 문제 였다. App ID에 최소한 하나의 ‘.’ (dot) 이 중간에 들어가야 한다. 이를 지키지 않으면 다음과 같은 에러가 나더라.

[ERROR] Error generating R.java from manifest

이런 error 때문에 고생한다면 appid 를 확인해보자!

p.s. Apple의 앱 아이디의 경우 apple에서는 domain을 거꾸로 적은 형태의 id를 권한다.( com.yomybaby.c ) 하지만 ‘test’ 와 같은 일반적인 텍스트 형태를 넣어도 Titanium에서는 문제 없이 돌아간다.
App ID는 말 그대로 앱을 나타내는 고유의 ID 이며 app을 구분하는데 쓰인다. push notification을 사용하기 위해서는 *와 같은 id를 쓰지 않고 com.domain.www 형태 이어야 한다.


모바일웹 개발시 URL 입력하기 어려우시죠? Titanium과 Nodejs로 해결하기

모바일 웹을 개발하다보면 실제 폰에서 테스트를 하기위해 개발중인 URL을 모바일 기기에 일일이 입력해야하는 어려움이 있다. 조그마한 터치 키보드로 긴 url을 치려다보면 속이 터진다. (사실 나는 모바일 웹개발이 주 업무가 아니어서 속이 터지는걸 자주 경험해보진 않았다.^^ 주변에서 개발하시는 분들이 이 일을 상당히 귀찮아 한다.)

이 귀찮음을 해결할 만한 어플을 Titanium과 Nodejs를 이용하면 뚝딱(?!) 만들어 보기로 했다. 컨샙은 이렇다.
내가 데스크탑 브라우저에서 보고 있는 웹페이지의 URL을 자동으로 모바일 기기로 보내서 모바일 기기에서도 똑같은 웹페이지가 보이도록 하는 것이다. (스마트폰에 키 입력을 하지 않아도 되도록 말이다!)
만들어 봤고 실제 작동하는 모습을 영상으로 담아보았다.


위에서 처럼 동작하기 위해 코딩한 파일은 2개이며 모두 .js파일 즉 javascript이다. (+북마클렛용 js코드 한줄)
파일 두개로 뚝딱(!?) 가능한 이유는 바로 Titanium과 Nodejs 덕분이다.

Titanium Appcelerator : javscript로 다양한 플랫폼의 application 개발이 가능한 framework ( appcelerator.com )
Nodejs : Server side javascript로 js만으로 server를 구현 할 수 있다. ( nodejs.org )

#1 app.js for Titanium mobile
아이폰/안드로이드 앱은 nodejs 서버에서 url을 가져온다. 아이폰의 경우 tcp socket을 통해 연결되어 있다. 아쉽게도 titanium sdk 1.7 preview 현재 버전까지는 안드로이드에서 tcp socket을 지원하지 않는다. 일단 안드로이드에서는 그냥 xmlhttprequest로 계속 주소 변화를 확인하도록 했다. (업데이트 : 1.7 정식버전에는 안드로이드도 지원하는 Socket API가 추가 되었다.)

// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

var webview = Titanium.UI.createWebView({
	url:'http://m.daum.net',
	bottom:40
});

var win = Titanium.UI.createWindow();
win.add(webview);

Ti.App.Properties.setBool('_watching', false);
var watchURLChange = function(host, port, win) {
	if(Ti.App.Properties.getBool('_watching')) {
		return false;
	}
	Ti.App.Properties.setBool('_watching', true);

	var socket = Titanium.Network.createTCPSocket({
		hostName: host,
		port: port,
		mode: Titanium.Network.READ_WRITE_MODE
	});

	socket.addEventListener('read', function(e) {
		try {
			var o = JSON.parse(e.data.text);
			switch(o.action) {
				case 'changeURL':
					Ti.API.info('url Changed : '+o.url);
					webview.url = o.url;
					break;
				case 'connect':
					Ti.API.info('Socket connected');
					webview.url = o.url;
					break;
			}
		} catch(event) {
			Ti.API.error('read error', event);
		}
	});
	// Cleanup
	win = win || Ti.UI.currentWindow;
	if(win) {
		win.addEventListener('close', function(e) {
			if (socket.isValid) {
				Ti.API.log('close socket');
				socket.close();
			}
		});
	}

	socket.connect();
	socket.write(JSON.stringify({
		action: 'echo',
		message: 'Socket connected'
	}));

};
//webview 아래 컨트롤 view 생성
(function() {
	var autoIntervalID;
	//reload 버
	var reloadBtn = Titanium.UI.createButton({
		title : "Reload",
		width : 60
	});
	reloadBtn.addEventListener('click', function() {
		webview.reload();
	});
	//뒤로가기 버튼
	var backBtn = Titanium.UI.createButton({
		title:'◀',
		enabled:false,
	});
	backBtn.addEventListener('click', function() {
		webview.goBack();
	});
	//앞으로가기 버튼
	var forwardBtn = Titanium.UI.createButton({
		title:'▶',
		enabled:false
	});
	forwardBtn.addEventListener('click', function() {
		webview.goForward();
	});
	//자동 리프레쉬 스위치
	var autoSwitch = Ti.UI.createSwitch({
		value:false,
		top:5,
		left:50,
		title:"",
		titleOn:"",
		titleOff:""
	});
	autoSwitch.addEventListener('change', function(e) {
		if(e.value) {
			autoIntervalID = setInterval( function() {
				webview.reload();
			},10000)
		} else {
			if(autoIntervalID)
				clearInterval(autoIntervalID);
		}
	});
	// 버튼 wrap
	var btnView = Titanium.UI.createView({
		width:'100%',
		height:40,
		backgroundColor:'#2A2623',
		layout:'horizontal',
		bottom:0
	});
	btnView.add(backBtn);
	btnView.add(forwardBtn);
	btnView.add(reloadBtn);
	btnView.add(autoSwitch);
	win.add(btnView);

	//webview의 loding activity indicator
	var toolActInd = Titanium.UI.createActivityIndicator({
		width:30,
		height:30
	});
	toolActInd.style = Titanium.UI.iPhone.ActivityIndicatorStyle.DARK;
	webview.add(toolActInd);
	webview.addEventListener('load', function(e) {
		toolActInd.hide();
		forwardBtn.enabled = webview.canGoForward();
		backBtn.enabled = webview.canGoBack();
	});
	webview.addEventListener('beforeload', function(e) {
		toolActInd.show();
		forwardBtn.enabled = webview.canGoForward();
		backBtn.enabled = webview.canGoBack();
	});
})();
win.open();

if (Titanium.Platform.name == 'iPhone OS') {
	//iphone의 경우 socket으로 연결
	watchURLChange("192.168.12.102", 8128, win);
} else {
	//android의 경우
	var oldModifyTime = "";
	setInterval( function() {
		var xhr = Ti.Network.createHTTPClient();
		xhr.open("GET","http://192.168.12.102:8080/getURL");
		xhr.onload = function () {
			var resultObj = JSON.parse(this.responseText);
			oldMtime = resultObj.mTime;
			if(oldModifyTime != resultObj.mTime) {
				oldModifyTime = resultObj.mTime;
				//안드로이드 웹뷰의 경우 주소가 같으면 reload하지 않아 강제로 reload
				if(webview.url == resultObj.url)
					webview.reload();
				webview.url = resultObj.url;
			}
		};
		xhr.send();
	},2000);
}

#2 server.js for Nodejs
nodejs에서는 두개의 서버를 띄운다. 1) socket연결을 위한 stream서버 2)android에서 주소를 가져가기 위한 http 서버

// extend array
Array.prototype.remove = function(e) {
	for(var i=0;i<this.length;i++){
	if(e==this[i]) {
		return this.splice(i,1);
	}
	}
};
var file = './url.txt', //browser에서 보낸 url을 저장해놓는 파
fileModifyTime=0; //url.txt파일의 수정된 시간 저장용

/**
 * stream server
 */
var sys = require('util'),
fs = require('fs'),
net = require('net'),
clients = [];

// 다중 접속이 가능하도록 clinet관리
function Client(stream) {
	this.name = null;
	this.stream = stream;
}

net.createServer( function(stream) {
	var client = new Client(stream);
	clients.push(client);

	stream.setTimeout(0);
	stream.setEncoding('utf8');

	stream.on('connect', function() {
		sys.puts('[S#1] App connected');
		fs.readFile(file, function(err, data) {
			stream.write(JSON.stringify({
				action: 'connect',
				url: data.toString()
			}));
		});
	});
	stream.on('end', function() {
		sys.puts('[S#1] disconnected');
		clients.remove(client);
		stream.end();
	});
}).listen(8128, function() {
	sys.puts('[S#1] Stream server running, please start application');
	fs.watchFile(file, {
		interval: 100,
		persistent: true
	}, function(curr, prev) {
		//파일 수정 시간을 비교하여 판
		if(curr.mtime.getTime() != prev.mtime.getTime()) {
			fileModifyTime = curr.mtime.getTime();
			sys.puts("modify Time :" + fileModifyTime);
			fs.readFile(file, function(err, data) {
				var cnt=0;
				//접속된 모든 client로 url을 수정시간과 함께 JSON으로 보냄
				clients.forEach( function(c) {
					c.stream.write(JSON.stringify({
						action: 'changeURL',
						url: data.toString()
					}));
					sys.puts('[S#1] URL file updated: ' + file + ":" + data.toString());
					sys.puts(++cnt);
				});
			});
		}
	});
});
/**
 * http Server
 */
var http = require('http'),
path = require('path'),
url = require('url');

http.createServer( function(req,res) {
	var uri = url.parse(req.url).pathname;
	var queryString = url.parse(req.url).query;
	console.log('[S#2] ' + uri);
	switch(uri) {
		case '/rd':
		case '/redirect':
			fs.readFile(file, function(err, data) {
				res.writeHead(302, {
					'Location': data.toString()
				});
				res.end();
				console.log("[S#2] redirect : " + data.toString());
			});
			break;
		case '/getURL':
			fs.readFile(file, function(err, data) {
				res.writeHead(200, {
					"Content-Type" : "text/html"
				});
				console.log("[S#2] getURL : " + data.toString());
				res.write(JSON.stringify({
					url : data.toString(),
					mTime : fileModifyTime
				}));
				res.end();
			});
			break;
		case '/newURL':
		default:
			res.writeHead(200, {
				"Content-Type" : "text/html"
			});
			res.write(queryString);
			console.log("[S#2] bookmarklet update :" + queryString);
			fs.writeFile(file,queryString, function(err) {
				if (err) {
					console.log("[S#2] write file error");
				} else {
					console.log("[S#2] write file success");
				}
			});
			res.end();
			break;
	}
}).listen(8080, function() {
	console.log('[S#2] http Server Running');
});

#3 browser bookmarklet script
브라우저에서 현재 보고 있는 페이지의 url을 node로 보내기 위한 북마크 javascript이다. 이 javascript를 북마크에 저장해두고 클릭하면 node로 url을 보낸다.

javascript:var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET","http://192.168.1.104:8080/?"+location.href,true); xmlhttp.send(null);

서버와 모바일 어플 모두 만드는데 직접 코딩한 파일은 겨우 두개의 .js, 두개 파일 합쳐도 300줄이 안된다. 이건 간단한 예시에 불과하다. 앞으로 발전하는 titanium, node와 함께 재미있는 일을 많이 할 수 있을 것 같다.^^


Titanium Studio에서 Android SDK 설정이 안될 때..

Titanium Studio가 공개되기 전에 Titanium Developer를 통해 Android, iOS 양쪽 모두 컴파일 하고 실행하는데 아무런 문제가 없었다.  Titanium Studio를 설치하고 나니 Android SDK를 찾지 못하는 이상한 문제가 발생했다. 처음엔 preview 버그겠지하고 지나가려고 했는데 자꾸 눈에 거슬렸다.

위 캡쳐에서 볼수 있듯이 “Could not locate the SDK at the given path” 라고 나온다. 분명 저 path에는 android sdk가 있다. 근데 왜 안될까? 어제 이걸로 고생했는데 혹시나 하고 이것 저것해보니 해결방법을 찾았다.
“SDK Platform Android API7″이 없어서 그런것이다. 꼭 1.7로 컴파일 해야되는 것도 아닌데 못찾는다. Titanium Developer를 쓸 때는 잘만 되었는데.. 왜 그런진 모른다. 어쨋든 문제 해결!! 나중에 원인을 알게되면 적어둬야지.