2013年10月31日 星期四

視差效果 (Parallax effect)

注意:以下教學只支援 iOS7 以上的 SDK!!

iOS 7 的特點除了毛玻璃效果之外還有另一個,就是視差效果 (Parallax effect),
這個功能比起毛玻璃效果效果使用起來簡單許多了,因為官方供提供這個 Class 給你使用,
所以可以自由的加在你想要套用這個效果的 View 上面了,以下就是這個效果的教學。

首先在你的 UIViewController 裡面建立一個 UIView,這個 UIView 的大小可以隨你喜歡,只要將它 addsubView: 上去就好了,
假設這個 UIView 叫做 viewA 好了,以下教學就用它做範例:
- (void)viewDidLoad
{
	[super viewDidLoad];
	
	// 首先先建立一個 X 軸的可偏移範圍,本教學就用 +30 到 -30 之間
	UIInterpolatingMotionEffect *xAxis = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
	[xAxis setMinimumRelativeValue:@(-30.0f)];
	[xAxis setMaximumRelativeValue:@(30.0f)];
	
	// 同樣,也建立一個 Y 軸的可偏移範圍,也是用 +30 到 -30 之間
	UIInterpolatingMotionEffect *yAxis = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
	[yAxis setMinimumRelativeValue:@(-30.0f)];
	[yAxis setMaximumRelativeValue:@(30.0f)];
	
	// 建立一個群組將 X 軸與 Y 軸的可偏移範圍包起來
	UIMotionEffectGroup *motionEffect = [UIMotionEffectGroup new];
	[motionEffect setMotionEffects:@[xAxis, yAxis]];
	
	// 把 viewA 建立起來,並且加到 viewController 中
	UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 200.0f)];
	[viewA setBackgroundColor:[UIColor whiteColor]];
	[viewA setCenter:self.view.center];
	
	[self.view addSubview:viewA];
	
	// 之後將這個視差效果加入到 viewA 中
	[viewA setMotionEffects:@[motionEffect]];
	
	// 用完後將它們釋放掉
	[xAxis release];
	[yAxis release];
	
	[motionEffect release];
	[viewA release];
}

這樣子執行就可以用了,只不過這在模擬器視沒有效果的,所以只能在實機上面使用,
所以這次就沒有畫面了。

本次的教學就到這邊,大家下次再見了。

2013年9月26日 星期四

增加毛玻璃特效到你的 UIView 中

注意:以下教學只支援 iOS7 以上的 SDK!!

這次 iOS7 的特點就是毛玻璃效果,不過只有特定的 View 可以使用,像是 navagation bar 與 tool bar 以及 alert view 等等。
但是可以透過作弊的方式將這個效果加到你要顯示的 View 裡面,下面就是教學。

首先先建立一個 toolbar 的全域變數
@interface DTBlurView ()
 {
  UIToolbar *_blurToolbar;
 }

 @end

之後在 initial 的時候將 toolbar 的 layer 加到自己身上
- (id)initWithFrame:(CGRect)frame
 {
  self = [super initWithFrame:frame];
  if (self) {
   [self setClipsToBounds:YES];
        
   _blurToolbar = [[UIToolbar alloc] initWithFrame:self.bounds];
        
   [self.layer insertSublayer:_blurToolbar.layer atIndex:0];
  }
  return self;
 }

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

這樣子就可以了,最後只要將它加到 ViewController 裡面就好了。
結果如下:


另外,本教學不能保證符合 Apple 的 iOS Human Interface Guidelines 的規定,
所以當你被退件的時候,請自行處理。

2013年8月24日 星期六

Array 的列舉

在早期 C 語言 Array 的列舉有兩種方法,分別為 for 與 for-in 兩種,
到了 Objective-C 這兩種方法也是繼續存在,不過除此之外多了另一個新的方法,
是採用 block 的 enumerate 的方式,它的好處是它會使用另一個 Thread 去處理,
不會影響原本的 Thread 導致畫面凍結的問題,至於這三個要怎麼使用就由我來做教學。

首先假設有一個 Array 名稱為 sampleArray,它的 Code 如下:
	NSArray *sampleArray = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"0", nil];

首先是最簡單的 for 列舉的方式,另外故意在列舉到 8 的時候停止,
至於為什麼,我就慢點再解答了。
	NSLog(@"For 的結果:");
	for (int i = 0; i <= sampleArray.count - 1; i++) {
		NSString *symbol = (NSString *)sampleArray[i];
		NSLog(@"%@", symbol);
	
		if ([symbol isEqualToString:@"8"]) {
			break;
		}
	}
結果如下:








接下來就是 for-in 的列舉,同樣列舉到 8 的時候停止。
	NSLog(@"For in 的結果:");
	for (NSString *symbol in sampleArray) {
		NSLog(@"%@", symbol);
	
		if ([symbol isEqualToString:@"8"]) {
			break;
		}
	}
結果如下:








最後是上面所說的 enumerate 的方式,同樣列舉到 8 的時候停止,
這時停止列舉的方式就與之前的完全不一樣了,這是因為在 block 中無法使用 break; ,所以折衷方案就變成這樣子了。
另外預設的 block 回傳內容如右:^(id obj, NSUInteger idx, BOOL *stop),
其中的 id obj 可以改為對應的 Array 內容,如果你的 Array 內容不固定的話,那還是使用預設的內容。
	NSLog(@"Enumerate 的結果:");
	[sampleArray enumerateObjectsUsingBlock:^(NSString *symbol, NSUInteger idx, BOOL *stop) {
		NSLog(@"%@", symbol);
	
		if ([symbol isEqualToString:@"8"]) {
			*stop = YES;
		}
	}];
結果如下:








另外 enumerate 的列舉有另一個方法,就是反向列舉,使用方法如下:
	NSLog(@"Enumerate Reverse 的結果:");
	[sampleArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSString *symbol, NSUInteger idx, BOOL *stop) {
		NSLog(@"%@", symbol);
	
		if ([symbol isEqualToString:@"8"]) {
			*stop = YES;
		}
	}];
這時候的結果如下:




基本上 enumerate 的列舉的效能比起另外兩個好上很多,
主要是它是使用另外一個 Theard 來處理的,不過成也 Theard,敗也 Theard,
要是你在列舉的過程中需要使用非同步的處理的話,就 enumerate 的列舉會讓你進入一陣混亂,
因為非同步的處理也是使用另一個 Theard 來處理的,因此會導致在另一個 Theard 中又進入了另一個 Theard,
這會讓這個程式超出你的掌控,所以這時候必須改回原始的 for 或是 for-in 來列舉。


最後,這次的教學到此為止,大家下次再見了。

2013年8月20日 星期二

隱藏 UITableViewCell 的 Edit Control

此教學在 iOS 7 上已經無效了,請改看隱藏 UITableViewCell 的 Edit Control [For iOS 7]


在 UITableViewCell 有一個欄位叫做 Edit Control,這是根據官方的文件所得知的,
不過常常會有需要自定一個當多選 Cell 的時候的圖片,如下圖:


不過 Edit Control 卻是私有的 Code,你無法自定它,
既然不能自定原始的 Edit Control,那就將它藏起來,並做一個新的給覆蓋過去就好了,
那麼要怎麼做了?這篇教學正是教你怎麼做到這件事。

首先就是建立一個繼承 UITableViewCell 的 Class,接下來是讓它與 UITableView 做連動,
要如何做連動我就不教了,這不是本篇的重點,
再來就是在這個 Class 動點手腳了,動手腳的就是 - layoutSubviews 這個 Method,

先 log 現在 cell 裡的 subviews 的資訊
- (void)layoutSubviews
{
	[super layoutSubviews];
	
	if (self.editing) {
		NSLog(@"%@", self.subviews);
	}
	
}

這時候得到的資訊如下圖:

其中 UITableViewCellEditControl 正是 Edit Control 的 Class 名稱,
不過 UITableViewCellEditControl 是私有的 Class,所以無法使用 isKindOfClass: 做判斷,
所以改用另一個繞路的方式來判斷,就是 NSStringFromClass() 將 Class 名稱轉成 NSString 之後再使用 isEqualToString: 去判斷,
這時候再把 Code 寫成下面的樣式:

- (void)layoutSubviews
{
	[super layoutSubviews];
	
	if (!self.editing) {
		return;
	}
	
	void (^enumBlock) (id, NSUInteger, BOOL *) = ^(UIView *subview, NSUInteger idx, BOOL *stop) {
		if ([NSStringFromClass([subview class]) isEqualToString:@"UITableViewCellEditControl"]) {
			[subview setHidden:YES];
	    
			*stop = YES;
		}
	};
	
	[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:enumBlock];
}

這時候礙眼的 Edit Control 就從這樣子:

變成了這樣子了:

這時候就能隨你去增加圖片來取代原始的 Edit Control 了。
本篇教學就到這邊告一個段落,下一次會是" Array 的列舉方法" 的教學,敬請期待。

2013年7月28日 星期日

第一次繪製漸層圖片就上手

這次是使用 UIBezierPath 製作圖片的進階應用繪製一個有漸層效果的圖片,
廢話不多說,趕快下面的教學吧。

// 這次就直接在 viewDidLoad 下面直接繪製
- (void)viewDidLoad
{
	[super viewDidLoad];
	
	// 建立要繪製圖片的座標
	CGRect frame = CGRectMake(20.0f, 20.0f, 200.0f, 200.0f);
	// 將座標中的尺寸取出以便之後使用
	CGSize imageSize = frame.size;
	// 設定邊線的寬度
	CGFloat linWidth = 3.0f;

	// 建立一個畫布,大小為剛剛取出的尺寸
	UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);

	// 建立一個 RGB 的顏色空間
	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
	// 建立繪製圖片用的 context
	CGContextRef context = UIGraphicsGetCurrentContext();

	// 建立漸層用的顏色,這邊只用兩個顏色,要更多顏色可以自行加入
	UIColor *beginColor = [UIColor colorWithRed:51.0f/255.0f green:102.0f/255.0f blue:204.0f/255.0f alpha:1];
	UIColor *endColor = [UIColor colorWithRed:0 green:0 blue:102.0f/255.0f alpha:1];

	// 將顏色加入陣列中
	NSArray *gradientColors = [NSArray arrayWithObjects:(id)beginColor.CGColor, (id)endColor.CGColor, nil];

	// 建立漸層顏色的啟始點,因為有兩個顏色,所以有兩個數值,如果有多個顏色就需要多個數值
	// 數值的範圍為 0~1。
	CGFloat gradientLocation[] = {0, 1};

	// 建立繪製漸層的基本資訊
	CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)gradientColors, gradientLocation);

	// 繪製一個矩型路徑,讓漸層的顏色能畫上去
	UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
	CGContextSaveGState(context);
	[bezierPath addClip];

	// 建立繪製漸層的起點
	CGPoint beginPoint = CGPointMake(imageSize.width / 2, 0);
	// 建立繪製漸層的終點
	CGPoint endPoint = CGPointMake(imageSize.width / 2, imageSize.height);

	// 將繪製的座標加入要繪製的漸層中
	CGContextDrawLinearGradient(context, gradient, beginPoint, endPoint, 0);
	// 建立圖片邊線的資訊
	CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
    
	// 將邊線繪製到路徑中
	[bezierPath setLineWidth:linWidth];
	// 將漸層色填滿路徑
	[bezierPath stroke];

	CGContextRestoreGState(context);

	// 將繪製完成的 context 輸出成 UIImage 格式
	UIImage *drawnImage = UIGraphicsGetImageFromCurrentImageContext();

	// 結束 context,並且釋放記憶體
	UIGraphicsEndImageContext();
	CGColorSpaceRelease(colorSpace);
	CGGradientRelease(gradient);

	// 將繪製完成的圖片加到 UIImageView 讓它顯示
	UIImageView *imageView = [[UIImageView alloc] initWithImage:drawnImage];
	[imageView setFrame:frame];

	[self.view addSubview:imageView];
	[imageView release];
}

完成結果如下:

2013年6月16日 星期日

Block 的 Copy 與 Release

在上一篇的教學教到了 Block 的使用,
通常在一個 Method 中傳入一個 Block 後,
在這個 Method 結束之前使用的時候就與平常的使用一樣。

不過如果要將 Block 用一個全域變數保留下來的話,
就會出現問題了,因為,Block 的傳值時是 Auto Release 的狀態的,
所以需要避免被系統 Release 掉,需要動點手腳。

在一般的變數都是使用 Retain 與 Copy 來增加 Retain Count 避免被系統 Release,
但是在 Block 上就只有 Copy 可以使用而已
當然有增加 Retain Count 也就有減少的方法,就是使用 Release,
要怎麼 Copy 與 Release 的話,就看下面的範例吧。

// 建立一個 Block 的形態
typedef void (^TestBlock) (void);

@interface BlockDemo : NSObject
{
	// 宣告一個全域的 Block
	TestBlock _testBlock;
}

@implementation BlockDemo

- (void)inputABlockForCopy:(TestBlock)testBlock
{
	// 將傳入的 Block 做 Copy 的動作,以確保不會被 Auto Release
	_testBlock = [testBlock copy];
}

- (void)dealloc
{
	// 在 Class 被 Release 時也讓全域的 Block 也 Release 掉
	[_testBlock release];
	
	[super dealloc];
}
	


其實很簡單,不過我之前將 Block 當成一個 struct,
但是它其實是一個 Object,所以可以做 Copy 與 Release 的動作,
好了簡單的教學到此為止,
下次我打算教如何使用程式碼繪出一張 UIImage,
下次再見囉。

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
倒是有點貴就是了。

2013年4月16日 星期二

解決經常使用相同的圖片所造成記憶體使用量增加的問題

這次是一個 Category 的進階應用
在某些時候會需要經常重複使用同一張圖片
有可能會造成記憶體使用量的增加,造成不必要的記憶體消耗
這時候可以使用以下方法

先建立一個基於 UIImage 的 Catagory
在 h 檔中建立以下 Code
+ (id)aImage

之後在 m 檔建立以下 Code
其中 aImage 是可以由你自行決定名稱
static UIImage aImage = nil;

+ (id)aImage
{
	if (aImage == nil) {
		aImage = [[UIImage imageNamed:@"__imageName__"] retain];
	}
	
	return aImage;
}

這時候在需要使用這張圖的時候可以直接使用 [UIImage aImage]; 就可以調用這張圖片
而且用過後會一直保留在記憶體中,直到這個程式被終結掉為止,
這樣子就不用一直將相同的圖片重新載入記憶體而造成記憶體的消耗。

2013年3月19日 星期二

發佈自定的 Notification

在之前的 Remote Control 的教學中有說到可以做在 appDetegate 中
但是在其他的 ViewController 要怎麼收到這些訊息了?
最簡單的是使用 Notification,但是要如何使用了?

在絕大部分的教學書都有說到如何接收 NotificationCenter 的訊息
但是很少書本有說到要如何發佈自定的 Notification 訊息,
其實很簡單,作法如下
[[NSNotificationCenter defaultCenter] postNotificationName:@"發佈的訊息名稱" object:nil];

這樣子就會在觸發這行時發佈出 Notification 訊息,這時候在需要接收的 Class 中做註冊 NotificationCenter 的動作
[[NSNotificationCenter defaultCenter] addObserver:self selector:接收到訊息所觸發的動作  name:@"發佈的訊息名稱" object:nil];

這時候就可以收到訊息時做相對映的動作了,當然有註冊訊息就有注銷訊息,
在你不需要接收訊息的時候就要做注銷的動作,注銷的方法如下,
[[NSNotificationCenter defaultCenter] removeObserver:self  name:@"發佈的訊息名稱" object:nil];

注銷後就不會再接收到訊息的,除非你再度註冊。

這次的教學就到此為止,下次的教學再見了。

2013年2月28日 星期四

Remote Control

這次是教如何使用多工列左邊的 Remote Control 來控制你的音樂播放
要使用這個功能很簡單,只要在 viewWillAppear 的時候宣到要接收 Remote Control 的事件
宣告如下:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

這樣子就會開始接收到了,但是有開始必定有結束
所以結束時要在 viewDidDisappear 的時候宣告停止接收 Remote Control 的事件
宣告如下:
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];

這樣子開始與結束接收 Remote Control 的事件都完成了
但是光是接收到事件是不夠的,還要做相對應的動作
這時候就要建立
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
這個 Method 來實作相對應的動作

但是這個 Method 的傳入值 event 有可能會是 Touch event 、Shake event
所以要先過濾掉,實作的內容如下
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
	if (event.type != UIEventTypeRemoteControl) return;

	switch (event.subtype) {
		case UIEventSubtypeRemoteControlTogglePlayPause:
		// 按下播放/暫停時的動作
			break;

		case UIEventSubtypeRemoteControlNextTrack:
		// 按下下一首歌的動作
			break;

		case UIEventSubtypeRemoteControlPreviousTrack:
		// 按下上一首歌的動作
			break;

		case UIEventSubtypeRemoteControlBeginSeekingForward:
		// 長按下一首歌時開始快進的動作
			break;

		case UIEventSubtypeRemoteControlEndSeekingForward:
		// 長按下一首歌時結束快進的動作
			break;

		case UIEventSubtypeRemoteControlBeginSeekingBackward:
		// 長按上一首歌時開始快退的動作
			break;

		case UIEventSubtypeRemoteControlEndSeekingBackward:
		// 長按上一首歌時結束快退的動作
			break;

		default:
			break;
	}
}

完成上面的實作後,這時候就可以按照你的播放器的作法放入相對應的動作了
另外現在只有在這個播放器的畫面下 Remote Control 才有作用
如果想不在播放器的畫面下時 Remote Control 也要有作用的話
我是建議將它實作到 appDelegate.m 裡面

不過要如何將那些動作傳入到播放器的畫面下?
這就要自己想想摟,這次的教學就到這邊,下次再見了

2013年1月16日 星期三

Category

這次的教學是 Catagory
先說明一下它是啥, Catagory 是一個將任一的 Class 進行擴充的功能,
主要是以補足 Class 所沒有的功能,我自己是將它與 Class Method 一起使用,
來擴充原本沒有 Class method 的內建 Class ,接下來就開始教怎麼做了。

首先開啟任一專案,之後按下⌘+N,或是 File > New > File… 新增一個檔案
新增時要選擇下面這一個,之後按下下一步,


現在就再 Catagory 欄位裡輸入你的檔名,Catagory on 的欄位是要擴充的 Class,
輸入完按下一步選擇要儲存的位置,這樣子就建好你的檔案了。


建好的檔名會像是這樣子,在+之前的要擴充的 Class 名稱,+之後的是自定的名稱


檔案建好後就開始建立要擴充的 Method 了
我就繼續使用現在的 Class 進行示範,
在原本的 UIWebView 的宣告中,都是先 initWithFrame: 建立它的大小,
之後再 loadRequest: 輸入要顯示的網址,
這樣子就需要消耗掉兩行,反而變得比較沒效率,
所以我採用了自定的 Class Mthod 的方法

UIWebView+webView.h
+ (id)webWithFrame:(CGRect)frame URL:(NSURL *)url;
- (id)initWithFrame:(CGRect)frame URL:(NSURL *)url;

因為自定了一個新的 Calss Method 就需要一個 Initial 做搭配,
雖然這並不一定,不過這是我的習慣,
接下來就實作了

UIWebView+webView.m
+ (id)webWithFrame:(CGRect)frame URL:(NSURL *)url
{
	UIWebView *webView = [[[UIWebView alloc] initWithFrame:frame URL:url] autorelease];

	return webView;
}

- (id)initWithFrame:(CGRect)frame URL:(NSURL *)url
{
	self = [super initWithFrame:frame];

	if (!self) return nil;

	[self loadRequest:[NSURLRequest requestWithURL:url]];

	return self;
}

這樣子就可以了,未來要使用 UIWebView 時就可以使用以下宣告方法,
UIWebView *webView = [UIWebView webWithFrame:CGRectMake(0, 0, 320, 480) 
										URL:[NSURL URLWithString:@"HTTP://www.google.com"]];
這樣子,就將原本的兩行變成了一行就能解決,
不過要記得使用
#import "UIWebView+webView.h"
才能使用,

這次的教學就到這邊,我再想到有啥新東西時就還會拿出來教的。