2013年5月8日 星期三

第一次學習 Block 就上手

Block 是從在 iOS 4 開始的新功能,
在使用 UIView animation 的時候常常看到,
但是不要只會使用內建的 Block 功能,
你也可以建立你自己的 Block 在你自己的程式中使用,
Block 像是將一個 Method 當變數使用,
廢話不多說,趕快開始教學吧。

首先,Block 可以當做一個變數型態來用,用法如下
	
	// 定義一個 BlockName1 的型態,傳入一個 int 的值,沒有回傳值
	typedef void (^BlockName1) (int someValue);
	
	// 這是另外一種 BlockName2 的形態,沒有傳入值,有 NSString 的回傳值
	typedef NSString* (^BlockName2) (void);

Block 的宣告與實作
	
	// 宣告 block1 為 BlockName1 的型態,
	BlockName1 block1;
	
	// 實作 block1
	block 1 = ^(int someValue){
		NSLog(@"Block1:%d", someValue);
	};
	
	// 宣告與實作 block2
	BlockName2 block2 = ^(){
		return @"I love block !!";
	};
	
	// 不宣告型態直接實作 Block
	int (^block3) (int a, int b) = ^(int a, int b){
		return a+b;
	};
	
	// 宣告與實作都結束了,接下來就是使用它了
	block1(3);
	NSLog(@"Block2:%@", block2());
	NSLog(@"Block3:%d", block3(3, 8));

執行的結果如下
	Block1:3
	Block2:I love block !!
	Block3:11

最後是重頭戲了,將 Block 與 Method 一起使用
	typedef (^NewBlock1) (void);
	
	// 宣告一個 Method 並實作它
	- (void)aMethodWithBlock:(NewBlock)newBlock otherBlock:(void (^) (NSString *string))stringBlock{
		newBlock();
		stringBlock(@"World");
	}

這時候可以在外部的 Class 呼叫這個 Method,並且實作 Block 傳入
	NewBlock block = ^(){
		NSLog(@"Hello ");
	};
	
	[aClass aMethodWithBlock:block  otherBlock:^(NSString *string){
		NSLog(@"%@ !!", string);
	}];

執行的結果如下
	Hello
	World !!

基本 Block 的教學就到這邊,
Block 有時可以將它當做一個 Delegate 的方法使用,
來實現需要回傳值的動作。

好了,教學就到此為止,等有其他可教的我會再做教學的。

2013年5月7日 星期二

用 CGPath 圍出一個不規則形狀的手勢區域

在一般的手勢都只能用在矩形的 UIVew 上面,
但是有時候會希望觸發手勢的區域會是在一個非矩形的範圍中;
還有想要使用手勢的 View 被疊在其他的 View 下面,
但是又不能改變它們的順序時。

以上兩種情況都可以使用 CGPath 來解決問題。

假設現在的情況如下,
我有一個矩形的 View,但是手勢的觸發區域想要在它的上半部的三角形中,
三角形下方的角剛好在 View 的正中央。

如下圖:


所以 Code 的寫法如下,這部分的 Code 可以實作在任一的 Method 中,
只要記得在需要時呼叫這個 Method 來建立這個路徑就好了。

首先要先將 CGPath 的變數宣告為全域變數
@interface viewController ()
{
	CGMutablePathRef  triangle;
}
@end

之後就開始繪製這個路徑了
	// 先取出 view 的基本資訊,像是 x\y 座標與長的數據,方便 CGPath 的建立
	CGFolat x = view.frame.origin.x;
	CGFolat y = view.frame.origin.y;
	CGFolat width = view.frame.size.width;
	// 將  triangle 初始化
	triangle = CGPathCreateMutable();
	// 先給它一個啟始點,就是這個 View 的中心點
	CGPathMoveToPoint(triangle, NULL, view.center.x, view.center.y);
	// 接下來從啟始點畫一條線到 View 的右上角
	CGPathAddLineToPoint(triangle, NULL, x, y);
	// 再來從起始點再畫另一條線到 View 的左上方
	CGPathAddLineToPoint(triangle, NULL, x + width, y);
	// 之後再封閉這個路徑,最後的一條線就會自動的補上了
	CGPathCloseSubpath(triangle);

這時候畫出來的路徑是看不到的,
所以可以搭配之前教學的 UIBezierPath 來繪製可視的區域,
來看畫出來的區域有沒有錯。

接下來我要用點擊的手勢在這個路徑中觸發,這個手勢在建立 View 的時候就被加在 View 裡面了,
所以就直接在手勢觸發的 Method 來實作了。
- (IBAtion)tapGesture:(UITapGestureRecognizer *)sender
{
	// 取得現在的點擊座標
	CGPoint tapPoint = [sender locationInView:sender.view];
	
	// 判斷點擊的座標時否在這個路徑裡面
	if (CGPathContainsPoint(triangle, NULL, tapPoint, NO)) {
		NSLog(@"Taped !!");
	}
}

這樣子就完成了,至於要怎麼實作就靠大家自己發揮了,
另外這個路徑不只能畫直線與 UIBezierPath 一樣可以畫圓弧、貝茲曲線等等,
這次的教學就到這邊,下次再見了。

2013年4月30日 星期二

關於 property 的二三事

在現行的的教學書籍都只是說 IBOutlet 需要用 property
接下來就沒有了
其實 property 包含了你所不知道的東西

property 其實是會自動產生 Setter 與 Getter 的兩個 Method
但是這兩個 Method 的實作是可以被覆寫的
你可以做你想要的實作動作
像是你做了 @property (nonatomic, retain) UIImage *image;
之後你可以在 m 檔中覆寫 Setter 的實作

- (void)setImage:(UIImage *)image
{	UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
	[imageView setFrame:self.view.bounds];
	[imageView setTag:1];
	
	[self.view addSubview:imageView];
	[imageView release];
}

當你覆寫 Setter 的時候你不需要使用 @synthesize image = _image;
因為你自己決定了 Setter 傳入參數的作用了
不過當覆寫 Setter 時,Getter 也需要被覆寫
所以就來做覆寫 Getter 的 Method

- (UIImage *)image
{	UIImageView *imageView = (UIImageView *)[self.view viewWithTag:1];
	
	return imageView.image;
}

這時候外部的 Class 也可以取得這個 image 的資訊了
另外 property 有 readonly 的參數
這時候就只要覆寫 Getter 就好了

當我知道原來 property 可以這樣子做之後我的程式的寫法就比較活了
因為可以用一行字取代兩個 Method 的宣告
而且同時能做到原來 property 的動作
可以說是一間樹德一兼數得阿

最後,下次的教學是如何使用 CGPath 來圍出一個觸碰的區域。

2013年4月23日 星期二

使用 UIBezierPath 製作圖片

之上一篇 " 解決經常使用相同的圖片所造成記憶體使用量增加的問題 " 其實有另外一個用法
就是搭配 UIBezierPath 來繪製圖片
所以這邊就教簡單的 UIBezierPath 繪製多邊形的方法
首先 UIBezierPath 可以使用在兩種情況下
一個是寫在 - (void)drawRect:(CGRect)rect Method 裡面
但是這個 Method 只會在繼承 UIView Class 中出現
在 UIViewController 中沒有這個 Method
所以變成要轉換成 UIImge 才能使用
接下來的教學會以用這顏色來區分這兩者的差異

深灰色為不會在 - (void)drawRect:(CGRect)rect Method 出現的 Code
橘色為只會在 - (void)drawRect:(CGRect)rect Method 出現的 Code

// 建立繪圖的畫布,其中的 Size 是畫布的大小
UIGraphicsBeginImageContext(self.view.bounds.size);
// 建立畫布的 Context
CGContextRef context = UIGraphicsGetCurrentContext();


// 建立繪圖的路徑
UIBezierPath *additionPolygon = [UIBezierPath bezierPath];
// 建立路徑的起始點
[additionPolygon moveToPoint:view.center]
// 建立一條線從起始點到指定的點;
[additionPolygon addLineToPoint:CGPointMake(x, y)];
// 再畫另一條線
[additionPolygon addLineToPoint:CGPointMake(width, y)];
// 封閉這個路徑
[additionPolygon closePath];

// 建立填滿這個路徑的顏色
CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]);
[[UIColor redColor] setFill];
// 將顏色填滿這個路徑
[additionPolygon fill];

// 建立路徑邊線的顏色
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
[[UIColor redColor] setStroke];
// 設定邊線的粗細
[additionPolygon setLineWidth:10.0f];
// 將顏色填滿這個路徑的邊線
[additionPolygon stroke];


// 將這個路徑轉成 UIImage 的形態
UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext();

// 結束這個畫布
UIGraphicsEndImageContext();

// 將畫出來的圖片放入 imageView
UIImageView *imageView = [UIImageView imageViewWithFrame:self.view.bounds];
[imageView setImage:bezierImage];

[self.view addSubview:imageView];



這樣子就可以靠程式碼畫一張簡單的圖片了
UIBezierPath 是一個萬能的繪圖路徑
它也有弧線、貝茲曲線、圓角矩形等路徑可以使用
另外我也推薦一個可以更快速繪圖的程式 PaintCode
它像是 Photoshop 一樣可以透過圖形介面進行繪畫
畫完後會自動轉成程式碼給你使用,只不過需要 NTD 2990
倒是有點貴就是了。