2012年12月30日 星期日

NSString 的 property attribute

假設有某個class,他有一個NSString*的property時,那該怎麼設計這個property的attributes呢 ?

@interface MyClass : NSObject

@property /* ( ??? ) */ NSString* Name;

@end

首先,by default,所有的property都是atomic,也就是說會自動implement thread safety,所以如果只是UI端會用到的property的話,通常是要設定成nonatomic。

接著的問題是,在ARC底下,property的default都是strong,也就是說MyClass的Name會跟他的caller所pass進來的NSString*指向同一個地方。

由於NSString*是immutable,所以似乎strong看起來是個不錯的設定值。可是,由於caller可能會傳入一個NSMutableString (derived from NSString),如果是在這樣子的情形底下的話,就無法保證MyClass的Name的值不會被改到了 !

所以大部分的情形底下,應該都是要設定成copy !


@interface MyClass : NSObject

@property (nonatomic, copy) NSString* Name;

@end


2012年12月29日 星期六

XCode Shortcut Tips

XCode一直用的不是很順手,尤其時要切換檔案時好像除了透過Project Navigator之外別無其他方法,這樣子就一定得用到滑鼠才行。

今天找到了幾個hotkey,操作起來比較順手一些,所以把他記錄下來。

  1. Command+0:這個可以show/hide 左邊Project Navigator
  2. Option+Command+0:這個可以show/hide右邊的Utility window
  3. Ctrl+5:Show Group Files,這個指令會顯示一個popup window,裡面列出project內的所有檔案。我把這個command改成Ctrl+Tab,這樣子的操作習慣就比較像Visual Studio的切換檔案的方式
Happy coding...


2012年12月24日 星期一

How to debug iOS program: the first step

當程式crash時,通常看到的畫面都是像這個樣子的:

程式break在main loop,從console的地方可以看到某個exception,可是不知道exception是從哪裡發出來的!

如果希望在程式一遇到exception時就馬上停下來(誰不希望這個樣子呢?),那我們可以這樣子做:

第一步先從Project Navigator內切換到Debug(?) tab,然後新增加一個breakpoint,如下


然後選擇Add Exception Breakpoints,畫面如下:



按Done之後,重新執行程式,xcode應該就會在程式要送出exception時把程式停在該停的地方了 !

For AppCode,可以從Run menu內選擇View Breakpoints,然後enable Exception breakpoints:



這樣子就大功告成了!!


Image Buttons, and when there are too many

延續計算機的習題, 今天的任務是要把其他的按鈕加到程式內。

要建立像這樣子的按鈕,我們可以使用UIButton,指定他的buttonface image,以及highlight image。由於button不是正方形,而且我們希望以後調整button大小時不需要重新製作image files,所以同樣的我們必須使用caps的方式來讓iOS幫我們自動調整影像大小。

建立一個image button的sample code大概是像這個樣子

[see sample code here]

注意到我們必須使用resizableImageWithCapInsets的方式來load image。另外PNG image檔案內必須定義外框的alpha channel,以便可以做出非正方形的效果。

OK,現在我們知道怎麼做出有影像的buttons了,可是接下來的問題是,畫面上這麼多個buttons,難道真的要用Interface Builder一個一個ctrl-drag到ViewController內嗎?這樣子程式不是變得很亂嗎?

一個很簡單的方法是,從Interface Builder內指定每個button的unique tag。Tag是一個number,所以我們可以把它當成Win32的control ID來使用,然後從程式內透過以下的方式來取得這個control:


// Return the button with the specified ID
//
- (UIButton*) getButton:(int)idButton
{
    return [self.view viewWithTag:idButton];
}

這樣子一來程式就變得比較乾淨了!

另外在設計button的內容時遇到了一個問題:怎麼設定 +,-, *, /, 這幾個button?如果是用一般的ASCII字元的話,看起來很難看。難道要自己畫不成?

類似的問題在StackOverflow上也可以找到解答:從Mac的menu bar上,click Language icon,在popup menu內會看到Show Character Viewer的選項,從這裡面就可以找到很多特殊字元,直接double click,就可以把對應的unicode paste到IB內了。

完成品如下:





2012年12月22日 星期六

iOS內如何寫出一個有Background Image的Label, Part 2

繼續前一篇文章內提到的有背景圖片的UILabel的實作方式,這一次試著直接subclass UILabel,然後加入padding以及background image的屬性設定.


// ImageLabel.h
//


@interface ImageLabel : UILabel

// Text Padding
//
@property (nonatomic, assign) UIEdgeInsets insets;

- (void) setBackgroundImage:(UIImage*) image;

@end

// ImageLabel.m
//
#import "ImageLabel.h"

@implementation ImageLabel

@synthesize insets = _insets;

- (void)drawTextInRect:(CGRect)rect 
{
   return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.insets)];
}

- (void)setBackgroundImage:(UIImage *)image
{
  CGRect rect = self.bounds;
  UIImage* imageScaled = [self imageWithImage:image scaledToSize:rect.size];

  self.backgroundColor = [UIColor colorWithPatternImage:imageScaled];
  imageScaled = nil;
}

- (UIImage*)imageWithImage:(UIImage*)image
               scaledToSize:(CGSize)newSize;
{
  UIGraphicsBeginImageContext( newSize );
  [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
  UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();

  return newImage;
}

@end

另外在controller內,放入一個UILabel物件,然後把它的base class改成ImageLabel,接著在viewDidLoad內,設定UILabel的屬性:

// Initialize result label
//
- (void)initResultLabel
{
  // Stretch the image with 1x1 caps on each side
  //
  UIImage* imageBackground = [[UIImage imageNamed:@"Rez/iPhone/bg-display.png"] stretchableImageWithLeftCapWidth:1 topCapHeight:1];
  
  [_resultLabel setBackgroundImage:imageBackground];
    imageBackground = nil;

  _resultLabel.insets = UIEdgeInsetsMake(2, 4, 2, 4);
}

這樣子的效果就跟原先的設計一樣,而且更簡潔。


上述程式碼雖然可行,不過我還不確定ImageLabel是否還必須處理以下的狀況?
  1. 有沒有可能setBackgroundImage時ImageLabel的frame size還沒有被設好?
  2. ImageLabel如果被resize的話,那該怎麼處理?
這個就等待下次有空時再來研究了。




2012年12月21日 星期五

iOS內如何寫一個有background image的label

給自己一個任務: 看能否寫出一個簡單的計算機程式. 在App store上面找幾個範例, 決定參考底下這個程式的layout:

所以我想,就先來implement上面的結果區吧!

用PhoneView把程式的bundle打開,看到一個background image file,長得像底下這樣子:


影像的長寬是 615 x 205,與上方顯示區的大小不一樣 !

所以我想,應該是想辦法放一個UILabel,然後設定他的background image是這張圖,iOS應該會自動調整影像的大小,可是我還要注意不要讓iOS把影像的邊緣給弄糊掉了,iOS內應該有類似Android Nine-Patch的觀念才對。

所以去網路上找了一些code,寫成了這個樣子:
    
    // Stretch the image with 1x1 caps on each side
    //
    UIImage* imageBackground = 
       [[UIImage imageNamed:@"Rez/iPhone/bg-display.png"]             
           stretchableImageWithLeftCapWidth:1 topCapHeight:1];
    
   _resultLabel.backgroundColor
       [UIColor colorWithPatternImage:imageBackground];
    
可是這個並不work!當圖片大小與Label不一致時,colorWithPatternImage並不會自動調整圖片大小,而是會做pattern fill,所以無法達成我要的效果。

到StackOverflow上面查了一下,有人suggest改用UITextField。由於UITextField可以指定backgroundImage,所以可以自動scale圖片。唯一要注意的只要把UITextField設定成是readonly就行了。

所以我做了第二次嘗試:

    UIImage* imageBackground = ..  

    _resultText.borderStyle = UITextBorderStyleNone;
    _resultText.background = imageBackground;

這樣子看到的效果是像這個樣子:


背景影像的scale以及邊緣的處理都是正確的,可是文字太靠右邊了,我需要一些padding!

再次查詢,這時候發現問題越來越大了,有的作法是subclass TextField,然後自定他文字的bounding box,有的作法是在TextField的右緣再放入一個空的view,我則是先試看看這樣子的作法:
  • 在TextField的下方放一個UIView,設定UIView的背景為底圖,
  • 把TextField改成是透明的,然後position TextField來產生padding的效果
這時候又發現,UIView也是沒有backgroundImage property,只有backgroundColor(跟UILabel一樣),所以圖片還是只能用pattern fill的方式擺放!看來還是必須想辦法自己寫code來scale background image。以下是相關的程式碼:

    UIImage* imageBackground = ..  

    // resize image to fit _resultTextBackground
    //
    CGRect rect = _resultTextBackground.bounds;
    UIImage* imageScaled = 
      [self imageWithImage:imageBackground scaledToSize:rect.size];

    _resultTextBackground.backgroundColor = 
      [UIColor colorWithPatternImage:imageScaled];
    
    _resultText.borderStyle = UITextBorderStyleNone;
    [_resultText setBackgroundColor:[UIColor clearColor]];
    
    const int cxPadding = 4;
    const int cyPadding = 4;
    _resultText.bounds = CGRectMake(cxPadding, cyPadding, 
       rect.size.width - cxPadding * 2, rect.size.height - cyPadding * 2);

另外

- (UIImage*)imageWithImage:(UIImage*)image
              scaledToSize:(CGSize)newSize;
{
   UIGraphicsBeginImageContext( newSize );
   [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
   UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();

   return newImage;
}

這樣子的效果就對了

只是我開始想到其他的問題:
  1. imageWithImage這個是個common function,從xcode的角度該用什麼方式來建構library呢?or 這個API可以用category的方式加到UIImage class內 ?
  2. 如果我必須自己scale image的話,那也許直接用UILabel也可以做的出來?
  3. 這code看起來已經有點亂了,也許要試著把這組control寫成一個獨立的元件來用?
問題1跟3是很基礎的架構性的問題,希望下次可以找到好的方法!!


2012年12月17日 星期一

Add Resource File to XCode的各種不同方式

以下整理把檔案/Folder加入XCode專案時的各種不同選項。

不管是從Finder內直接拖曳檔案/Folder進入XCode,或是從XCode內執行File | Add Files to ... ,XCode都會顯示以下的畫面讓user決定檔案加入的方式:


如果是放入單一檔案的話,則唯一重要的選項是是否選擇Copy items into destination group's folder。如果選擇了這個項目的話,則檔案會被複製到project的目錄底下,從此以後任何的修改就以project內的這個檔案的內容為主,與原始檔案無關。如果不選擇的話,則project內看到的檔案會是一個reference to 原始檔案,使用者就必須確認這個檔案與project的目錄相對關係。

如果是放入一個目錄的話,則Folder內的選項會決定這些新加入的檔案的Folder架構。
如果選擇[Create groups for any added folders]的話,則xcode會依照原始目錄的架構把檔案加到project內,可是目錄的架構會以Group的方式來模擬,從UI上會看到一個黃色的Group icon


反之如果選擇[Create folder reference]的話,則xcode會產生一個藍色的Folder icon,如上圖。

這兩者最大的差異是,Group的架構看起來雖然像是個folder,可是實際上檔案都是攤平在同一層的,所以原先的目錄如果是以Group的方式加入之後,最後產生的app檔案內所有的檔案都是放在同一層內,程式內如果要reference這些檔案的話就不需要傳入Group的path。如果是Create folder reference的話,那原始目錄的架構還是會繼續維持,程式內要讀取這些檔案時就必須注意檔案的path。

上述兩者選項都可以搭配[Copy items into destination group]選項。如果選擇的話,則xcode會把檔案或是目錄copy到project folder內。注意到如果不是[Create folder reference]的話則原始目錄內的所有檔案會被copy到project folder內的同一層。 如果不選擇的話,則xcode會記錄原始目錄的位置,所以使用者在移動project也要注意目錄的相對關係。

如果一個專案內需要用到很多的檔案時,最好是可以有一個目錄架構以方便管理,這時候應該是選擇[Create folder reference],然後optionally的搭配[Copy items]選項來決定以後要在哪個目錄內管理這些檔案。





2012年12月16日 星期日

如何manage image resource files

當我們有大量的image files/folders需要加到xcode的專案內時, 最好的方式是讓這些folder/files以reference的方式加到專案內, 這樣子的話我們可以直接從file system上面去maintain這些檔案, 而且從xcode內看到的樣子也會自動跟file system上面的是一致的.

(請參考以下這一篇的作法: http://majicjungle.com/blog/123/)

為了確認xcode build出來的app的layout跟我們想的一樣, 可以從xcode內把build的output file放置到project folder底下, 作法如下 (XCode 4.5.2)

XCode | Preference | Locations, select Derived Data, Advanced, 然後把Build location設定成Relative to Workspace, 如下圖:
這樣子的話每次build之後在project的目錄底下就會有一個Build的目錄, 可以從裡面找到build出來的application bundle, 透過Show Package Content就可以看到整個bundle的內容.