顯示具有 學習筆記 標籤的文章。 顯示所有文章
顯示具有 學習筆記 標籤的文章。 顯示所有文章

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

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

2012年12月31日 星期一

Class Method (二):Signleton

如果你有要在多執行續使用 signletion 的話,請改看這篇文章。
Class Method (三):Signleton 改 - 在多執行續的解法

之前有說到 Class Method 還有另一種用法,就是 Singleton,
它是在第一次宣告這個 Class 取得記憶體位置之後,
之後的宣告都不會改變它的記憶體位置的使用方法,
假設現在有兩個 Class,一個是 aClass、另一個是 bClass,還有一個使用 Singleton 方法的 cClass
aClass 與 bClass 都會宣告 cClass,
這時候不管是哪個 Class 先宣告,另一個 Class 裡的都會使用到相同記憶體位置的 cClass,
像是需要保持資料庫的連結時就會這麼做,
在 Objective-C 中內建的 Class 中,UIDevice、NSBundle 等等都有這種用法,
說明就到此為止,接下來就教怎麼做。

首先與上一篇的建立 Class Method 的方法一樣,
不過比較不同的的是 Singleton 通常不會有傳值的動作。

Method.h
+ (id)method;

建立完了以後就是來實作它了

Method.m
+ (id)method
{
	static Method *singleton = nil;
	if (singleton == nil) {
		singleton = [[Method alloc] init];
	}
	return singleton;
}

Singleton 與一般的 Class Method 的差別是 Singleton 完全不需要 release,
因為它的記憶體位置是需要被保留下來的,
所以之後它只要在第一次宣告之後,
未來的宣告都會使用相同的記憶體位置。

這次的教學就到此為止,下次等我有想到有不同的教學再來吧。

2012年12月30日 星期日

Class Method (一)

有一段時間沒有更新這個網誌了
剛好趁跨年假期來打個一篇
這次是 Class Method,這是大部分的書本沒交到的使用方法,
不過要是學會的話會愛不釋手,因為可以省下打一堆字,
下面就開始這次的教學了。

這次是 Class Method

什麼是 Class Method ?
在 Objective-C 中的 [NSString string] 等方法就是 Class Method。
它同時也是符合 Auto Release 的功能。

那麼要如何寫一個 Class Method 呢?
其實很簡單,只要將一般的 Method 的開頭減號改成加號即可。
不過實際的內容要怎麼寫呢?

假設有兩個檔案為 Method.h 與 Method.m
一個 Class Method 都會是對外公開的 Method
所以 Method 的名稱都會打在 h 檔中

Method.h
+ (id)aMethod; // 這是一個不需要傳入值的 Class Method
+ (id)aMethodWithValue:(NSInteger)integer; // 這是一個需要傳入值的 Class Method

實作 Class Method 有三種方法:
一是直接回傳其他 Class 的 Class Method,這個就不用說了,
二是自己建立自己的 Class Method,這個是這次教學的重點,
三是建立一個 singleton 的 Class Method,這算是 Class Method 的一種變形,在下個教學會教到。

Method.m
+ (id)aMethod
{
	Method *method = [[[Method alloc] init] autoRelease];
	// 其實這跟一般的 Class 宣告方法一樣,
	return method;}

+ (id)aMethodWithValue:(NSInteger)integer
{
	Method *method = [[[Method alloc] initWithValue:integer] autoRelease];
	// 有傳值的 Class Method 就比較特別了,這要看你傳入的值要給哪個初始化的 Method,如果沒有這個 Method 就要自己建立一個了。
	return method;
}

這樣子在其他 Class 裡要用到這個 Class 的功能時就可以這樣子宣告,

Method *method = [Method aMethod];
或
Method *method = [Method aMethodWithValue:0];

這樣子就比一般 Class 宣告的方法短多了,而且又符合 Auto Release 的條件,
就不會忘了這個變數需要 Release 的問題。

未完續待