2010年9月21日 星期二

How to convert .app to .ipa

使用 Xcode 所編譯出來的都是 .app 檔案, 但是大部份情況來說, 使用 .ipa 比 .app 方便多了
因為 .ipa 可以簡單的透過 iTunes 安裝並同步到 iPhone. 既然 ipa 比較方面, 那究竟要如何
達成此幕目的呢? 做法很簡單, 只需要幾個步驟就可以完美產生 .ipa

開啓你的 Xcode 專案, 依照下圖新增一 Build Phase


將下述內容直接複製到 script 欄位中即可


payload_dir="$CONFIGURATION_BUILD_DIR/Payload"
app_bundle_dir="$CONFIGURATION_BUILD_DIR/${PROJECT_NAME}.app"

/bin/rm -rf "$payload_dir"
/bin/mkdir "$payload_dir"
/bin/cp -R "$app_bundle_dir" "$payload_dir"
/bin/cp iTunesArtwork "$CONFIGURATION_BUILD_DIR/iTunesArtwork"
cd "$CONFIGURATION_BUILD_DIR"

/usr/bin/zip -y -r "${PROJECT_NAME}.ipa" Payload iTunesArtwork
rm -rf "$payload_dir" iTunesArtwork


新增 Build Phase 之後, 可以在專案列中看到如下圖般的 "Run Script" 選項


當完成上述步驟時, 只要編譯你的專案 .ipa 就會自動產生 (當然前提是要編譯要成功)
在 build messages 裡面可以看到類似下面的訊息


一切都完成時, 此時就可以看到我們所產生的 .ipa 檔了


Questions for uploading .ipa binary to apple

case 1:
"The CodeResources file must be a symbolic link to _CodeSignature/CodeResources"


If you're using command-line "zip", you need to use zip -y -r to preserve the symlink.
reference


case 2:
Application failed codesign verification.  The signature was invalid, or it was not signed with an Apple submission certificate.


用文字編輯器開啓 .pbxproj , 搜尋 CODE_SIGN_IDENTITY 應該會看到類似下面的話面



以筆者的例子而言, 要找的是 Distrubution , 除了 Distribution 以外, 要留意的有幾個要確定的地方
確認在 buildSettings 裡的 CODE_SIGN_IDENTITY , PROVISIONING_PROFILE (有兩組)
是否為所需要的. 問題就來了, 要怎樣才能知道我們所要的資訊是什麼呢?
針對 CODE_SIGN_IDENTITY , 我們需要開起 Mac 裡的 Keychain Access , 並選擇左下方的“憑證”欄位



 打開所需要的憑證



觀看其內容會發現有一個欄位寫著憑證名稱, 此名稱就是要填入到上述 CODE_SIGN_IDENTITY 的值
置於另一個 PROVISIONING_PROFILE 呢? 此時請打開 Xcode | Windows | Organizer


    
上述的 Profile Identifier 所顯示的值就是我們需要填入 PROVISIONING_PROFILE 的.
上述兩組值需要根據你所申請的 Certification/Provision 的實際情況來填寫
reference



2010年9月17日 星期五

How to implement customized keyboard on iOS4

在 iPhone 上面開發應用程式時, 在使用輸入鍵盤時, 或多或少都會遇到客制化鍵盤的問題, 這邊筆者以
簡單的數字鍵盤來示範客制化的動作. 這部份我想網路上已經有不少 sample code , 但大部份基本上都是
以 SDK 3.x 的版本去實作, 以"特定寫法"來實作客制化在 iOS4 會有問題, 這部份稍候會提到兩版本的差異.


上述看到的例子是 UIKeyboardTypeNumberPad 搭配 "Done" 的圖示所組合而成的. 在開
始介紹如何實作之前, 先稍微提一下網路上查到的一些範例寫法. 因為 SDK 升版之
後在架構上有做了些修改, 所以導致行為上的不正確. 以下面這例子為例, 它可以正
常的在 iOS4 之前的版本運行, 但在 iOS4 上卻會有看不到上面 "Done" 圖示的問題.

- (void)loadView{
    ...
    textFieldContent.delegate = self;
    textFieldContent.placeholder = @"press me";
    textFieldContent.keyboardType = UIKeyboardTypeNumberPad;
    textFieldContent.returnKeyType = UIReturnKeyDone;
    [self.view addSubview:textFieldContent];
    [textFieldContent release];
    
    [[NSNotificationCenter defaultCenteraddObserver:self 
                                    selector:@selector(keyboardWillShow:) 
                                        name:UIKeyboardWillShowNotification 
                                      object:nil];
    
}

- (void)keyboardWillShowOnDelay:(NSNotification *)notification{
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 163, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    [doneButton setImage:[UIImage imageNamed:@"DoneUp.png"] forState:UIControlStateNormal];
    [doneButton setImage:[UIImage imageNamed:@"DoneDown.png"] forState:UIControlStateHighlighted];
    [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];
    
    UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
    UIView* keyboard;
    for(int i=0; i<[tempWindow.subviews count]; i++) {
        keyboard = [tempWindow.subviews objectAtIndex:i];
        if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES)
            [keyboard addSubview:doneButton];
    }
}

上述這段代碼主要原理是透過跟 OS 註冊 keyboard 相關的 notification, 並在顯示
keyboard 時, 在 keyboard view 上添加所需要的特定 UIView, 簡單流程大致如下
1. 註冊 UIKeyboardWillShowNotification : 當 keyboard 要秀時, OS 就會呼叫
    keyboardWillShow
2.  當被 keyboardWillShow 叫用時, 搜尋屬於 keyboard 的 view
   if([[keyboard descriptionhasPrefix:@"<UIKeyboard"] == YES)
3. 當找到所需要的 view 時, 再將需要的 view 加入即可
   [keyboard addSubview:doneButton];
上面就是一個 customized keyboard 的簡單實作流程. 但是為什麼這段 code 會無法
在 iOS4 上正確執行呢? 問題點主要出在上述的第 2 個步驟.
在舊的 SDK 中, 當 UIKeyboardWillShowNotification 事件發生且叫用 keyboardWillShow 
時, 此時的 keyboard view 已經被添加到 windows 裡了, 但是在 iOS4 的世界中, 相同
情況發生時, keyboard view 卻會在下個 event loop 裡才會被添加到 windows 中, 也
就是因為如此, 所以上述
[[[UIApplication sharedApplicationwindowsobjectAtIndex:1];
會找不到 keyboard view. 除了這原因以外, 還有另一個重要的差異性, 第 2 步驟所比
對的 @"<UIKeyboard" 字串在 iOS4 中也被修正過, 它被藏在 @"<UIPeripheralHostView"
裡.
針對這兩點, 所以將只要將之修正即可正常的在 iOS4 上執行
1. keyboard view
   既然知道是 keyboard view 會在下個 event loop 才會被放到 windows 裡, 所以我們
   可以透過下面方式將 keyboardWillShow 延遲叫用
   [self performSelector:@selector(keyboardWillShow:) withObject:nil afterDelay:0];
2. 修正比對 @"<UIKeyboard" 的方式

   if ([[possibleKeyboard description] hasPrefix:@"<UIPeripheralHostView"]) 
       possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];

   if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"])  
   {
       foundKeyboard = possibleKeyboard;
       break;
   }                
經過上述兩個修正之後的 code 大概會如下 :
    [[NSNotificationCenter defaultCenter] addObserver:self 
                    selector:@selector(keyboardWillShowOnDelay:) 
                        name:UIKeyboardWillShowNotification 
                      object:nil];

- (void)keyboardWillShowOnDelay:(NSNotification *)notification
{
    [self performSelector:@selector(keyboardWillShow:) withObject:nil afterDelay:0]; 
}

- (void)keyboardWillShow:(NSNotification *)notification
{    
    UIView *foundKeyboard = nil;
    
    UIWindow *keyboardWindow = nil;
    for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) 
    {
        if (![[testWindow class] isEqual:[UIWindow class]]) 
        {
            keyboardWindow = testWindow;
            break;
        }
    }
    if (!keyboardWindow) return;
    
    for (UIView *possibleKeyboard in [keyboardWindow subviews]) 
    {            
        //iOS3
        if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"]) 
        {
            foundKeyboard = possibleKeyboard;
            break;
        }
        else
        {
            // iOS 4 sticks the UIKeyboard inside a UIPeripheralHostView.
            if ([[possibleKeyboard description] hasPrefix:@"<UIPeripheralHostView"]) 
            {
                possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];
            }                                                                                
            
            if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"]) 
            {
                foundKeyboard = possibleKeyboard;
                break;
            }                
        }            
    }
    
    if (foundKeyboard) 
    {
        // create custom button
        UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
        doneButton.frame = CGRectMake(0, 163, 106, 53);
        doneButton.adjustsImageWhenHighlighted = NO;
        [doneButton setImage:[UIImage imageNamed:@"DoneUp.png"] forState:UIControlStateNormal];
        [doneButton setImage:[UIImage imageNamed:@"DoneDown.png"] forState:UIControlStateHighlighted];
        [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];
        
        [foundKeyboard addSubview:doneButton];
    }
}
這邊要注意到的是, 因為同一個 app 有可能會在新舊版本的 OS 上執行, 所以若只有
針對 iOS4 而做修改, 就有可能會造成舊 iPhone 無法正常值行, 有鑒於此, 上述這段
 code 針對比對字串做了點小技巧, 讓此 code 可以符合新舊版本.
完整的範例可以到 這裡 下載

2010年9月12日 星期日

Introduce COM (Component Object Model)

這陣子對於 DirectShow 的研究, 研究的越仔細, 就越覺得 DirectShow 的強大, DirectShow
簡化了多媒體的撥放, 格式的轉換, 也允許開發者可以自訂新的元件. 因 DirectShow 是基於
COM (Component Object Model) 所發展出來的, 其裡面的 Component 都是可以很簡單的被
使用, 但也因為是 COM, 所以要開發 DirectShow 相對的就必須要對 COM 有所了解.

COM 並不是某一種特別的應用, 而是一個你可以用來創造任何形式應用的 Model. 例如 ActiveX
, OLE 都是使用 COM 為基礎來開發, 而 windows 上常看到的 DLL 也是一個 COM 的應用,但
COM 的應用並不只有這樣而已.

"COM 定義了一個建立元件的標準". 原則上每個程式設計師都可以自訂屬於自己的元件標準,
但以這樣的前提下, 每個人所開發出來的元件就可能會有不相容的情形, 而軟體元件只有在我們
都同意使用相同定義時才會有其價值. 因為 COM 的標準, 所以只要所有程式設計師都採用此
規定的標準來實作軟體元件, 這樣這些軟體元件才可以一起運作.

COM 支援 OOP 的三個原則: Encapsulation, Polymorphism, Inheritance.
1. Encapsulation :
  透過封裝, 開發者並不需要知道元件內的實作方式或是其演算法, 僅需知道如何與元件聯繫即
  可, 對開發者來說, 元件模組是如何實作, 或是用哪種語言實作都不重要, 只有元件模組的
  使用方式才重要.

2. Polymorphism :
  "一個介面 , 多個方法". 它可以讓開發者自由的將模組給實行成他所適合的模組特性, 因此
  你可以明確指示某個元件要如何工作, 而其他則由其他人自行決定要如何運作.

3. Inheritance :
  允許一個元件去繼承其他元件功能的特性.

COM 所定義的 interface 中, 最重要的就是 IUnknown, 它是所有 COM 物件都必須要實作的
interface. IUnknown 定義了三個 interface : QueryInterface(), AddRef(), Release().
QueryInterface :
  此介面會查詢元件是否實作了某個指定的介面, 若有實作, 則回傳此介面的指標. 如果指定的
  interface 沒有被支持的話, QueryInterface() 會回傳 E_NOINTERFACE error code.

AddRef,Release :
  在 COM 裡管理方式是透過 reference count 的方式, 當 count 為 0 時, 此物件就會被釋放

以下為 DirectShow 裡 CClassFactory 針對上述三個 interface 的實作
STDMETHODIMP CClassFactory::QueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv,E_POINTER)
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    *ppv = NULL;

    // any interface on this object is the object pointer.
    if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) {
        *ppv = (LPVOID) this;
// AddRef returned interface pointer
        ((LPUNKNOWN) *ppv)->AddRef();
        return NOERROR;
    }

    return ResultFromScode(E_NOINTERFACE);
}


STDMETHODIMP_(ULONG) CClassFactory::AddRef()
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
    if (--m_cRef == 0) {
        delete this;
        return 0;
    } else {
        return m_cRef;
    }
}

每一個 COM 都會有一個 GUID (Global Unique IDentifier), 譬如 :

B38847F0-4CBE-4705-B55A-D0F13552B385

GUID 可以透過 GuidGen.exe 來產生, 通常此程式會放置在 [VC/VS2005]/Common/Tools/Bin
當要使用 QueryInterface 來查詢需要的介面時, 第一個傳入參數就是此介面的 GUID


在任何 COM API 被呼叫之前, COM 的初始化是必須的, 可以透過 CoInitialize() 來達到目的.

//NotUsed 參數是被保留的, 且必須要為 NULL.
HRESULT CoInitialize(LPVOID *NotUsed); 

另外當不在需要 COM 時, 需要呼叫下面函式釋放 COM.

void CoUninitialize();

除了 CoInitialize/CoUninitialize 以外, COM 也提供了 CoCreateInstance 來載入 COM

                           HRESULT __stdcall CoCreateInstance(
                                REFCLSID rclsid,                                 //所要求元件的 CLSID
                                LPUNKNOWN pUnKOuter,                   //控制元件的 IUnknown
                                DWORD dwClsContext,                        //元件伺服器的執行背景
                                REFIID riid,                                             //元件所要求的介面
                                LPVOID *ppv);                                       //傳回元件介面的指標

對於 CoCreateInstance , 可能還需要在稍微簡單解釋一下, 他到底是如何運作的. 我們先大致
看一下 CoCreateInstance 的實作流程

HRESULT CoCreateInstance(...)
{
   HINSTANCE hinstDll;
   DLLGETCLASSOBJECT DllGetClassObject;

   hinstDll = LoadLibrary("xxxx.dll");
   if (successful)
   {
      DllGetClassObject = (DLLGETCLASSOBJECT) GetProcAddress(hinstDll,"DllGetClassObject");
      if (DllGetClassObject)
         hr = DllGetClassObject(...);
   }

   return hr;
}

上述會看到當呼叫 CoCreateInstance 時, 會有幾個動作須要完成
1. 載入所需要的 dll
2. 拿到 DllGetClassObject function pointer, 此 DllGetClassObject 是由 COM 所定義的 interface
   HRESULT __stdcall DllGetClassObject(REFCLSID rclsid,
                                       REFIID riid,
                                       LPVOID *ppv);
呼叫完 CoCreateInstance 時, 理論上應該就可以獲得所需要介面的 pointer 進而操作此
 COM 物件. 另外當我們使用 Microsoft 所提供的開發工具建立 COM 專案時, 上述的
DllGetClassObject 我們將不用自己實作, 在 COM 已經完成這些事情了, 我們只需要依照步
驟建立所需要的介面即可.

How to develop a multi-language app

目前 iPhone 的普及應該已經算是接近國民機的等級了, 在世界各地 iPhone 依然都還
是最搶手的手機之一, 若有想要在 iPhone 上面開發應用程式, 操作的使用界面就不能
單單只用自己國家的語言顯示, 若是企圖想要將自己的 app 發佈到其他國家, 多國語
系的支持就會是必要的.  至於要如何在 iPhone 上面開發多國語系的 app 呢?其實步驟
相當簡單, 首先你先新建一個 windows-base 的新專案, 並如下圖般的新增檔案(New File)


選擇 "Resource" 裡的 "Strings File" 選項

此新增的檔案名稱命名為 "Localizable.strings", 接著依照下圖點選 "Get Info"

開啓 Info 視窗之後, 點擊左下角的 "Make File Localizable" 並選到 "General" tag
此時在你會看到可以新增語系的選項"Add Localization", 若要新增繁體中文則輸入
zh_TW, 而簡體中文則輸入 zh_CN, 在下圖的語系中, 支援了三種



當完成之後會在專案檔案列表中看到 "Localizable.strings" 底下會產生語系字串檔
以筆者的例子而言, 會有三個檔案 "English" , "zh_TW", "zh_CN"


分別在此三個檔案裡面分別輸入不同語系字串內容
English :

"MultiString"="English Version";

zh_TW :
"MultiString"="繁體版本";

zh_CN :
"MultiString"="简体版本";




到目前為止, 已經完成了多國語系開發所需要的準備工作, 接下來就是示範如何
將他們呈現在 iPhone 畫面上.
開啓專案裡的 MultiLanguageAppDelegate.m, 並在 didFinishLaunchingWithOptions
裡面增加如下內容:


    CGRect frame = CGRectMake(10.0, 100.0, 200.0, 40.0);
    UITextView *text = [[UITextView alloc]initWithFrame:frame];
    [text setTextColor:[UIColor blackColor]];
    [text setFont:[UIFont systemFontOfSize:24]];
    [text setText:NSLocalizedString(@"MultiString",nil)];
    [window addSubview:text];
    [text release];
    

此時只要編譯並直接執行 iPhone , 然後在 Simulator 裡切換不同語系, 就會看到不同
的顯示字串

完整的 MultiLanguage 範例可以到 這裡 下載

如何自製憑證

在 iPhone 開發程式一般都會先在 iPhone SDK 所提供的 Simulator 下開發,
可是只有 Simulator 是不夠的, 我想每個人都會想把自己寫的應用程式放到 device
(也就是自己的 iPhone 手機) 上去執行,在這過程中, 發現到新版本的 Xcode &
iPhone SDK 在編譯 target device 版本時, 就會需要憑證來作認證, 若沒有適
當的憑證, 就會遇到如下的錯誤訊息 “Code Signing Identity ‘iPhone Developer’
does not match any valid, non-expired, code-signing certificate in
your keychain” 一般的方式就是去 Apple 官網申請帳號, 並繳交年費 99 USD
纔會獲得一個正式的憑證以供開發者開發, 可是就一般的開發者而言, 這步驟有點擾人,
一來不想要多花錢, 二來又不確定是否自己會走上開發 App Store 上的應用程式這條路,
所以不太想去花這筆錢, 有鑑於此, 我就 google 了一下,發現到的確有很多人在討論如
何不透過官方憑證來達到編譯 target device , 由於時間上的差異, 以及開發工具版
本的不同,  所以自己也親自將這些方式都一一驗證, 以及精簡, 就以我的開發環境來說
iPhone SDK 3.1.2
Xcode 3.2
只會需要三個步驟就可以達到所需要的效果 , 做完下面三個步驟索要修改的東西之後,
應該在 xcode 編譯時, 就不會在出現憑證錯誤的訊息了

1. 修改"/Developer/Platforms/iPhoneOS.platform/Info.plist"文件,默认是用Property
    List Editor打开,然后添加:
    1.1. PROVISIONING_PROFILE_ALLOWED = NO
    1.2. PROVISIONING_PROFILE_REQUIRED = NO
    1.3. 把其中的三处XCiPhoneOSCodeSignContext替换成XCCodeSignContext保存即可.


2. 自製憑證:
應用程式 | 工具程式 | 鑰匙圈存取


憑證名稱一定要是 “iPhone Developer”, 然後直接一直點選”繼續”



中間過程中會有需要你選擇加密方式, 選擇如圖片上所示 RSA


一直按"繼續"直到完成憑證製作完成為止.

3. 在 iPhone Project 裡面設定 "Code Signing"
   在 Project 選單里選 "Get Info"

   在 Build Tag 裡面找到如下的欄位, 並選擇剛剛所產生的憑證即可


上述這樣設定之後, 應該可就可以將所寫好的 app 直接安裝到 Device 中.
[Note]
此方是是在 iPhone SDK 3.1.2 + Xcode 3.2 環境下測試驗證是可行的, 並未在 iOS4
測試, 因為筆者在 iOS4 時代已經跟 apple 花錢加入 iPhone Developer 了.





2010年9月8日 星期三

DirectShow Environment Setup

設置 DirectShow 開發環境需要先下載下面兩個檔案
DirectX SDK 以及 DirectX SDK Extra
前者主要是 DirectX 所需要基本的 SDK, 而後者則包含了 DirectShow/DirectSound
下載完成之後直接安裝 DirectX SDK, 安裝完成之後會發現在
C:\Program Files\[SDK 安裝目錄]\Samples\C++ 發現到裡面並沒有 DirectShow/
此時只需要將 DirectX SDK Extra 所下載的檔案解開, 並把 Extra/ 裡的 DirectShow 複製到
上述目錄中即可, 完成之後應該會看到如下的結果:



看到 DirectShow 之後, 並不代表已經完成環境設定了, 比較麻煩的是需要自行去編譯 
DirectShow Libs
打開 C:\Program Files\[SDK 目錄]\Samples\C++\DirectShow\BaseClasses 底下的 
basesclases.sln, 針對 Debug/Debug_Unicode/Release/Release_Unicode 這四個都重新
編譯過, 編譯完成即可完成 DirectShow 環境建置
在編譯這四個版本的時候, 會遇到一些編譯上的問題, 因為 DirectShow 之前是利用 VC6 開
發, 所以在 compiler 比較嚴謹的 vs2005 下會有一些不合法的編譯錯誤

解決方式分別如下 : 
case 1:
   錯誤寫法 : operator=(LONG);
  (LONG) operator=(LONG);

case 2:
     for (long iDone = 0; ...)
     宣告不可在迴圈內使用, 須在 for 之前


DirectShow 環境設定好之後, 之後若需要使用 DirectShow 時, 只需要在專案中加入
   新增 Include Path : [SDK 目錄]\Include
                                  [SDK 目錄]\Samples\C++\DirectShow\BaseClasses
                                  [SDK 目錄]\Samples\C++\DirectShow\Common
   新增 Lib path     : [SDK 目錄]\Samples\C++\DirectShow\BaseClasses\Debug_Unicode (或是 Release)
   新增 linking lib  : strmbasd.lib winmm.lib (若是 release 則將 strmbasd.lib 換成 strmbase.lib)

[NOTE]
   若你的電腦有安裝 Nero, 就有可能會在執行過程中遇到 "Protection Error", 當發生時, 只需要
   移除 Nero 即可