2014年7月27日 星期日

GPUImage 滿漢大餐: 下載 GPUImage & 編譯成 Framework 來使用

繼續之前的部份,要使用第三方 Library 的第一個步驟就是先下載它,
那麼就來下載吧。

下載

GPUImage git 頁面的網址是:https://github.com/BradLarson/GPUImage
有幾個下載的方法,這邊我就教其中的兩個。

第一個:直接下載,這是最簡單的方式,只要找到右邊的 Download ZIP,就可以下載它了。

第二個:使用 git clone 來下載它,在 Download ZIP 的上方能找到 HTTPS clone URL,那邊就是下載用的網址,
只要下在終端機中下 "git clone 網址" 這個指令就能了。

下載完成後到你下載的目錄中找到 GPUImage 的目錄,
這時候能看到這些檔案,如下圖

檔案的用途與說明如下:
build.sh這是要編譯成 Framework 的檔案,這等等會教怎麼使用。
examples這是範例的資料夾,這個有興趣的人就自己看吧
framework原始檔案都放在這裡面,不想使用 Framework 的人可以直接使用裡面的檔案
GPUImage.podspec這是 Cocoa Pod 的描述檔,不過這邊不會用到它
License.txt這是放這個第三方 Library 使用哪個 License,在這邊是使用 BSD-style License
README.md這是這個第三方 Library 的基本描述與說明

使用

下載完畢後就是將它放入自己的專案中使用,
在 GPUImage 的目錄中找到 framework 下面的 Source 資料夾,
這個資料夾裡面都是放著 GPUImage 的原始檔案,
所以這個資料夾可以直接放入你自己的專案中使用。

編譯成 Framework

剛剛說到的 build.sh 是編譯成 Framework 來用的,
但是在編譯之前有一件事要做,
build.sh 裡面有一個參數要修改,
所以要先用文字編輯器來打開它,看你們習慣用哪一個打開就用那一個吧,

它個前段內容如下:
#!/bin/bash

set -e

IOSSDK_VER="7.0"

# xcodebuild -showsd

在那裡面能找到 IOSSDK_VER="7.0" 這一行,
這是在描述你的 iOS SDK 的版本,它個預設是使用 7.0,
如果你想要編譯成 6.0 版本的人,可以將它改成 6.0,
修改完之後將它存檔,可以準備進行編譯了。

開始編譯之前,請檢查一下 build.sh 的檔案屬性是否為 "可執行" 的,
檢查的方法是,請開啟你的終端機,切換目錄到 GPUImage 的目錄底下,
輸入 ls -la 的指令,就能看到這個目錄底下所有的檔案屬性。
(我在圖中輸入的 ll 就是 ls -la 的縮寫,這就與本次的主題無關,就不教如何使用了)

這時後檢查一下 build.sh 的檔案屬性有沒有如下的x
-rwxr-xr-x@ 1 darktt staff 1159 Jul 24 20:46 build.sh

如果沒有的話就請輸入以下指令:
chmod +x build.sh

這樣子就可以改變它的屬性了。

前面的準備工作都做完了,接下來就可以編譯了,
把剛剛開啟的終端機輸入以下指令,
./build.sh
這時候就能看到有一堆字跑出來,
如果看到的字是這樣子的話
xcodebuild: error: SDK "iphoneos7.0" cannot be located.
這就要修改一開始的 IOSSDK_VER="7.0" 了,
現在的最新版本是 7.1,所以要將它改成 7.1。

最後如果有看到
** BUILD SUCCEEDED **
這個的話那就代表正常結束了,

這時候就能看到多了一個資料夾,
這裡面就能找到 GPUImage.framework,
另外在 Release-iphoneos 能找到 libGPUImage.a 這個檔案,
(如果你只要使用模擬器的話就去 Release-iphonesimulator 找)
這兩個就是要放入你的專案中的檔案。

這樣子準備工作就全部結束了,
下次開始就要教正式的使用了,
下次再見。

2014年7月19日 星期六

GPUImage 滿漢大餐:序



這次是 GPUImage 的全套教學,
我會想這麼做的原因是,最近我有在用 GPUImage 開發一個 app,
但是在開發的過程中發現 GPUImage 的中文資源貧乏,並且它自己的說明文件也不齊全,
所以就想說我自己來做這一整套的教學了,不過這個教學是沒有截止的期限 XDD,
因此最好祈禱我不要中途而費。

什麼是 GPUImage?

GPUImage 是一個第三方的 API,
它整合了 OpenGL 與 AVFoundation,
並且支援 iOS 與 Mac 雙平台,
為了做到 Apple 一直不想做的濾鏡 (Filter) 的功能,

為什麼做是 Apple 不想做了?
因為根據 CoreImage 的 Framework 所提供的 CIFilter 只能做到一些陽春的濾鏡效果,
並且可修改的參數一大堆,導致開發者要一次又一次的測試,才能做出一個滿意的濾鏡較果,
這中間需要消耗大量個時間去做測試,這不是任何一位開發者願意看到的結果。

而 GPUImage 做到了,它提供更容易使用的環境,
並且結合了 AVFoundation 的相機、影像播放等功能、
讓開發者能做出一個相機的 app 並且能同時使用濾鏡做即時預覽的功能,
這是它強大之處。

章節

首先我先大概說明 GPUImage 的章節內容,
GPUImage 分為靜態圖像與動態影片兩個部分,
所以大章節就用這兩個做區分。

  1. 基礎上手篇:
    1. 下載 GPUImage
    2. 編譯成 Framework 來使用
  2. 相機篇:
    1. 做一個你自己的相機,用 GPUImage
    2. 用濾鏡處理你的相片
  3. 錄影篇:
    1. 來個即時濾鏡的攝影機,並且錄下你的生活
    2. 將你的生活影片染上不同的顏色與效果吧

目前是打算這麼做,
未來這一篇文章將會成為後續文章的索引,
當有後續文章出來之後我會將它加上連結,
方便大家使用。

2014年6月16日 星期一

Block in Swift

第一次學習 Block 就上手中教了在 Objective-C 的 Block 作法,
不過在 Swift 裡的作法又變了一個,所以才做了這個教學,
其實 Swift 的 Block 不叫做 Block,叫做 Closures,
它是一個將 Method 當做一個變數使用的應用。

一般的宣告方法如下
var aClosures: (NSString) -> Void = {string in
	NSLog("String: %@", string)
}

Closures 比較特別的是基本宣告都是宣告傳入或傳出的變數型態,
型態所對應變數名稱是在實作的時候才給予,這在基本宣告的時候還可以,
不過下面這個宣告方法就有可能看不懂了。

將 Closures 定義成一個型態之後再宣告
typealias bClosures = (NSNumber!) -> Void

var _bClosures:bClosures = {number in
	 NSLog("Number: %@", number)
;}

這時候如果不看這個型態內部定義的變數的話,就會完全不懂 number 這個變數是啥型態了,
這只能說 Swift 的風格特殊了。

接下來,有宣告當然就要有實作 Closures 的 Method,
其實實作 Method 的方法與 Block 的實作 Method 差異比較小,也可以說作法幾乎是相同的。

func methodWithaClosures(aClosures:(NSString) -> Void)
{
	NSLog("String: %@", string)
}

func methodWithbClosures(closures:bClosures)
{
	closures(NSNumber.numberWithInteger(150))
}

有實作了 Method,這時候就要回到上面將宣告好的 Closures 傳進去了。

var aClosures: (NSString) -> Void = {string in
	NSLog("String: %@", string)
}

typealias bClosures = (NSNumber!) -> Void

var _bClosures:bClosures = {number in
	 NSLog("Number: %@", number)
;}

self.methodWithaClosures(aClosures)

self.methodWithbClosures(_bClosures)

教學就到這邊,下次再看看有哪個東西需要教的了。

2014年5月15日 星期四

Class Method (三):Signleton 改 - 在多執行續的解法

在上一篇文章的 Class Method (二):Signleton 有在多執行續使用上的問題,
所以追加教學在多執行續中的 Signleton 的作法。

在舊的 signleton 的作法是這個樣子:
+ (id)method
{
	static Method *singleton = nil;
	if (singleton == nil) {
		singleton = [[Method alloc] init];
	}
	return singleton;
}

這在多執行續之下會有每個執行續都建立一個相同的 signleton 的問題,
所以要避免這個問題,就需要改寫 signleton 的寫法。

新個寫法如下,利用 GCD 的 dispatch_once 的方法來確保,
不管在哪個執行續下都是只有一個 signleton 的物件存在。
+ (id)method
{
	static Method *singleton = nil;
	
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		singleton = [DTFileController new];
	});
	
	return singleton;
}

2014年5月1日 星期四

從圖片檔案讀取 EXIF 資訊

這次是要如何從檔案中讀取一個 EXIF 資訊回來,
EXIF 是保存由相機拍攝下來的資料,
所以通常只要是相機拍的數位相片都有這項資訊,
但是要怎麼取出這些資訊了?
作法請看下面的教學

首先這個教學需要使用 ImageIO 的 framework,所以自己要實作的時候要記得將他 import 進來。

- (NSDictionary *)imagePropertiesWithPath:(NSString *)path
{
	// 將檔案的路徑轉成 NSURL 物件
	NSURL *imageFileURL = [NSURL fileURLWithPath:path];
	// 讀取這個檔案的資訊
	CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageFileURL, NULL);
	
	/* 設定準備讀資料的選項,這裡是不要將檔案使用快取的方式,
		這是避免檔案過大,而消耗過多記憶體空間的問題。 */
	NSDictionary *options = @{(id)kCGImageSourceShouldCache: @(NO)};
	
	// 從檔案資訊中擷取關於圖片的資料
	CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (CFDictionaryRef)options);
	
	CFRelease(imageSource);
	
	return (NSDictionary *)imageProperties;
}

- (void)viewDidLoad
{
	~
	// 取得剛剛的圖片資訊
	NSDictionary *imageProperties = [self imagePropertiesWithPath:_filePath];
	
	// 再從中取出 EXIF 資訊
	NSDictionary *EXIFInformation = imageProperties[(id)kCGImagePropertyExifDictionary];
	
	// 取出 EXIF Auxiliary 資訊
	NSDictionary *EXIFAuxiliary   = imageProperties[(id)kCGImagePropertyExifAuxDictionary];
	
	~
}

這樣子就能取得 EXIF 資訊,另外還有 GPS 資訊也是可以在 imageProperties 中取得,
另外,這次的範例檔:https://github.com/Darktt/EXIF-Reader
範例檔的使用方法,建議使用模擬器,先執行過一次,在 Log 中能找到

**** Directory Path: 

這個資訊,這後面就是接著你要放數位照片的路徑,
請用 Finder 打開這個路徑,放入你要讀取的照片,
重新執行這個程式,這時候就會讀到這張照片的檔名了。

2014年1月5日 星期日

隱藏 UITableViewCell 的 Edit Control [For iOS 7]

根據之前的 隱藏 UITableViewCell 的 Edit Control 做到了隱藏的效果了,
不過這個作法在 iOS 7 上卻失效了,因為 iOS 7 的 Cell 的 Subview 結構改變了,
所以之前的教學就失效了,因此再度為 iOS 7 多開一次教學。

首先照上次的教學先做一次看看現在的 Cell 的 Subview 結構變成了什麼?

在 iOS 7 的時候 Cell 的 Subview 竟然只剩下 UITableViewCellScrollView 一個!
那個其他的 view 去哪了?

不管怎樣,先看看 UITableViewCellScrollView 裡面有什麼東西,
用跟上次一樣的作法,log 看看 UITableViewCellScrollView 有什麼 subviews
- (void)layoutSubviews
{
	[super layoutSubviews];
	
	if (self.editing) {
		UIView *subview = self.subviews.lastObject;
		
		if ([self.subviews count] == 1 && [NSStringFromClass([subview class]) isEqualToString:@"UITableViewCellScrollView"]) {
			NSLog(@"%@", subview.subviews);
		}
	}
	
}

結果如下,竟然! UITableViewCellScrollView 內部是包著那些消失的 View!

既然這樣子就照著之前的作法將它給隱藏起來。
- (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;
		}
	};
	
	UIView *subview = self.subviews.lastObject;
	
	if ([self.subviews count] == 1 && [NSStringFromClass([subview class]) isEqualToString:@"UITableViewCellScrollView"]) {
		[subview.subviews NSEnumerationReverse
	}
}

如果你需要支援 iOS7 以下的版本的話就可以這樣子用。
- (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;
		}
	};
	
	UIView *subview = self.subviews.lastObject;
	
	if ([self.subviews count] == 1 && [NSStringFromClass([subview class]) isEqualToString:@"UITableViewCellScrollView"]) {
		[subview.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:enumBlock];
		
		return;
	}
	
	[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:enumBlock];
}

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"
才能使用,

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