blog.Ring.idv.tw

iPhone

QRCode 掃描器 - 怪獸版 (支援紙本電子發票)

最近寫了一個「QRCode 掃描器 - 怪獸版」,但其實目前市面上QRCode掃描器那麼多了,為何我還想做一個? 除了練功之外,那就是市面上所有的QRCode掃描器在設計上其實都很雷同,但這也沒啥不好的,只要功能有符合大眾的需求就好了! 的確如此,但除了這之外還能不能有些不一樣?我想,而這也就是為何會有「QRCode 掃描器 - 怪獸版」App的產生。

首先,先來談談設計好了... 為什麼我會選擇「大眼怪」來作為設計的主軸?其實答案很簡單,當我確定要做一個QRCode相關的App之後,腦海裡瞬間掃過N張影像,唯獨這張「大眼怪」讓我印象深刻,直覺就認為它和QRCode掃描器相當搭,可以直接將怪獸的眼睛作為掃描的路口,所以就在這以「怪獸」為主軸的大方向確定之後,接下來其它的設計部份也就比較容易去處理了。

接著從功能面上來談... 嗯~這麼說好了,當初在決定寫一個QRCode掃描器的同時,除了介面的設計之外,那就是目前絕大多數的QRCode掃描器都沒有支援「紙本電子發票」的功能(除了少數幾款之外),所以「嶄新的介面」+「支援紙本電子發票」這兩個因素下,讓我決定開始來開發這個App。

在開發的過程中,由於並不是第一次開發App,所以倒是沒有碰到什麼比較技術性難解的問題,反倒是之前在研究「Multipeer Connectivity」時還比較棘手一些,這部份有機會再寫篇文章好了,唯一可能會稍微卡關一下的可能就是文字解碼的部份吧! 根據「電子發票證明聯 - 二維條碼」的規格書,它總共支援了三種文字編碼格式,它們分別為UTF-8Big5Base64。不過在這測試的過程中,以Base64為編碼的紙本電子發票我倒還沒看過,大多都以UTF-8為編碼的較為廣泛些,另外有少數幾間會用Big5格式,例如:中國石油…,所以解決文字的解碼問題之後,剩下的就是一些瑣碎的小事了。

有人會問我,那這個App可以直接掃描對獎嗎?基本上要實現這個功能並沒有什麼難度,但現階段我並不想做這個功能,理由其一是個人認為「掃描對獎不會比人工對獎快」,二是「傳統人工對獎比較有fu」,所以這對獎功能還是暫時先保留吧!

至於關於這個App的目前成績,從7/23日上架後到本文完稿為止,目前這個App在App Store台灣區的工具程式類排行取得算還不錯的成績(個人認為),因為在沒有花任何一毛錢做行銷之下,iPad排行從一開始的119名,到目前最佳的41名,而iPhone排行更是從榜單外,衝進到榜單內的第88名,另外用「qrcode」這個關鍵字搜尋,也從上架第一天分別在iPad及iPhone排行第5和第6名,直到現在躍進到第3、4名,由於目前只有上架五天還不到...未來如果更多人知道的話,我想排名應該可以再更好!

P.S. 最後在此特別感謝曾經幫我在FB分享的親朋好友!

2014-07-28 12:46:41 | Add Comment

Smart App Banner

Smart App Banner是iOS6的新特色之一,它的功能主要是應用在當使用者瀏覽到某網頁時,在該網頁的上方會出現一個App介紹的橫幅版面,透過這樣的方式可以讓使用者方便快速的連結下載此App,而它的用法也相當簡單:

Smart App Banner

下述語法即呈現Angry Birds的Smart App Banner在某網頁中,主要的用法在於「app-id=343200656」,後面的數字換成你的App ID即可。

<meta name="apple-itunes-app" content="app-id=343200656">

P.S. 上述測試裝置使用iPad3/iOS6 beta1, 奇怪的是iPod Touch4/iOS6 beta1尚未支援。

Web Application Title

另外在iOS6也新增了此meta tag,它的作用在於當我們想要將某網頁「加入主畫面」時,基本上預設會採用網頁上的<title>文字內容,然而透過下述的簡單設定即可以區隔一般網頁的<title>和「加入主畫面」的title:

<meta name="apple-mobile-web-app-title" content="My App">

2012-06-19 08:46:01 | Add Comment

Web Application for iOS

隨著iOS6開始支援更多的HTML5 related APIs和相關規格,如:requestAnimationFrameWeb Audio APIWebSocket (RFC 6455),下述是一些針對iOS平台開發Web Application可參考的相關組態設定:

Viewport Meta Tag

Viewport組態設定可以說是Mobile Web Application最重要的設定之一,因為它是直接用來控制瀏覽器是如何呈現你的網頁內容:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>

width=device-width: 表示用來適應各個不同裝置的寬度大小

initial-scale: 1.0 用以初始化的scale

maximum-scale: 1.0 由於初始化的scale為1.0,所以此行等同於讓使用者無法縮放

詳細的設定細節與圖文說明可參考:Configuring the Viewport

Full-Screen Mode

設定Web Application是否採用full-screen mode來運作

<meta name="apple-mobile-web-app-capable" content="yes" />

另一方面,開發者也可以使用JavaScript去取得「window.navigator.standalone」property來判斷目前是否運作在full-screen mode。

Status Bar Appearance

設定狀態列的樣式,分別有「default」、「block」和「black-translucent」可以使用,若採用full-screen mode運作可以採用「black-translucent」

<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>

Format Detection

由於Safari預設會自動偵測可能的電話格式字串並主動加入連結,所以下述設定可以將此功能關閉

<meta name="format-detection" content="telephone=no">

Webpage Icon for Web Clip

此設定為當Web Application加入主畫面螢幕之後,在螢幕上要呈現的圖示:

<!-- iPhone/iPod -->
<link rel="apple-touch-icon" href="images/touch-icon-iphone.png"/>
<!-- iPad -->
<link rel="apple-touch-icon" sizes="72x72" href="images/touch-icon-ipad.png"/>
<!-- iPhone/iPod Retina -->
<link rel="apple-touch-icon" sizes="114x114" href="images/touch-icon-iphone-retina.png"/>
<!-- iPad Retina -->
<link rel="apple-touch-icon" sizes="144x144" href="images/touch-icon-ipad-retina.png"/>

Startup Image

Web Application啟動後要顯示的初始畫面,預設為320x460 (20px保留狀態列使用)

<link rel="apple-touch-startup-image" href="img/splash-screen-320x460.png">
<!-- iPad Landscape -->
<link rel="apple-touch-startup-image" sizes="1024x748" href="img/splash-screen-1024x748.png" />
<!-- iPad Portrait -->
<link rel="apple-touch-startup-image" sizes="768x1004" href="img/splash-screen-768x1004.png" />
<!-- iPhone Landscape Retina -->
<link rel="apple-touch-startup-image" sizes="960x600" href="img/splash-screen-960x600.png" /> 
<!-- iPhone Portrait Retina -->
<link rel="apple-touch-startup-image" sizes="600x960" href="img/splash-screen-600x960.png" /> 

Add to Home Screen

上圖是iPhone瀏覽Yahoo!行動版首頁所出現的「Add to Home Screen」提示,原本想說自己來寫一個好了~ 後來想想這東西應該有早已有人作出來了,找了一下發現網路上有位Matteo Spinelli作者開發了ADD TO HOME SCREEN元件,有興趣的話可以參考它的作法。

Cancelling Default Touchmove Event

防止使用者移動頁面,直接通知Browser不要執行預設的touchmove事件。

window.onload = function()
{
	document.addEventListener("touchmove", function(e){
		e.preventDefault();
	}, false);
}

參考資源

iPhone 4 Retina “apple-touch-startup-image” for Web-apps

Supported Meta Tags

Configuring Web Applications

2012-06-18 16:42:37 | Add Comment

HTML5 video autoplay on iOS

許多時候我們在使用iPhone/iPad瀏覽一些包含HTML5 video的網頁時,會發現為何網頁中的video都不會自動播放?而使用一般PC平臺上的瀏覽器卻又都能自動播放?

根據iOS-Specific Considerations的說明如下:

In Safari on iOS (for all devices, including iPad), where the user may be on a cellular network and be charged per data unit, 
preload and autoplay are disabled. No data is loaded until the user initiates it. This means the JavaScript play() and load() methods 
are also inactive until the user initiates playback, unless the play() or load() method is triggered by user action. 
In other words, a user-initiated Play button works, but an onLoad="play()" event does not.

簡單來說~ 其實主要是用來防止「以量計價」的行動網路費爆增~ 這考量的確沒錯! 但是為何不將此功能改為可選項開關呢?因為現階段有許多採用資費吃到飽方案或是用WiFi網路的客群,若能提供可選項開關或是像使用GPS定位一樣的作法,主動彈出一個confirm視窗來提示是否允許自動播放也不失為一種方式呀!

所以~ 現階段如果想在iPhone/iPad瀏覽器中播放video那就得透過使用者觸發才行... Orz 話雖如此,但其實還可以透過另一種方式來達到video自動播放,那就是透過UIWebView元件來達成,也就是說~ 如果你是採用PhoneGap開發的應用程式,那恭禧你~ 只需要對AppDelegate.m 原始碼加上下述設定:

theWebView.allowsInlineMediaPlayback = YES;
theWebView.mediaPlaybackRequiresUserAction = NO;

同時也需要針對你的video element加上「webkit-playsinline」attribute即可,如:

<video id="video" src="xxx.mp4" autoplay webkit-playsinline></video>

現在開啟你的App就能看到video自動播放!

不過iOS Safari針對Canvas的支援仍有些不足~ 所以上述HTML5 Canvas&Video: Big Buck Bunny範例就算移植到UIWebView仍然無法看到效果的~ 因為iOS Safari針對Canvas少了下述APIs的支援:

void drawImage(HTMLVideoElement image, double dx, double dy);
void drawImage(HTMLVideoElement image, double dx, double dy, double dw, double dh);
void drawImage(HTMLVideoElement image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh);

如此就只能被迫將video轉成一張張image才能達到類似的效果了...

參考資源

UNSOLVED HTML5 VIDEO ISSUES ON IOS

iPad and iPhone html5 video autoplay

2012-01-04 10:41:59 | Add Comment

PhoneGap 1.0.0 for Android

PhoneGap 最初是由「Nitobi」這間位於加拿大的公司在2008年8月所發起的,而到2010年中IBM也開始參與投入研發。它是一套能讓你選擇採用「New BSD」或「MIT」license的Open Source Mobile Framework,它主要的目的在於能讓開發人員透過一些Web技術(HTML+JavaScript)來存取行動裝置的Native API,而且它支援了Android、iOS、Blackberry、WebOS、Windows Phone 7等平台的支援,就在上個月的29日它發佈了PhoneGap 1.0.0。

開發人員只需要撰寫一份HTML+JavaScript的程式,就可以在這些平台上存取這些行動裝置所提供的功能,如:Camera, Compass, Contacts等...,然而為何需要有這個Framework的存在呢?這最大的原因就在於我們現階段仍無法透過Web來存取上述這些裝置功能,而W3C雖然正在針對行動裝置制定標準的Device APIs,不過現階段仍無法透過Web來存取照相機或相簿等功能(Android 3.0將支援Media Capture API),所以你可以將PhoneGap當做是一個「過渡性」的產品,或者等未來Web都已支援這些Native APIs,但卻有一致性或相容性等其它問題時,也許那時PhoneGap仍然是一個好的選擇,這就像是HTML5與Flash現階段的情況,看看Google+首波合作的遊戲商就可以知道了.. 16款遊戲中有15款採用Flash來開發

探討 PhoneGap 1.0.0 for Android

本文並不是一篇教你如何使用PhoneGap建立HelloWorld的無痛上手,而是將深入探討PhoneGap 1.0.0在Android平台上如何扮演Web與Native API之間的橋梁角色,有興趣的朋友請至Github - phonegap下載。

本文會採用Notification.alert這個簡單的API來說明這之間的流程,至於為什麼JavaScript上早已經有alert功能,為何還需要Notification.alert呢?這兩者之間最大的差別就在於Notification.alert可以讓你「客製化」一些資訊,如:標題(title)或按鈕上的文字(buttonLabel)。

下述是PhoneGap所定義的Notification.alert的API

navigator.notification.alert(message, alertCallback, 〔title〕, 〔buttonName〕)

用法如下:

function alertDismissed() {
    // do something
}
navigator.notification.alert(
    'You are the winner!',  // message
    alertDismissed,         // callback
    'Game Over',            // title
    'Done'                  // buttonName
);

JS - Notification.prototype.alert

接著直接將PhoneGap的JS原始碼翻來看(phonegap-1.0.0.js),它會執行「PhoneGap.exec」。

Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) {
    var _title = (title || "Alert");
    var _buttonLabel = (buttonLabel || "OK");
    PhoneGap.exec(completeCallback, null, "Notification", "alert", [message,_title,_buttonLabel]);
};

JS - PhoneGap.exec

這部份就是JavaScript如何和Native API溝通的橋梁,這裡有個「callbackId」,它是由「service name + counter」所組成的,例:Notification2,並以此callbackId為key,以callback funcation為value先暫存在PhoneGap.callbacks的陣列。

PhoneGap.exec = function(success, fail, service, action, args) {
    try {
        var callbackId = service + PhoneGap.callbackId++;
        if (success || fail) {
            PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
        }
        
        var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));

而重點就在於下面這一行:

var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));

prompt」?沒錯~ PhoneGap 1.0.0 for Android就是透過JavaScript的「prompt」function將相關參數傳到原生的Java程式中,為何能這樣達成?原因就在於Android所設計的WebView元件,它讓開發人員能夠透過WebChromeClient來自訂JavaScript中的alert, confirm, prompt等功能的處理方式,而上述函式實際傳送到Java中的參數其實就如下圖所示:

DroidGap.java - onJsPrompt

上述所提供的「prompt」實際上就是呼叫DroidGap.java中的「onJsPrompt」函式(line:868),更確切的說「onJsPrompt」是被宣告在GapClient(inner class)中,詳細的細節請參考Source code。

public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

而「onJsPrompt」處理的方式就是先判斷所傳進來的「defaultValue」是否為「gap:」字串開頭,以本文的為例就是將「gap:['Notification','alert','Notification2',true]」進行拆解,再透過「pluginManager.exec」去執行。

if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
       	JSONArray array;
        try {
        	array = new JSONArray(defaultValue.substring(4));
        	String service = array.getString(0);
        	String action = array.getString(1);
        	String callbackId = array.getString(2);
        	boolean async = array.getBoolean(3);
        	String r = pluginManager.exec(service, action, callbackId, message, async);
        	result.confirm(r);
        } catch (JSONException e) {
        	e.printStackTrace();
        }
}

PluginManager.java

基本上在PhoneGap for Android的原始碼中,它所提供的各式API也都稱為Plugin,意指為:Notification、Compass、Camera..等都稱為Plugin,實際上這些一個個的功能也都繼承於Plugin class(它實作IPlugin介面),所以該PluginManager就是用來管理這些Plugin。

在「pluginManager.exec」的函式中,主要就是取得這些特定的Plugin物件,並透過「plugin.execute」來執行。

PluginResult cr = plugin.execute(action, args, callbackId);  //line:119 in PluginManager.java

Notification.java

由於本文以Notification為例,所以上述函式(plugin.execute)會執行「Notification.execute」。

public PluginResult execute(String action, JSONArray args, String callbackId) {
		PluginResult.Status status = PluginResult.Status.OK;
		String result = "";		
		
		try {
			if (action.equals("beep")) {
				this.beep(args.getLong(0));
			}
			else if (action.equals("vibrate")) {
				this.vibrate(args.getLong(0));
			}
			else if (action.equals("alert")) {
				this.alert(args.getString(0),args.getString(1),args.getString(2), callbackId);
				PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
				r.setKeepCallback(true);
				return r;
			}

從「Notification.execute」可以得知,它同時處理了Notification中的「beep」、「vibrate」、「alert」等功能,而實際的「alert」功能實現如下:

public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) {

	final PhonegapActivity ctx = this.ctx;
	final Notification notification = this;
		
	Runnable runnable = new Runnable() {
		public void run() {
	
			AlertDialog.Builder dlg = new AlertDialog.Builder(ctx);
			dlg.setMessage(message);
			dlg.setTitle(title);
			dlg.setCancelable(false);
			dlg.setPositiveButton(buttonLabel, new AlertDialog.OnClickListener() {
				public void onClick(DialogInterface dialog, int which) {
					dialog.dismiss();
					notification.success(new PluginResult(PluginResult.Status.OK, 0), callbackId);
				}
			});
			dlg.create();
			dlg.show();
		};
	};
	this.ctx.runOnUiThread(runnable);
}

實際上透過Android原生的「AlertDialog」來呈現對話框的介面。

至於點擊按鈕之後的callback應用就留待有興趣的朋友自行研究了~ 不過簡單來說它就是透過前端的Ajax(採用Recursive function)不斷地向原生Java中的CallbackServer.java(ServerSocket)請求,若有資訊(callbackId等)就回傳給前端的Ajax。

2011-08-15 22:52:00 | Comments (10)

Previous Posts
Copyright (C) Ching-Shen Chen. All rights reserved.

::: 搜尋 :::

::: 分類 :::

::: 最新文章 :::

::: 最新回應 :::

::: 訂閱 :::

Atom feed
Atom Comment