顯示具有 iPhone 標籤的文章。 顯示所有文章
顯示具有 iPhone 標籤的文章。 顯示所有文章

2012年8月23日 星期四

讓空白的UITableViewCell消失吧~

有的時候總會小失落自已的app怎麼就是少人家一點什麼...
阿~你看看左邊醜陋的Table一眼就知道是系統預設樣式...



















明明只有三筆資料為什麼下面還有一條條的分隔線

想看看精緻版本嗎?右邊接著看下去...
右邊的如果你仔細去看他的原始碼,他還區分成最上面第一個cell,中間的,跟最下面的cell

換句話說,他取消了原本格線的風格,透過"美術"方式來達成
如果今天沒有"美術"的奧援要怎麼辦呢?

只有一個section時:
1
2
3
4
5
6
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    if ([self numberOfSectionsInTableView:tableView] == (section + 1)){
        return [[UIView new] autorelease];
    }       
    return nil;
}


有一個以上的section可以用:
1
2
3
4
5
6
7
8
- (float)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
     // This will create a "invisible" footer
     return 0.01f;
 }
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    // To "clear" the footer view
    return [[UIView new] autorelease];
}

完成圖如下~





















資料來源: stackoverflow & cocoawithlove

2014/07/04 UPDATE:

這也是一個不錯的方法.
1
tableView.tableFooterView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];

2011年6月27日 星期一

Damn! Where is my Personal Hotspot!!!

If it is not about updating to iOS4.3,
If it is not about thousands reset methods,
If it is not about carrier's network...

Checkout if you had ever downloaded Onavo

無論我怎麼還原網路設定,重啟iPhone,連升級iOS5 beta都試過了
就是沒有還我個人熱點阿阿阿阿~

Setting -> General -> Profiles -> Remove Onavo provisioning profile
用力的砍掉Onavo的profile吧,我似乎看到他的profile有動到APN
呼呼~搞了好幾個星期...真是氣死我了!!

2011年4月28日 星期四

如何讓手勢偵測在subview裡作用? (How to make UIGestureRecognizer work in subview?)

我們都知道UIGestureRecognizer只支援iOS3.2以上的環境
但是讀過Jesse的文章之後,我覺得我可以很放心的去使用這個API

網路上也很多這類的文章,詳細的原理可以參考官方文件
我僅針對我實作過程中所遇到的問題 貼上一些個人的解法跟筆記
在一陣copy paste過後,程式編譯似乎沒什麼問題,可是就是手勢沒反應

1
[self.imageView addGestureRecognizer:tapGestureRecognize];

因為人家的事件是發生在UIView上面…不是UIimageView

2. 那不指定self.view可以換成別的subview嗎?
當然可以阿,在接收到gesture時filter掉我們不接受的view或是特定範圍,
使用下面這個event function

1
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch

3. 喂~沒反應阿! gestureRecognizer: shouldReceiveTouch: 根本就沒有觸發進來阿
喔~我想您跟我的煩惱是一樣滴,因為沒花一些時間k文件才會出現觀念上的問題
UIGestureRecogizer根本就沒有委託來處理這樣的event,當然會遲遲等不到event的發生阿
所以呢?

所以開始貼一些code吧
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//.h
@interface showDigitNumberController : UIViewController


//.m
// Create the Swipe Gesture Recognizer
UISwipeGestureRecognizer *SwipeRecognizer = nil;
 
SwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                            action:@selector(mySwipeGestureRecognizer:)];
[SwipeRecognizer setDirection:(UISwipeGestureRecognizerDirectionUp)];
[self.view addGestureRecognizer:SwipeRecognizer];
// assign the delegate here
SwipeRecognizer.delegate = self;
[SwipeRecognizer release];
 
SwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                            action:@selector(mySwipeGestureRecognizer:)];
[SwipeRecognizer setDirection:(UISwipeGestureRecognizerDirectionDown)];
[self.view addGestureRecognizer:SwipeRecognizer];
[SwipeRecognizer release];
 
// Create Single Tap Gesture Recognizer
UITapGestureRecognizer *tapGestureRecognize = [[UITapGestureRecognizer alloc] initWithTarget:self
      action:@selector(myTapGestureRecognizer:)];
tapGestureRecognize.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapGestureRecognize];
[tapGestureRecognize release];

- (void)mySwipeGestureRecognizer:(UISwipeGestureRecognizer *)Sender {
 switch(Sender.direction){
   case UISwipeGestureRecognizerDirectionUp:
  // do something 
        break;
   case UISwipeGestureRecognizerDirectionDown:
  // do something 
        break;
   case UISwipeGestureRecognizerDirectionLeft:
  // do something 
        break;
   case UISwipeGestureRecognizerDirectionRight:
  // do something 
        break;
   default:
        break;
 }
}

- (void)myTapGestureRecognizer:(UITapGestureRecognizer *)Sender{
 // do something
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
    shouldReceiveTouch:(UITouch *)touch {
}


4. 誠如先前所講的,若是要讓指定的subview接受gesture的event就是在這個function裡實作
   網路上所提到的作法也是百百種

   //有將superview給filter掉的作法
   //UIView *gview; //This is the superview containing the buttons
   //if([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]])
   //     return !([touch.view isKindOfClass:[UIButton class]] && [touch.view
   //isDescendentOfView:gview]);


   //也有使用touch.view == menu1Btn || touch.view == menu2Btn …龐大if判斷式的作法
   //http://stackoverflow.com/questions/4556944/a-tips-for-testing-existence-of-subviews


    //就連官方sample code的作法也是if判斷的作法
    //if ((touch.view == segmentedControl) && (gestureRecognizer == tapRecognizer)) {
    //    return NO;
    //}
    //return YES;

    我的作法也是把不想接受到的gesture event的物件給填進去,不過沒有預期的效果
    那到底怎麼偵測一個subview的手勢呢?
    a. 首先我在xib裡面view底下又多加了一個UIView, 並且指定我要的範圍大小
    b. 在.h及.m裡將這個UIView的getter setter設定好,並確定xib的link有拉好
    c. 在原先指定addGestureRecognizer的view換成custom的這個就好了耶

2011年3月15日 星期二

iPhone 給我多國語系檔(.lproj),其餘免談

這篇筆記的內容"並不是"探討 怎麼建立一個多國語系檔
而是記錄iPhone上各國語系縮寫,以及讀取上的一些問題
所以大家可以按上一頁啦XD

由於多國語系的關係,我們可能需要知道使用者目前的語系設定
取得方法如下:
NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSArray* languages = [defs objectForKey:@"AppleLanguages"];
NSString* preferredLang = [languages objectAtIndex:0];
NSLog(@"LangCode: %@",preferredLang);

ISO-639的各國語言對應到iPhone上面的語言列表可以參考


但是,當我們用實機測試的時候一定會遇到窘境
嗄~suomi,dansk,hrvatski...
原來iPhone上的語言列表早就已經替換成該語言的文字了
這對一個"外國人"來說是一件挺痛苦的事情
參考一下 stackoverflow 的這篇文章吧!
ex: Swedish = svenska
Finnish = suomi
Danish = dansk
Croatian = hrvatski

事情還沒結束
看到沒,language code有pt, pt_PT
還有中文也是有分zh_CN, zh_TW
鬼打牆的情節緊接上演,
無論怎麼切換pt 與pt_PT,模擬器跟iPhone就是沒辦法切換
花了我好幾個小時的結論如下:
1. 如果語系檔的檔名設定有誤,iPhone預設值會是English
ex: ja.lproj誤設為jp.lproj

2. 如果語系相近,會以有相關的語系檔讀取為優先
ex: pt.lproj與pt_BR.lproj 無論設定是 português (Portugal)或是português
預設就是讀取pt.lproj

3. 如遇上述兩個問題,也確定已經更正好了
記得!!要先clean project 再重新編譯

可能還有人會對zh_CN, zh_TW,zh_Hans,zh_Hant
或是該用underline(_) 還是dash(-)符號有問題
可以參考另外一篇文章:
MacUknow

2011年2月22日 星期二

iPhone 發佈時遇到的小小warning

在我distribution的時候有遇到這麼個小小warning

warning: 'The Validate Built Product build setting was not enabled when building for Distribution.'

這是個滿貼心的warning,因為有了Validate Built Product這個檢查,
會初步幫忙開發者找到問題,避免在submit時發生問題.

參考網站有詳細的圖文介紹
我這裡僅以文字描述:P
1. 在xcode裡開啟project的內容(Get Info)
2. 切換到build tag
3. 搜尋Validate Built Product
4. checkbox確定要打勾勾

呵~勾完重新執行的結果,馬上就有icon的問題...

iPhone 發佈時遇到的 icon problem

這是我在作distribution時遇到的其中一個問題
Icon specified in the Info.plist not found under the top level app wrapper:
icon.png (-19007)

網路上滿多解答的,我參考了coco2d for iPhone上的討論

1. 首先將info.plist的Icon files欄位修改成CFBundleIconFiles
2. 然後新增 (名稱大小寫都照著取,即使是iPad project也一樣)
    Icon.png
    Icon@2x.png
    Icon-72.png
    Icon-Small.png
    Icon-Small-50.png
    Icon-Small@2x.png
3. 重新將project的resource file刪除再加入,以確保project有最新的icon

最後,還可以參考一下官網的guide
像57x57 pixels is for iPhone/iPod
114x114 pixels is for high resolution iPhone/iPod
72x72 pixels is for iPad
(我想以後可能還要提供72@2x的icon吧~)

2010年12月31日 星期五

iPhone error滿天飛 debug來跨年

有時error往往會在最關鍵的時候冒出來
(譬如說:我想過Christmas,我想去跨年...但是有些bug還沒解)
尤其是一些系統上的問題或是鬼打牆的小細節

首先登場的是
"No provisioned iPhone OS device is connected"

明明就接好好的device,你跟我說沒有provision
要解決這奇怪的現象,
可以試試在Organizer把provisioning裡的files通通delete讓他重裝

接著登場的是
"The program being debugged is not being run"

很無厘頭的error,但是解法跟上面的症狀是一樣的
進入Organizer把provisioning profiles清一清
還不行的話,從apple developer重新下載吧

最後登場的是
"CFPropertyListCreateFromXMLData(): 
Old-style plist parser: missing semicolon in dictionary."

話說在我要提供多國語言需求下,建立了lproj的strings file
奇怪的是出了這個error還跟我說 Unexpected character at line 1
第一行很ok阿,哪有什麼問題!也有semicolon阿
然而在我浪費了約1個小時的青春尋找問題
最後....在文件的很後面 很後面 的某一行.....真的少了一個semicolon...@#$%
所以at line 1是騙人的,semicolon是一定有漏掉的地方
睜大眼睛快找找吧~

Happy New Year & Happy Debugging  lol

2010年12月22日 星期三

iPhone 我後悔了~ performSelector 停下來阿

雖然短的跟一行文沒什麼兩樣
但是之前沒記下來,那就開個版面寫一下

Tips:
故事是這樣發生的...
下面的動作是在touch event觸發之後會去執行的
[self performSelector:@selector(myFunc) 

           withObject:nil 

           afterDelay:delay];

但是,在這個延遲的期間又去觸發touch event...
已經下達的performSelector是不會理會的
使用者可能會有UI行為不一致的感覺

因此,在下達performSelector若能把前一次 未執行完的動作給停掉
會是比較理想的作法,cancel 方法如下:
[NSObject cancelPreviousPerformRequestsWithTarget:self

                                       selector:@selector(hideMsg)

                                           object:nil];


參考來源: stackoverflow
                iphonedevsdk

2010年12月13日 星期一

iPhone 更改專案名稱 (update:完整版)

剛起始一個專案的時候
我有個很糟糕的習慣就是取了爛名字
什麼xxxtest阿,xxxdemo等等...
等真的要release的時候不免被人家念一頓

其實要修改 project name也不難
1. 找到專案的info.plist
2. 找到"Bundle display name"的欄位
3. 將原本 ${PRODUCT_NAME} 改成想要的名稱

如果遇到
the program being debugged is not being run
的error message也不要慌,
shift + command + K 把專案給clean重編就好了

參考資料:stackoverflow
              iphonedevsdk

update: 2011/05/10
嗯~原本的作法算是治標不治本
如果精明一點可以發現透過organizer看到的device log都還是舊名稱
雖然之前就有看到網路上有教學
不過好害怕整個project改爛了就很好笑了

anyway,凡是總有第一次,先備份起來總不會失控吧!
更改專案名稱真的沒有這麼恐怖,給我5分鐘就可以搞定的:)

Let's change the project name completely
1. Copy all files under old project directory to a new one.
    拷貝所有舊專案資料夾底下的file到新的資料夾


2. Rename "old_name.xcodeproj" to "new_name.xcodeproj" then open it.
    將舊的xcodeproj檔名改成想要的新名稱,然後用xcode打開


3. Check if project with the new name under Group&Files pane and Target pane
    修改並確定project name及Targets的名稱是新的


4. Clean the project by shift+command+K and quit xcode that's it, 
    don't rename any other file name at this state.
    清除專案,別急著將其他檔案更名,拜託,我在這步失敗過:P


5. Right click on the "new_name.xcodeproj" and choose "show package content",
    open "project.pbxproj" by xcode.
    回到資料夾在xcodeproj檔案上面按右鍵 選顯示套件內容 打開project.pbxproj


6. Replace all "old_name" with "new_name" save and quit
    用new_name取代所有的old_name, 儲存後離開


7. Open the project again, and rename "old_name" to "new_name" in entire project,
    the red lost referenced files should disappear.
    再次開啟xcode將 project中出現的所有"old_name"用"new_name"取代


8. Open "new_nameViewController.xib" and "MainWindow.xib" to correct the class
     identity.
    開啟"new_nameViewController.xib"與"MainWindow.xib"更改delegate, controller的
    class identity欄位


9. 重新編譯,完成!!

我的作法並無使用console,希望對大家有幫助

參考資料:WebObject blog

iPhone POST url遇上跳脫字元的處理方式

寫過網路程式的人都知道
POST url之前最好要將內容encode過
不然decode過程中一定會有判讀錯誤的問題
ex: user=123&abc&pass=qwe=asd
encode後送出 user=123%26abc&pass=qwe%3Dasd
原本以為下面的encoding method用得好好的
沒想到是一個大bug

[unencodedObj stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];


所以請大家改用

(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                 (CFStringRef)unencodedObj,
                                                    NULL,
                             CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                          kCFStringEncodingUTF8 );


參考來源: simonwoodside
                iphonedevsdk

2010年11月23日 星期二

iPhone UIImage的圓角效果及橫向splash screen

原本以為這兩件事情是有難度的,
還好是自己嚇自己~
參考看看囉:)

UIImage圓角效果
1. 找到左側列表的Frameworks,右鍵加入existing frameworks
QuartzCore.framework

2. 記得在.m的檔案也加入
#import

3. 設定圖片來源的同時也設定圓角效果的屬性
imageView.layer.cornerRadius = 5.0;
imageView.layer.masksToBounds = YES;

//And to add a border:
imageView.layer.borderColor = [UIColor lightGrayColor].CGColor;
imageView.layer.borderWidth = 1.0;
imageView.image = [UIImage imageNamed:@"image.png"];

比較一下有無圓角的效果吧~

載入橫向的splash screen
根據網路上的結論是…辦不到!!
ㄜ~程式上是辦不到,不過圖片可以動手腳
iPhone的Default.png請轉90度,變成320x480
iPad的Default.png請轉90度,變成768x1024


資料來源:
           iPhoneSDKPro
           iphonedevsdk

2010年11月8日 星期一

iPhone can't change language of Xcode

寫程式或許會遇到丈二金剛摸不著頭緒的時候
debug可能會嚐到眼花撩亂頭暈目眩的情形
但是當你碰上Xcode的build setting語言改不掉


一開始會很火大…
阿不就系統設定(system preference)裡面
語言與文字選項(language and text)順序調整一下
再不然,狠一點從list上面移除

再來會很無奈
重開Xcode,登出登入,重開機,無濟於事阿!!!

最後會掉入絕望的深淵
只能看著像是外星來的語言
冒著error,warning無情的嘲弄
猜測屬性的設定,這邊試試看,那邊試試看

不是我歧視日文,而是真的完全看不懂阿
不能這樣溫良恭儉讓下去了~
好不容易我找到了大絕招,這樣做就對了!

1. 首先找到Developer -> Application -> Xcode
2. Ctrl + click -> Show Package Contents -> Resource
3. 把那個叫Japanese.lproj 整個砍了吧!!

重新開啟Xocde,迎接清爽的build setting page吧 :)

資料來源: macosx.com

2010年11月3日 星期三

iPhone 自訂navigation bar全攻略 包含按鈕,高度及圖片背景

iPhone的UI真的有很多小細節,我想也累積一些東西了就po上來吧.
自製化的navigation bar到底有多彈性呢?
我想改變navigation bar上面按鈕的樣式,可以!
想改變navigation bar的高度,可以!
想給navigation bar上個圖片,可以!
那..那navigation bar上按鈕的位置呢? 還是可以!!

如果技巧太鼈三就包涵一下啦:P
navigation bar中的按鈕參照UIBarButtonItemStyle所定義項目少了custom這項
不過可以透過initWithCustomView的方式建立自訂的按鈕
詳細作法如下:
1. 先建立一個UIButton,設定其大小位置以及各狀態對應的圖片,最後還要一個action的指向

UIButton * fooButton = [UIButton buttonWithType:UIButtonTypeCustom];

fooButton.bounds = CGRectMake(0, 0, 40.0, 40.0);

[fooButton setImage:[UIImage imageNamed:@"foo.png"] 

   forState:UIControlStateNormal];

[fooButton addTarget:self 

        action:@selector(fooFired:) 

     forControlEvents:UIControlEventTouchUpInside];

2. 接著new一個UIBarButtonItem後,加入navigationItem(這邊以右邊為例)

UIBarButtonItem *fooBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:fooButton];

 self.navigationItem.rightBarButtonItem = fooBarButtonItem;

 [fooBarButtonItem release];

(trick 1) 接下來講講navigation bar的高度怎麼改變,
網路上可以找到一些方法,不過直接改變高度似乎不可行(至少我自己試過不行)
self.navigationController.navigationBar.frame.size.height
另外一個就正常啦,set自己想要的大小位置

[self.navigationController.navigationBar 
setFrame:CGRectMake(0, 0, self.view.width, height)];

(trick 2) 給navigation bar上個圖片也不會太難
1. 首先new一個UIImageView

UIImageView * banner = [[UIImageView alloc]

initWithFrame:CGRectMake(0, 0, width, height)];

2. 指定這個ImageView載入的圖片

banner.image = [UIImage imageNamed:@"banner.png"];

3. 透過addsubview的方式加進navigation bar(這裡額外指定layer在最底層)

[self.navigationController.navigationBar insertSubview:banner atIndex:0];

 self.navigationController.titleView = banner;  上面這個作法會讓titleView蓋過title的字 (very important)

咳~醜媳婦終究還是要見公婆
改變navigation bar按鈕位置的方法有點投機
如果想要達到下圖的樣子要善用trick1跟trick2

首先在我們改變navigation bar高度的過程中
一定會發現按鈕的位置都偏下,就像下面的圖

沒關係,我們在navigation bar上面放一個高度較高,size較大的圖片蓋過去
如此一來navigation bar上面的按鈕怎麼看都會在中間啦!

順帶一提:
自訂的navigation bar剛開始跑起來都還好端端的,
但是隱藏過後又要顯示時,
按鈕設定全跑了,navigation bar高度也會回復預設值.
後來解決的方法還滿暴力的,
就是要再度顯示之前,再設定一次navigation bar的frame size

[self.navigationController.navigationBar 
setFrame:CGRectMake(0, 0, width, height)];
[self.navigationController setNavigationBarHidden:NO 
                                       animated:NO];

Update:
如果rotate會導致navigation bar上面的button位置跑掉,
表示其高度被設定回預設值,
解決的方法也是在rotate完成後,再設定一次navigation bar的frame size

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {

  //set your frame size here

}

             stackoverflow - BarButtonItem

2010年10月13日 星期三

iPhone/iPad start apps with specific orientation and device detection

好久沒po筆記了,只能說俺的惰性實在是太容易復發了
一般來說iPhone跟iPad一執行程式起來都是直立狀態
(PortraitOrientation)

如果想指定啟動後就是橫著的方向,設定的方式如下:
1. 因為是app一開始執行就要做的動作,
   所以設定也是在一開始的ViewController裡面
    {ProjectName}ViewController.m  -->
    - (BOOL)shouldAutorotateToInterfaceOrientation:
            (UIInterfaceOrientation)interfaceOrientation

2. 修改並指定interfaceOrientation朝左或右
   return YES;
   return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
   or
   return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);

   補充說明一下 return YES的作法:
   device會參考plist中的Supported interface orientations欄位,
   有指定到的方向device會自己做旋轉動作.

   另外,UIInterfaceOrientationLandscape與UIDeviceOrientationLandscape
   的方向是剛好相反的(這特性可以被作為一些判斷依據)

3. 在參考資料的過程中,有這樣的需求是程式在iPhone跟iPad起始的方向要不同
   一樣是在原處動手腳,加上一些判斷式就能夠分辨device種類了.
   BOOL iPad = NO;
#ifdef UI_USER_INTERFACE_IDIOM
   iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
#endif
   if (iPad) {
      // 條件成立表示device為iPad,執行環境iOS3.2以上
   } else {
      // 條件不成立有兩種可能1.device是iPhone或iPod
      // 2.執行環境iOS3.2以前版本不支援UI_USER_INTERFACE_IDIOM
   }

有興趣可以看一下UI_USER_INTERFACE_IDIOM在UIDevice.h的定義
目前我執行起來沒有什麼問題~
網路上有很多討論,如果是universal apps又是another story了:)

很開心在網路上開始看到有人site我的文章了耶~
但是可以請好心的註明一下出處嗎?

參考資料: JEFF LAMARCHE's blogger
               UIDevice Class Reference
               Ole Begemann's blogger
               Sangraal at Programbles
               BillyGiggles at iphonedevsdk


2011/01/03 補充:
iOS version detection

既然有偵測device 型號的方法
iOS版本的偵測當然也不能缺席

#ifdef __IPHONE_3_0
// works on >= iOS 3.0
#else
// works on < iOS 3.0
#end



float ver = [[[UIDevice currentDevice] systemVersion] floatValue];
if (ver >= 3.0) {
    // Only executes on version 3 or above.
}
資料來源:stackoverflow

2010年9月21日 星期二

iPhone Live audio streaming using AudioQueue


原本用AVAudioPlayer來播放一些音效感覺很方便,
可是要播放即時的音訊串流就沒輒了.
請見 Technical Q&A QA1634

好吧~那就自立自強捲起袖子… 趕緊google阿!
不過google大神給的答案實在很朦朧,
勉勉強強找到的殘破程式碼也有看沒有懂.

幸好天無絕人之路 :)
Apress出的iPhone cool projects這本書第六章有寫捏,
又有source code可以run,實在"揪甘心"阿.



官方網



















範例程式:下載

如果剛點開範例程式,跟我一樣有頭暈眼花的症狀.
那不用擔心,沒有這麼難,看完下面解釋說不定就懂了.

從架構上來講,一共分成三個部分:

1. AudioPlayer跟AudioRequest可以視作一個大項
    主要負責下達URLrequest,並將收到的data回傳給AudioStream
    另外就是擔任AudioStream的委託(delegate)去使用AudioQueue

2. AudioStream有三件主要的事情
    * 將一收到的data進行parse並得到StreamID
    * 有了StreamID之後會分成 propertyCallback與packetCallback
       先講propertyCallback, 其目的顧名思義就是要辨識Audio的屬性
       就想像成一段資料收進來,需要貼上標籤,註明格式,頻率等等
    * packetCallback會將收進來的stream分成數個packet
       然後請委託(delegate)幫忙執行AudioQueue的程式部分

3. AudioQueue會接受來自委託(delegate)的幾件事情
    * AudioQueueNewOutput會產生一個新的playback audio queue物件
    * AudioQueuePlay會開始播放AudioQueue裡頭的音訊
    * 上述兩項是延續自propertyCallback的部份,在packetCallback這邊
       因為有一整段stream的大小,所以透過AudioQueueAllocateBuffer給定
       適當的buffer size後,使用AudioQueueEnqueueBuffer一直塞buffer
       到audio queue就會播出我們要的串流音訊啦!


NOTE: 2011/01/10
這個範例被我拿來改用之後發現有memory allocation的問題
我原以為是我改壞了 才造成memory allocation不斷飆升的情況
最近花了一點時間修改一下 發現問題就出在AudioQueueAllocateBuffer
這個function不應該在data每次進來都做一次
如果好好看過Apple提供的playback示意圖
AudioQueueAllocateBuffer只會在AudioQueue宣告出來後指定好

講是這樣講啦~
實際測試了一下書中範例給的mp3
neilmix[dot]com[slash]book[slash]etude[dot]mp3
嗯…一切自我感覺良好,沒有問題
換成linear PCM格式的wav source…結果是"大崩壞"!
追了很久發現有一個叫packetDescription的structure資料全都是null,
難怪在memory copy時都會造成crash.

所以精華來啦~
1. 這邊mp3的audio stream是VBR格式,也就是說每個packet大小可能不一,
    因此packetDescription會特別記錄這些資訊.
    linear PCM的wav是CBR格式自然沒有這些資料,
    所以簡單的方法就是把packetDescriptions copy這段註解掉.

1
2
3
4
5
memcpy(outBufferRef-&gt;mAudioData, data.bytes, data.length);
outBufferRef-&gt;mAudioDataByteSize = data.length;
//memcpy(outBufferRef-&gt;mPacketDescriptions, packetDescriptions, 
  sizeof(AudioStreamPacketDescription) * packetCount); 
outBufferRef-&gt;mPacketDescriptionCount = packetCount;


2. 由於CBR格式中的bit rate是固定關係, AudioQueueEnqueueBuffer後兩個
    參數為0,跟NULL.

3. 總結來講,AudioQueue service是精隨.只要搞清楚流程,一邊餵進Audio基本資料
    另一邊allocate好audio queue把data依序塞進去,輕鬆播放串流音訊非難事!

參考資料: iOS Reference Library
                zonble
                cocoaChina
                stackoverflow (synthesize with CoreAudio)
                stackoverflow (iPhone combine audio files)    

歐~差點忘了,
在iOS4的simulator環境下跑,開始的時候都會頓一下
log會出現"AddRunningClient starting device on non-zero client count"訊息
雖然不會造成什麼大問題,不過這樣卡卡真的有點討厭就是了
網路上一片無解阿~討論串

2010年9月20日 星期一

iPhone get rid of UIColor

還在用UIColor嗎?嫌系統顏色不夠豐富嗎?
如果想使用hex value來自定色彩
網路上都有提供簡單作法:

1. 把下面整段marco複製到最前頭,define好RGB的範圍
#define UIColorFromRGB(rgbValue) [UIColor \
colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

2. 之後就有UIColorFromRGB()這個method可以用.例如:
[aButton setTitleColor:UIColorFromRGB(0x999999
              forState:UIControlStateNormal];

3. 就這樣,很簡單的~

   參考資料:iPhone Dev SDK
                 eric e. dolecki

2010年9月13日 星期一

iPhone 使用NSLog來顯示各種型態資料

我在xcode的開發過程中,
除了使用build and debug mode來逐一檢查參數的值
有時候也需要在build and run mode底下留一些訊息,
這時候一定會用到NSLog.
可是使用上若是沒注意資料型態,胡亂指定可是會導致crash的,
因為邪惡的compiler在編譯過程中根本不會有錯誤阿!

所以這邊筆記一下常用的資料型態取法
NSLog(@"%@",myobj);

%@         object
%d, %i     signed int
%u         unsigned int
%f         float/double

%x, %X    hexadecimal int
%o         octal int
%zu        size_t
%p         pointer
%e         float/double (in scientific notation)
%g         float/double (as %f or %e, depending on value)
%s         C string (bytes)
%S         C string (unichar)
%.*s       Pascal string 
(requires two arguments, pass pstr[0] as the first, 
pstr+1 as the second)

%c         character
%C         unichar

%lld       long long
%llu       unsigned long long
%Lf        long double

資料來源: cocoadev
               cocoachina

這篇出自cocoachina的精華文章更清楚喔

2010.12.08  補充顯示boolean參數方法
1
NSLog(@"Bool object is: %@ ",(boolobj ? @"YES" : @"NO"));

2011.10.21 補充顯示CGPoint的方法
1
NSLog(@"The point is: %@ ",NSStringFromCGPoint(point));

2010年9月7日 星期二

iPhone Executing Code in the Background (上)

為了徹底搞清楚iOS提供的多工有什麼規範
我居然很認真的看了官方的一些資料,還筆記起來
想說都筆記了,就分享一下,也歡迎大家多多指教
Executing Code in the Background


大部分的程式只要一進入background就會被suspend住而且不執行任何code。
除非我們特別去notify系統,蘋果有提供幾個option讓不同種類的行為在背景下執行。

application準備在background執行
1. application會先link against iOS4 
2. 接著在假定支援多工的情形下會自動implement 合適的method來處理轉換到背景
     的這件事情

application檢查到底有沒有支援多工
首先,程式有背景執行工作的特性不見得都能被iOS的device所支援
又如果,device根本就不支援背景執行工作或不是搭配iOS4及之後的版本,
那系統會用最早先的方式來處理我們的application.
具體來說,當我們的app要離開時,app會被終止並從memory中清掉,然後app的delegate會收到 applicationWillTerminate:的訊息

反正app在不支援多工的情形下應該要有所準備.
如果我們的code想要有支援背景工作但不去執行這部份,
可以用 UIDevice class中 multitaskingSupported的屬性來檢查到底有沒有支援多工.
如果app是要支援iOS4之前的系統那更要在執行背景工作前check這項屬性

Checking for background support on earlier version of iOS
UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:@selector(isMultitaskingSupported)])
   backgroundSupported = device.multitaskingSupported;

先宣告我們支援哪些背景工作
要支援一些類型的背景工作就必須在app執行前先宣告好.
app使用Info.plist中UIBackgroundModes的鍵值來宣告這類的事情,
這些鍵值的內容是包含了一個或以上的array,例如:

audio: app 可以在背景播放有聲內容.
location: app 可以讓使用者知道自己的所在位置.
voip: app 可以讓使用者使用internet 來打電話.

上述的這些value可以讓app知道什麼時間點該起來回應相對的事件.
然後iOS提供了兩種背景工作的方式

- app 可以要求系統提供一個extra的時間給這項工作
- app 預先排好要被傳遞的local notification時間點

背景狀態轉換的支援
基本上來講只要app是使用iOS4或更新的版本都會支援背景狀態的轉換
如果要很完整的support,或是說用比較嚴謹且正確的方式來支援
則需要確保在delegate中有實作下面幾個methods:

上述這些methods都是在app執行當中即時告知我們app將要轉換狀態了
雖然說有些method早在專案建立之初就已經有實作了,
只是這邊需要增加一些行為判斷來確保背景執行起來是很安全的.
例如: 當一個進入背景並且從記憶體被清除的app重新被launch起來時
我們可能會希望可以繼續之前離開時的狀態.
這個method來檢查有沒有額外的information或是其他已儲存的參數需要在app被喚起後把它restore回app的ui

成為多工感知且可靠的Application
在背景當中執行app一定是比前景中執行要來的限制多多,即使app沒有要在背景執行,
官方也建議了以下的原則:
  • Do not make any OpenGL ES calls from your code.
  • 只要createEAGLContext的物件或是牽涉到任何有關 OpenGL ES的drawing指令會讓app馬上被終止掉.
  • Cancel any Bonjour-related services before being suspended.
  • 當我們的app要移到背景但還沒被suspend之前,應該要在Bonjour那邊取消註冊並且關閉所有與網路服務有關的listing sockets.再說,一個被suspend的程式也不能對incoming 的服務要求有所回應.所以為了避免出現占著茅坑不拉屎的狀況出現,如果不在suspend之前自己關閉所有Bonjour服務,系統還是會自動幫我們關掉的.
  • Be prepared to handle connection failures in your network-based sockets.
  • 系統可能會在一些原因下將socket與我們的app拆開來.只要我們socket-based的程式有為一些類型的網路failure做準備,像是:lost signal或是網路轉換就不會導致任何不正常的問題.當我們要返回程式時使用socket遇到失敗可以很簡單的重新建立連線來解決.
  • Save your application state before moving to the background.
  • 在記憶體不足的情況下,背景執行中的程式會從記憶體中被清掉.被suspend的程式首當其衝,而且系統不會有任何的提示.所以app應該在進入背景執行前要備妥充足的資訊,記錄,參數好讓需要的時候可以重新組回來.在回復app的同時會讓使用者看到app主畫面重啟時的snapshot可以增加一致性.
  • Release any unneeded memory when moving to the background.
  • 當然背景執行的程式需要有充足的狀態訊息來達到快速重返前景執行的目的.但是有物件或是滿大的記憶體空間都沒有要再使用(例如沒用到的圖檔)就應該考慮在進入背景執行前把他們都release掉
  • Stop using shared system resources before being suspended.
  • 如果app有用到系統分享出來的資源(例如AddressBook),要在被suspend之前停止使用.這些系統分享出來的資源優先權會落到前景中的app,如果捨不得放掉這些資源系統還是會強迫中斷的.
  • Avoid updating your windows and views.
  • 雖然說在app背景執行中一直去操縱或是增加window,view object不會導致程式被終止.但是這類的事情應該要延後到app又重回前景再來處理.畢竟在背景的程式是看不到畫面,一直update是update心酸的嗎
  • Respond to connect and disconnect notifications for external accessories.
  • 如果app與外部的附件有communication,系統會自動發出斷線的通知給要進入背景的app.而app應該要註冊這個通知好用來關閉與外部附件的session.當app重回前景時也會藉由註冊的通知來給予重新連線的機會
  • Clean up resources for active alerts when moving to the background.
  • 為了在切換app間保留內文,系統並不會在app進入背景執行時自動關掉action sheet(UIActionSheet)或是alert view(UIAlertView).
  • 但app使用的是iOS 3.x或之前版本, alert view與action sheet仍會被關掉,所以app的cancellation handler就有機會在那邊執行了.這時開發者可以自己決定是要在進入背景執行前提供一個合適的清除行為(手動去cancel那些action sheet或是alert view)還是要把內文資訊給存下來,之後再回復那個view.(萬一在app被終止掉的狀況下)
  • Remove sensitive information from views before moving to the background.
  • 在app要進入背景執行前,系統會給app當下的window做一個快照,當app又要回復時會秀這個快照一下下.因此從applicationDidEnterBackground:這個method回復之前,我們因該針對敏感的使用者私人訊息加以隱藏或覆蓋.
  • Do minimal work while running in the background.
  • 在背景工作能執行的時間要比前景有更多的限制.如果我們的app在背景播放音樂或是在監控location的變化,那就要focus在重點上並把較不重要的事情挪後處理.app若是在背景中花很多時間在執行一些事情,系統會縮短給app的時間甚至通通給終止掉.

還有~等我又發瘋把它們認真看完吧...

    內容回應