2011年1月27日 星期四

幫 iBooks 增加 英漢字典


update: 2011/02/18

參考資料:
1. ibook的翻译功能
   http://bbs.weiphone.com/read-htm-tid-971970.html

2. [分享] 讓iBook也有繁體中文字典 - 第3頁
   http://iphone4.tw/forums/showthread.php?t=87551&page=3

3. 让iBook能查英汉词典及汉语大字典
   http://bbs.weiphone.com/read-htm-tid-1029292.html

4. 傻瓜化“给iBook添加字典的方法!
   http://bbs.weiphone.com/read-htm-tid-1364300.html


在此以 iPod Touch 為例來說明 ....


一. 前置步驟:
A. 首先 iPod Touch 必須先 JB 過, 然後在可上網的環境下,
    開啓 iBooks 的任一本英文書, 並使用字典功能:
 

B. 接著就按下載. 開始下載官方提供的 三個英英字典:
 
   New Oxford American Dictionary.dictionary
   Oxford American Writer's Thesaurus.dictionary
   Apple Dictionary.dictionary
說明: 這麼作的原因, 是要在 iPod Touch 的 iBooks 相關目錄下,
         自動產生存放字典檔的 Dictionaries 目錄.

二. 下載英漢字典檔:
     A. 懶蟲簡明英漢詞典.dictionary.rar
     B. 朗道英漢字典.dictionary.rar
     C. 21世紀英漢漢英雙向詞典.dictionary.rar
    
     來源: http://iphone4.tw/forums/showthread.php?t=87551&page=3
     說明: Mac 內建的字典格式跟 iBooks 字典檔的格式是相同的,
             所以也可以使用 Mac 上的字典檔.



三. 在 Mac 上利用 DiskAid 來查找 iBooks.app 資料夾與 Dictionaries 資料夾.
     A. 尋找 /var/mobile/Applications/xxxx 目錄下, 含有 iBooks.app 的資料夾:
         結果 => xxxx 為: 535B5987-7EA1-4D6B-A6BA-5658D0993D8D

        iBooks.app 的資料夾:
        /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/iBooks.app

     B. 在此 xxxx 目錄下的 Library  目錄內即有 Dictionaries 資料夾.

        Dictionaries 資料夾:
        /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/Library/Dictionaries
     

四. 調整: 字典管理順序設定檔
     A. 順序設定檔位置:
        /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/iBooks.app/
        檔名: BKDictionaryManager_LanguageToOrder.plist

     B. 下載(sftp)後, 在此 .plist 內, 可以找到以下這段:
        <dict>
            <key>en</key>
            <array>
                <string>com.apple.dictionary.NOAD</string>
                <string>com.apple.dictionary.OAWT</string>
                <string>com.apple.dictionary.AppleDictionary</string>
            </array>
        ........
        <dict>

        說明: 1. <key>en</key> 代表: 當遇到英文(en)時,
                  會依序(由上而下)查找以下的字典,
                  當有找到時, 就不會再找下一個字典, 亦即:
                  查詢的結果只會出現最先找到的字典那一個結果.

                2. <string> .... <string>, 這三組字串是先前下載的英英字典的 ID 值:
                   New Oxford American Dictionary.dictionary
                   Oxford American Writer's Thesaurus.dictionary
                   Apple Dictionary.dictionary
                                  
     C. 先假設我們要查找的英漢字典的先後順序, 並定好 ID 值:
        1. 懶蟲簡明英漢詞典
           英文檔名: Lazy Worm English-Chinese Dictionary
                     ID: com.apple.dictionary.LWECD

        2. 朗道英漢字典
           英文檔名: Langdao Englih-Chinese Dictionary
                     ID: com.apple.dictionary.LECD          

        3. 21世紀英漢漢英雙向詞典
           英文檔名: 21 century bidirectional dictionary
                     ID: com.apple.dictionary.21CBD

     D. 則 BKDictionaryManager_LanguageToOrder.plist 調整如下:

        <dict>
            <key>en</key>
            <array>
                <string>com.apple.dictionary.LWECD</string>
                <string>com.apple.dictionary.LECD</string>
                <string>com.apple.dictionary.21CBD</string>
                <string>com.apple.dictionary.NOAD</string>
                <string>com.apple.dictionary.OAWT</string>
                <string>com.apple.dictionary.AppleDictionary</string>
            </array>
        ........
        <dict>

     E. 如此一來, 當字典檔都處理好時, 查詢 iBooks 內的英文時就會
        依以下的字典順序
來查找:

        1. 懶蟲簡明英漢詞典
        2. 朗道英漢字典
        3. 21世紀英漢漢英雙向詞典
        4. New Oxford American Dictionary
        5. Oxford American Writer's Thesaurus
        6. Apple Dictionary

     F. 存檔並回傳(sftp), 調整檔案權限.(ssh 登入)
        cd /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/iBooks.app

        chown mobile:mobile BKDictionaryManager_LanguageToOrder.plist


五. 處理: 英漢字典檔
     A. 懶蟲簡明英漢詞典
         1. 解壓縮: 懶蟲簡明英漢詞典.dictionary.rar , 進入 Contents 目錄,
            打開 Info.plist 找到並調整以下設定:

    <key>CFBundleDisplayName</key>
    <string>懶蟲簡明英漢詞典</string>   
    <string>Lazy Worm English-Chinese Dictionary</string> // 檔名
   
    <key>CFBundleIdentifier</key>
    <string>com.apple.dictionary.懶蟲簡明英漢詞典</string>
    <string>com.apple.dictionary.LWECD</string> 
    // ID => Lazy Worm English-Chinese Dictionary 縮寫

   
    <key>CFBundleName</key>
    <string>懶蟲簡明英漢詞典</string>  // 字典標題? (預設未改, 尚無作用)

         2. 新增資料夾名稱: Lazy Worm English-Chinese Dictionary

         3. 將 Contents 目錄放進來, 資料夾改名為:
            Lazy Worm English-Chinese Dictionary.dictionary


         4. 上傳到 /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/Library/Dictionaries 下.


     B. 朗道英漢字典
         1. 解壓縮: 朗道英漢字典.dictionary.rar , 進入 Contents 目錄,
            打開 Info.plist 找到並調整以下設定:

    <key>CFBundleDisplayName</key>
    <string>朗道英漢字典</string>
    <string>Langdao Englih-Chinese Dictionary</string> // 檔名
   
    <key>CFBundleIdentifier</key>
    <string>com.apple.dictionary.朗道英漢字典</string>
    <string>com.apple.dictionary.LECD</string>
    // ID => Langdao Englih-Chinese Dictionary 縮寫

  
    <key>CFBundleName</key>
    <string>朗道英漢字典</string> // 字典標題? (預設未改, 尚無作用)

         2. 新增資料夾名稱: Langdao Englih-Chinese Dictionary

         3. 將 Contents 目錄放進來, 資料夾改名為:
            Langdao Englih-Chinese Dictionary.dictionary

         4. 上傳到 /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/Library/Dictionaries 下.


     C. 21世紀英漢漢英雙向詞典
         1. 解壓縮: 21世紀英漢漢英雙向詞典.dictionary.rar , 進入 Contents 目錄,
            打開 Info.plist 找到並調整以下設定:

    <key>CFBundleDisplayName</key>
    <string>21世紀英漢漢英雙向詞典</string>
    <string>21 century bidirectional dictionary</string> // 檔名
   
    <key>CFBundleIdentifier</key>
    <string>com.apple.dictionary.21世紀英漢漢英雙向詞典</string>  
    <string>com.apple.dictionary.21CBD</string> 
    // ID => 21 century bidirectional dictionary 縮寫
      
    <key>CFBundleName</key>
    <string>21世紀英漢漢英雙向詞典</string> // 字典標題? (預設未改, 尚無作用)

         2. 新增資料夾名稱: 21 century bidirectional dictionary

         3. 將 Contents 目錄放進來, 資料夾改名為:
            21 century bidirectional dictionary.dictionary

         4. 上傳到 /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/Library/Dictionaries 下.


     D. 調整權限: (ssh)
        cd /var/mobile/Applications/535B5987-7EA1-4D6B-A6BA-5658D0993D8D/Library/Dictionaries

        chmod 775 *.*
        chown mobile:mobile *.*


     E. 將 iPod Touch 重新開機.


六. 備註:
        A. 當 iBooks 版本有升級時, 只需要將調整好的 BKDictionaryManager_LanguageToOrder.plist
           檔案重新上傳(或先刪除原檔), 然後調整權限即可.

        B. 如果 JB iOS 4.2.1 後, 無法正常開啓 iBook 新下載的 .epub 電子書, 解決方式:
           1. 在 Cydia 增加資源 (Add Source): http://repo.insanelyi.com
           2. 搜尋並安裝 iBooks Fix
           3. 重新啟動主機

2011年1月12日 星期三

PSX Emulator for the iPhone/iPod Touch


update: 2011/01/28

在 iPhone 上玩 PS 模擬器:

由於在 Cydia 上無法成功安裝 Psx4All, 出現訊息: cydia.zodttd.com Host Unreachable;
即使把 "cydia.zodttd.com" 加入 Sources, 也無法安裝, 因此以下改成用手動安裝.

參考來源:
1. [iPhone][Cydia] 如何安裝 deb 包裝的 App
   http://blog.lauct.org/archives/1499

2. psx4all | Apple iPhone School
   http://www.appleiphoneschool.com/psx4all/

3. [O] Psx4All 4.0.10 [Repo] Sony Playstation emulator! New and improved! 
   http://xsellize.com/topic/103541-o-psx4all-4010-repo/


A. 在 Cydia 上搜尋 & 安裝: BTstack 套件.
=> 不然之後的手動安裝會失敗並出現以下訊息:

   com.zodttd.psx4all depends on ch.ringwald.btstack; however:
   Package ch.ringwald.btstack is not installed.

   com.zodttd.psx4all depends on unzip; however:
   Package unzip is not installed.

   com.zodttd.psx4all depends on unrar; however:
   Package unrar is not installed.

   p.s. 只要安裝 BTstack 就可以了, unzip 與 unrar 就會自動安裝好.


B. 下載相關檔案到 Mac 上:
   (1). Cydia 的 .deb 安裝檔案: Psx4All v4.0.10
       (此版本支援: PSP formatted PSX EBOOT.PBP file)
       => com.zodttd.psx4all_4.0.10_iphoneos-arm.deb (962.26 KB)
           http://www.multiupload.com/AI083WOAP9

   (2). BIOS file: scph1001.bin
       => http://www.veggie.it...sx/scph1001.zip

   (3). diablo 遊戲檔 (使用: PS 轉 PSP 的 .PBP 檔也可以, 但是比較不順)
       => diablo.bin : http://www.megaupload.com/?d=PVOY3Y88


C. 共享 Mac 的網路給 iPhone: (確定已用 Cydia 安裝了 OpenSSH)
   => Mac OS X > 系統偏好設定 > 共享 > Internet 共享
      (都設定好時要勾選, 此時 Mac 本身可能無法上網)
      > 共享來源: 乙太網路; 對使用以下傳輸埠的電腦: AirPort

   => iPod Touch > 設定 > Wi-Fi > 選擇網路: SnowLeopard (同時查看 IP, ex: 10.0.2.2)

   => Mac OS X > 終端機 > ssh root@10.0.2.2 (enter password: xxxx)


D. 安裝 deb 包裝的 App:
   (1). 利用 sftp 上傳 Psx4All v4.0.10 的 deb 檔案到 iPhone 的以下目錄內:
       /var/mobile/Media/ 然後用 ssh 登入 iPhone 並執行以下安裝步驟:
       cd /var/mobile/Media/
       dpkg -i com.zodttd.psx4all_4.0.10_iphoneos-arm.deb

  
(2). 將 iPhone 重新開機, 即安裝完成.(桌面會出現 PlayStation 的AP圖示, 如下示)

  
      

      


p.s. 我是將所有要上傳的檔案都傳好, 並放好位置後才重新開機.
       但是 Transmit (sftp) 需要斷線重連, 才會出現更新的目錄.


E. 上傳 BIOS 檔:
   利用 sftp 上傳 scph1001.bin 檔案到 iPhone 的以下目錄內:
   /var/mobile/Media/ROMs/PSX/ 然後用 ssh 登入 iPhone 並調整權限:

   cd /var/mobile/Media/ROMs/PSX/
   chmod 755 scph1001.bin


F. 上傳遊戲檔:
   利用 sftp 上傳 diablo.bin 檔案到 iPhone 的以下目錄內:
   /var/root/Media/ROMs/PSX 然後用 ssh 登入 iPhone 並調整權限:
  
   cd /var/mobile/Media/ROMs/PSX/
   chmod 755 diablo.bin

G. 備註:
1. 我在 iPod Touch 3rd generation 上, 跑得不是很順暢, 遊戲畫面也太小了,
   有時還會有輕微的爆音, 希望以後有機會可以在 iPad 上試試.

2. 遊戲的簡易圖示說明:

(1).
按下桌面 PlayStation 的AP圖示後, 會出現以下的主選單,
    最下方有 4個 Tab; 最左邊的 Browse 可以瀏覽到你上傳的遊戲,
    通常用來玩新遊戲時會點選.


(2).  Recent Tab 記錄著最近執行過的檔名.


(3). Saves Tab 存放著, 經過執行 "Save [Currently Loaded]" 或
    "Save State To New File" 產生的檔案, 可以用來快速載入遊戲,
    不過較佔儲存空間.


  用 ssh 登入 iPod Touch 後, 可以在 /var/mobile/Media/ROMs/PSX/ ,
  看到產生的檔案.


(4). Options Tab 提供遊戲額外的設定, 其中的 Autosave 應該就是讓 user
     選擇是否要自動存檔, 預設為: 否.


(5). 一開始執行遊戲時, 需要選擇畫面的方向(直的或橫的),
     以及有無聲音.


直向的遊戲: (建議使用, 如下所示)
優點: 操作界面與遊戲界面分開, 不會相互干擾到.
缺點: 遊戲畫面較小.


橫向的遊戲: (如下所示)
優點: 遊戲畫面較大.
缺點: 操作界面與遊戲界面重疊, 會相互干擾到.
        (特別是在做設定時, 如存檔)


(6). press start to continue:
    a. start 鍵是在 X 鍵的左下角那個突起的小圓點.
    b. 它左邊的小圓點應該是 select 鍵.
    c. 最左下角的返回圖示, 可以作 Save [Currently Loaded],
       Save State To New File 或 Quit To Menu.



(7). 按下 start 後的選單.


(8). 進入 options > controller setup 看看遊戲操作方式:
    攻擊: X
    動作: 方塊
    施展魔法: 三角
    裝備魔法: O
    開啓魔法書: L2
    自動地圖切換: R2
    快速補血: L1
    快速補法: R1


(9). 按 三角(back), 回上個選單, 選擇 new game;
    目前好像只能選擇單人玩.


(10). 選戰士.


(11). 命名:


(12). 難度: 一般


(13). 進入遊戲, 此時:
      a. X 鍵 左下角為 paused
      b. paused 鍵左邊為 select

(14). 按 select 鍵進入:


(15). 選擇: game speed, 可將遊戲速度調快.


(16). 存檔功能




(17). 進入地下層


(18). This is diablo .... 這就是 戴艾波




2010年11月18日 星期四

iPhone 開發筆記18: cocos2d MultipleTouch

A. 新增 cocos2d Application Project: boardGame5

B. HelloWorldScene.h
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"

// HelloWorld Layer                 //@add
@interface HelloWorld : CCLayer <CCTargetedTouchDelegate>

// returns a Scene that contains the HelloWorld as the only child
+(id) scene;

@end

-----------------------------------------------------------------------------------------------

C. boardGame5AppDelegate.m

- (void) applicationDidFinishLaunching:(UIApplication*)application
{
    CC_DIRECTOR_INIT();
   
    //@add
    // Init the window
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   
    //@add
    [window setUserInteractionEnabled:YES]; 
    [window setMultipleTouchEnabled:YES];
    ....
}

-----------------------------------------------------------------------------------------------

D. HelloWorldScene.m

// on "init" you need to initialize your instance
-(id) init
{
    // always call "super" init
    // Apple recommends to re-assign "self" with the "super" return value
    if( (self=[super init] )) {
       
        //@add
        self.isTouchEnabled = YES;  
        // @add
        [[CCTouchDispatcher sharedDispatcher]
         addTargetedDelegate:self priority:0 swallowsTouches:YES];
    ....
    }
    return self;
}


// @add
-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent*)event
{   
    CGPoint touchLocation=[touch locationInView:[touch view]];
    touchLocation=[[CCDirector sharedDirector] convertToGL:touchLocation];
    CGPoint nodePosition = [self convertToNodeSpace: touchLocation];
   
    int t_x = nodePosition.x;
    int t_y = nodePosition.y;    

    ....
   
    return YES;
}

2010年11月17日 星期三

iPhone 開發筆記17: cocos2d 圖片動畫效果

A. 新增 cocos2d Application Project.

B. 在 HelloWorldScene.m:
-(id) init
{
    // always call "super" init
    // Apple recommends to re-assign "self" with the "super" return value
    if( (self=[super init] )) {
        ....
        // 從左上角往右下角切割圖檔
        //CCSprite *sprite = [CCSprite spriteWithFile:@"Icon.png" rect:CGRectMake(20, 20, 57, 57)];

        CCSprite *sprite = [CCSprite spriteWithFile:@"Icon.png"]; // source size
        sprite.position = ccp(240, 50);
   
        /* 動畫效果: case01 -> normal */
        id action =
        // (1). CCJumpTo: 跳躍至絕對座標
        // [CCJumpTo actionWithDuration:2 position:ccp(300,0) height:50 jumps:4]; // -> 300, 0

        // (2). CCJumpBy: 跳躍至相對座標(以原始座標累加距離的跳躍)
        // [CCJumpBy actionWithDuration:2 position:ccp(300,0) height:50 jumps:4]; // -> 540, 50

        // (3). CCRotateTo: 旋轉
        // [CCRotateTo actionWithDuration:2 angle:180];
       
        // (4). 淡入
        // [CCFadeIn actionWithDuration:2];
       
        // (5). 淡出
        // [CCFadeOut actionWithDuration:2];
       
        // (6). 移至絕對座標
        // [CCMoveTo actionWithDuration:2 position: ccp(100, 100)];
       
        // (7). 移至相對座標
        // [CCMoveBy actionWithDuration:2 position: ccp(100, 100)]; // 340, 150

        // 混合動作
        // (8). CCSequence: one by one (連續動作)
        [CCSequence actions:
        // [CCRotateTo actionWithDuration:2 angle:180], // ---> error with reverse happend?
        [CCRotateBy actionWithDuration:2 angle:180],
        [CCJumpBy actionWithDuration:2 position:ccp(300,0) height:50 jumps:4],
        [CCFadeOut actionWithDuration:2],
        [CCCallFunc actionWithTarget:self selector:@selector(callback1)],
        nil];

        // 混合動作
        // (9). CCSpawn: do it the sametime (同時動作)
        /*
         [CCSpawn actions:
         [CCRotateTo actionWithDuration:2 angle:180], // ---> error with reverse happend?
         [CCRotateBy actionWithDuration:2 angle:180],
         [CCJumpBy actionWithDuration:2 position:ccp(300,0) height:50 jumps:4],
         [CCFadeOut actionWithDuration:2],
         [CCCallFunc actionWithTarget:self selector:@selector(callback1)],
         nil];
         */

        /* 動畫效果: case02 -> repeat */
        // (10). CCRepeatForever: 無限重複執行
        // CCRepeatForever *repeat = [CCRepeatForever actionWithAction:action];
       
        // (11). CCRepeat: 重複執行
        // CCRepeat *repeat = [CCRepeat actionWithAction:action times:2];

       
/* 動畫效果: case03 -> reverse */
        // (12). reverse: 反向執行
        // id t_reverse = [CCSequence actions:action, [action reverse], nil];

        // runAction
        // case 1: for normal
        [sprite runAction:action];
       
        // case 2: for repeat
        // [sprite runAction:repeat];
       
        // case 3: for reverse
        // [sprite runAction:t_reverse];

        [self addChild: sprite];
    }
    return self;
}


- (void) callback1
{
    CCLabel *labelEnd = [CCLabel labelWithString:@"Game Over" fontName:@"Marker Felt" fontSize:64];
   
    CGSize size = [[CCDirector sharedDirector] winSize];
   
    // position the label on the center of the screen
    labelEnd.position = ccp( size.width /2 , size.height/2 - 100 );
   
    [self addChild: labelEnd];
}

iPhone 開發筆記16: cocos2d 安裝與移除

A. cocos2d 官網: http://www.cocos2d-iphone.org/

B. 下載穩定版本(Stable version): http://www.cocos2d-iphone.org/download

C. 安裝方式:
   1. 解壓縮下載的檔案: cocos2d-iphone-0.99.4.tar.gz
   2. 在 Terminal 下, 到解壓縮目錄內執行:
      $ cd cocos2d-iphone-0.99.4
      $ sudo ./install-templates.sh
   3. 可以看到以下的安裝訊息:
      Installing cocos2d template:
      /Library/Application Support/Developer/Shared/Xcode/Project Templates/cocos2d 0.99.4/cocos2d Application/

      Installing cocos2d + box2d template:
      /Library/Application Support/Developer/Shared/Xcode/Project Templates/cocos2d 0.99.4/cocos2d Box2d Application/

      Installing cocos2d + chipmunk template:
      /Library/Application Support/Developer/Shared/Xcode/Project Templates/cocos2d 0.99.4/cocos2d Chipmunk Application/

      creating destination directory:
      /Library/Application Support/Developer/Shared/Xcode/File Templates/cocos2d 0.99.4/

D. 移除方式:
   刪除: C 安裝後所產生的目錄即可.

iPhone 開發筆記15: 單點觸控與多點觸控

// 單點觸控
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint location = [touch locationInView:touch.view];
    // location.x
    // location.y
}

----------------------------------------------------------------------------------------------------

// 多點觸控
A. 新增一個 Window-based Application Project: multiTouch

B. 新增繼承 UIView 的 Class: TouchView
----------------------------------------------------------------------------------------------------

C. multiTouchAppDelegate.h
#import <UIKit/UIKit.h>
//@add
#import "TouchView.h"

@interface multiTouchAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

----------------------------------------------------------------------------------------------------

D. multiTouchAppDelegate.m

#import "multiTouchAppDelegate.h"

@implementation multiTouchAppDelegate

@synthesize window;

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {   
   
    // Override point for customization after application launch.
    //@add
    [application setStatusBarStyle:UIStatusBarStyleBlackOpaque]; // 改變狀態列背景

    TouchView *touchView = [[[TouchView alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    [window addSubview:touchView];   
   
    [window makeKeyAndVisible];
   
    return YES;
}

----------------------------------------------------------------------------------------------------

E. TouchView.h

#import <UIKit/UIKit.h>

@interface TouchView : UIView {
    //@add
    CGPoint touch1;
    CGPoint touch2;
    double unitDistance;
    double initialDistance;
}

//@add
- (double)getDistance:(CGPoint)fromPoint toPoint:(CGPoint)otherPoint;

@end

----------------------------------------------------------------------------------------------------
F. TouchView.m

#import "TouchView.h"

@implementation TouchView

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        // Initialization code
        //@add
        [self setMultipleTouchEnabled:YES];
    }
    return self;
}

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    NSArray *allTouches = [touches allObjects];
    int count = [allTouches count];
    if (count > 0)
        touch1 = [[allTouches objectAtIndex:0] locationInView:self];
    if (count > 1)
    {
        touch2 = [[allTouches objectAtIndex:1] locationInView:self];
        initialDistance = [self getDistance:touch1 toPoint:touch2];
        NSLog(@"initialDistance:%f",initialDistance);
    }
    [self setNeedsDisplay]; // 強制更新畫面: invoke -> drawRect
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   
    NSArray *allTouches = [touches allObjects];
    int count = [allTouches count];
    if (count > 0)
        touch1 = [[allTouches objectAtIndex:0] locationInView:self];
    if (count > 1)
    {
        touch2 = [[allTouches objectAtIndex:1] locationInView:self];
        unitDistance = [self getDistance:touch1 toPoint:touch2];
        NSLog(@"unitDistance:%f",unitDistance);
    }
    [self setNeedsDisplay];
// 強制更新畫面: invoke -> drawRect
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    if (initialDistance > unitDistance) {
        // 縮小
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"multiTouch" message:@"Your touch is close to eachother" delegate:nil cancelButtonTitle:@"Yep, I did." otherButtonTitles:nil];

 
        [alert show];
        [alert release];   
    }
    else if (initialDistance < unitDistance){
        // 放大
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"multiTouch" message:@"Your touch is distant from eachother" delegate:nil cancelButtonTitle:@"Yep, I did." otherButtonTitles:nil];

 
        [alert show];
        [alert release];
    }
}


// 二點距離: 平方相加, 開根號
- (double)getDistance:(CGPoint)fromPoint toPoint:(CGPoint)otherPoint
{
    double deltaX = otherPoint.x - fromPoint.x;
    double deltaY = otherPoint.y - fromPoint.y;
    return sqrt(pow(deltaX, 2) + pow(deltaY, 2));
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    //@add Line
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 2);
    CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
    CGContextMoveToPoint(context,touch1.x,touch1.y);
    CGContextAddLineToPoint(context,touch2.x,touch2.y);
    CGContextStrokePath(context);
}

- (void)dealloc {
    [super dealloc];
}

@end

2010年11月11日 星期四

iPhone 開發筆記14: 背景音樂

A. 在專案的 "Frameworks" 新增以下的 framework: CoreAudio.frameworkAudioToolbox.framework .

B. 在專案的 "Groups & Files" 新增一個 Group: "Audio", 並新增以下的檔案: GBMusicTrack.hGBMusicTrack.m

//
//  GBMusicTrack.h
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

//#import <Cocoa/Cocoa.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>

#define NUM_QUEUE_BUFFERS    3

@interface GBMusicTrack : NSObject
{
    AudioFileID                       audioFile;
    AudioStreamBasicDescription     dataFormat;
    AudioQueueRef                   queue;
    UInt64                            packetIndex;
    UInt32                            numPacketsToRead;
    AudioStreamPacketDescription   *packetDescs;
    BOOL                             repeat;
    BOOL                             trackClosed;
    AudioQueueBufferRef            buffers[NUM_QUEUE_BUFFERS];
}

- (id)initWithPath:(NSString *)path;
- (void)setGain:(Float32)gain;
- (void)setRepeat:(BOOL)yn;
- (void)play;
- (void)pause;

// close is called automatically in GBMusicTrack's dealloc method, but it is recommended
// to call close first, so that the associated Audio Queue is released immediately, instead
// of having to wait for a possible autorelease, which may cause some conflict
- (void)close;

extern NSString    *GBMusicTrackFinishedPlayingNotification;

@end

------------------------------------------------------------------------------------------------------

//
//  GBMusicTrack.m
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

#import "GBMusicTrack.h"

static UInt32 gBufferSizeBytes = 0x10000; // 64k

NSString *GBMusicTrackFinishedPlayingNotification
= @"GBMusicTrackFinishedPlayingNotification";

@interface GBMusicTrack (InternalMethods)

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer);
- (void)callbackForBuffer:(AudioQueueBufferRef)buffer;
- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer;

@end

@implementation GBMusicTrack

#pragma mark -
#pragma mark GBMusicTrack

- (void)dealloc
{
    [self close];
    [super dealloc];
}

- (void)close
{
    // it is preferrable to call close first, before dealloc if there is a problem waiting for
    // an autorelease
    if (trackClosed)
        return;
    trackClosed = YES;
    AudioQueueStop(queue, YES);
    AudioQueueDispose(queue, YES);
    AudioFileClose(audioFile);
}

- (id)initWithPath:(NSString *)path
{
    UInt32        size, maxPacketSize;
    char        *cookie;
    int            i;
   
    if(!(self = [super init])) return nil;
    if (path == nil) return nil;
   
    // try to open up the file using the specified path
    if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, kAudioFileCAFType, &audioFile))
    {
        NSLog(@"GBMusicTrack Error - initWithPath: could not open audio file. Path given was: %@", path);
        return nil;
    }
   
    // get the data format of the file
    size = sizeof(dataFormat);
    AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);
   
    // create a new playback queue using the specified data format and buffer callback
    AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);
   
    // calculate number of packets to read and allocate space for packet descriptions if needed
    if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)
    {
        // since we didn't get sizes to work with, then this must be VBR data (Variable BitRate), so
        // we'll have to ask Core Audio to give us a conservative estimate of the largest packet we are
        // likely to read with kAudioFilePropertyPacketSizeUpperBound
        size = sizeof(maxPacketSize);
        AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
        if (maxPacketSize > gBufferSizeBytes)
        {
            // hmm... well, we don't want to go over our buffer size, so we'll have to limit it I guess
            maxPacketSize = gBufferSizeBytes;
            NSLog(@"GBMusicTrack Warning - initWithPath: had to limit packet size requested for file path: %@", path);
        }
        numPacketsToRead = gBufferSizeBytes / maxPacketSize;
       
        // will need a packet description for each packet since this is VBR data, so allocate space accordingly
        packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);
    }
    else
    {
        // for CBR data (Constant BitRate), we can simply fill each buffer with as many packets as will fit
        numPacketsToRead = gBufferSizeBytes / dataFormat.mBytesPerPacket;
       
        // don't need packet descriptsions for CBR data
        packetDescs = nil;
    }
   
    // see if file uses a magic cookie (a magic cookie is meta data which some formats use)
    AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);
    if (size > 0)
    {
        // copy the cookie data from the file into the audio queue
        cookie = malloc(sizeof(char) * size);
        AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);
        AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);
        free(cookie);
    }
   
    // allocate and prime buffers with some data
    packetIndex = 0;
    for (i = 0; i < NUM_QUEUE_BUFFERS; i++)
    {
        AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
        if ([self readPacketsIntoBuffer:buffers[i]] == 0)
        {
            // this might happen if the file was so short that it needed less buffers than we planned on using
            break;
        }
    }
    repeat = NO;
    trackClosed = NO;
    return self;
}

- (void)setGain:(Float32)gain
{
    AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}

- (void)setRepeat:(BOOL)yn
{
    repeat = yn;
}

- (void)play
{
    AudioQueueStart(queue, nil);
}

- (void)pause
{
    AudioQueuePause(queue);
}

#pragma mark -
#pragma mark Callback

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
    // redirect back to the class to handle it there instead, so we have direct access to the instance variables
    [(GBMusicTrack *)inUserData callbackForBuffer:buffer];
}

- (void)callbackForBuffer:(AudioQueueBufferRef)buffer
{
    if ([self readPacketsIntoBuffer:buffer] == 0)
    {
        // End Of File reached, so rewind and refill the buffer using the beginning of the file instead
        packetIndex = 0;
        [self readPacketsIntoBuffer:buffer];
       
        // if not repeating then we'll pause it so it's ready to play again immediately if needed
        if (!repeat)
        {
            AudioQueuePause(queue);
           
            // we're not in the main thread during this callback, so enqueue a message on the main thread to post notification
            // that we're done, or else the notification will have to be handled in this thread, making things more difficult
            [self performSelectorOnMainThread:@selector(postTrackFinishedPlayingNotification:) withObject:nil waitUntilDone:NO];
        }
    }
}

- (void)postTrackFinishedPlayingNotification:(id)object
{
    // if we're here then we're in the main thread as specified by the callback, so now we can post notification that
    // the track is done without the notification observer(s) having to worry about thread safety and autorelease pools
    [[NSNotificationCenter defaultCenter] postNotificationName:GBMusicTrackFinishedPlayingNotification object:self];
}

- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer
{
    UInt32        numBytes, numPackets;
   
    // read packets into buffer from file
    numPackets = numPacketsToRead;
    AudioFileReadPackets(audioFile, NO, &numBytes, packetDescs, packetIndex, &numPackets, buffer->mAudioData);
    if (numPackets > 0)
    {
        // - End Of File has not been reached yet since we read some packets, so enqueue the buffer we just read into
        // the audio queue, to be played next
        // - (packetDescs ? numPackets : 0) means that if there are packet descriptions (which are used only for Variable
        // BitRate data (VBR)) we'll have to send one for each packet, otherwise zero
        buffer->mAudioDataByteSize = numBytes;
        AudioQueueEnqueueBuffer(queue, buffer, (packetDescs ? numPackets : 0), packetDescs);
       
        // move ahead to be ready for next time we need to read from the file
        packetIndex += numPackets;
    }
    return numPackets;
}

@end

------------------------------------------------------------------------------------------------------

C. 在 xxxViewController.m 檔:

   #import "GBMusicTrack.h"


D. 在 xxxViewController.m 檔, 修改 viewDidLoad():
- (void)viewDidLoad {
    [super viewDidLoad];
    ....
    //background music
    GBMusicTrack *song = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"streetFighter" ofType:@"mp3"]];

    [song setRepeat:YES];
    [song play];
}