since: 2012/09/06
update: 2012/09/06
reference:
I touchs: Filter4Cam 學習之 Core Image Filter With CATransition
A. 說明
1. 使用
轉場特效的時機:
a. 原始影像 -> 套用濾鏡.
b. 套用濾鏡的影像 -> 恢復成原始影像
c. 套用 A 濾鏡 -> 套用 B 濾鏡
2. 在這邊有
二個時間要處理: 一是 "
執行轉場特效" 所花的時間(將設為 1.5 秒),
另外是, 開始執行轉場特效多久後, 會去作
濾鏡的套用.(將設為 0.75 秒)
3. 在轉場的過程中, 當
轉場還未完全結束時(即使看起來好像結束了), 此時去點選
濾鏡應該是不能發生作用的.
a. 因此需要在 ViewController 裡宣告一個可用來
決定是否可點選濾鏡的
自定 protocol 變數:
id<TableSelectionDelegate> tsDelegate;
並且將此工作
委派(delegate)給存放濾鏡 Cell 的 Table (
HorizontalTableCell):
// ViewController => tableView:cellForRowAtIndexPath:
self.tsDelegate = cell;
b. 接著, 在存放濾鏡 Cell 的 Table 類別 (HorizontalTableCell) 裡, 宣告依循
<TableSelectionDelegate> 協定, 並實作 "
是否可點選濾鏡" 的功能:
- (void)allowsTableSelection:(BOOL)allows
{
self.currentTableView.allowsSelection = allows;
}
c. 備註:
原本在 ViewController.h 裡, 直接定義 <TableSelectionDelegate> 協定,
但是 HorizontalTableCell 類別卻無法參照到, 可能是 Xcode 本身的問題,
因此將
<TableSelectionDelegate> 協定, 放到一個新增的
header file 來處理.
-------------------------------------------------------------------------------
B. 新增 "可否點選濾鏡表單之代理" 協定
1. Xcode >
File >
New >
File...
iOS >
Cocoa Touch >
Objective-C protocol >
Next
Protocol: TableSelectionDelegate
>
Next
Targets: Filter4Cam (
checked)
>
Create
2. 開啟 TableSelectionDelegate.h 檔案, 修改如下:
#import <Foundation/Foundation.h>
// "可否點選濾鏡表單之代理" 協定
@protocol TableSelectionDelegate <NSObject>
//@add
- (void)allowsTableSelection:(BOOL)allows;
@end
-------------------------------------------------------------------------------
C. 新增用來標示 "是否要作轉場特效" 的全域變數
1. 說明: (以下將在之後一一處理)
a. 在 HorizontalTableCell.m 的
tableView:didSelectRowAtIndexPath: 方法裡,
無論是 "
選取濾鏡" 或 "
取消濾鏡", 都會標示要作 "
轉場特效":
self.dataObj.startTransition = YES;
b. 接著, 在 ViewController.m 的
captureOutput:didOutputSampleBuffer:fromConnection: 方法裡,
會檢查是否要作轉場特效:
[self checkTransition];
c. 承上, 如果是的話, 就會開始執行轉場特效:
[self startAnimation];
當轉場特效
結束時, 會自動呼叫:
animationDidStop:finished: 方法,
並且將 "
是否要作轉場特效" 標示為否:
self.dataObj.startTransition = NO;
2. 開啟 GlobalDataClass.h 檔案, 修改如下:
....
@interface GlobalDataClass : NSObject
{
//@add: array for storage filterCells
NSMutableArray *filterCellArray;
BOOL isUsingFilter; // 有使用濾鏡?
BOOL startTransition; // 標示: 是否啟用轉場特效?
....
}
//@add
@property (nonatomic, strong) NSMutableArray *filterCellArray;
@property (assign) BOOL isUsingFilter;
@property (assign) BOOL startTransition;
....
3. 開啟 GlobalDataClass.m 檔案, 修改如下:
....
//@add
@synthesize filterCellArray = _filterCellArray;
@synthesize isUsingFilter = _isUsingFilter;
@synthesize startTransition = _startTransition;
....
-------------------------------------------------------------------------------
D. 轉場特效
1. 開啟 ViewController.h 檔案, 修改如下:
(檢查: 必須要 #import <QuartzCore/QuartzCore.h>)
....
//@add
#import "Filter4CamHelper.h"
#import "ConstantDefined.h"
#import "HorizontalTableCell.h"
#import "GlobalDataClass.h"
#import "TableSelectionDelegate.h"
....
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate, UITableViewDelegate, UITableViewDataSource>
{
....
//@add for CATransition
BOOL transitioning; // 是否正在作轉場特效?
//@add for delegate used
id<TableSelectionDelegate> tsDelegate;
}
....
@property (assign) BOOL isUsingFrontCamera;
@property (assign) BOOL isLastFrontCamera;
@property (assign) BOOL transitioning;
@property (assign) CGRect destRect;
@property (assign) /* weak ref */ id<TableSelectionDelegate> tsDelegate;
....
//@add: functional buttons
- (void)savePhoto; // "拍照"
- (void)switchCameras; // "鏡頭切換"
- (void)switchLight; // "閃光燈切換"
- (void)switchObserver; // "觀察者模式"
- (void)checkTransition; // 檢查是否要作轉場特效
- (void)startAnimation; // 開始執行轉場特效
@end
2. 開啟 ViewController.m 檔案, 修改如下:
....
// step 01:
@synthesize isUsingFrontCamera = _isUsingFrontCamera;
@synthesize isLastFrontCamera = _isLastFrontCamera;
@synthesize transitioning = _transitioning;
....
//@add
@synthesize destRect = _destRect;
@synthesize tsDelegate = _tsDelegate;
....
// step 02:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//@update: use reusableCells
HorizontalTableCell *cell = [self.reusableCells objectAtIndex:indexPath.section];
// 說明: 一般是在 AppDelegate 中, 設定 delegate 的關係, 在此處剛好可利用
self.tsDelegate = cell;
return cell;
}
....
// step 03:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
....
//@add: 檢查是否要作轉場特效
[self checkTransition];
if(self.dataObj.isUsingFilter == YES)
{
if (self.currentFilter = [self.dataObj.filterDictionary objectForKey:self.dataObj.filterID])
{
self.ciImage = [self.currentFilter filterImage:self.ciImage];
}
}
....
}
....
// step 04:
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataObj.isUsingFilter = NO;
self.dataObj.startTransition = NO; // 預設: 不進行轉場特效
self.isUsingFrontCamera = NO; // 預設為後置鏡頭
self.isLastFrontCamera = NO; // 預設上次為後置鏡頭
self.transitioning = NO; // 目前沒有正在作轉場特效
lastOrientation = Unknown; // 設備上次的擺放方向: 未知
....
}
....
// step 05:
- (void)viewDidUnload
{
....
self.observerButton = nil;
self.dataObj = nil;
self.currentFilter = nil;
self.tsDelegate = nil;
}
....
// step 06:
// 檢查是否要作轉場特效
- (void)checkTransition
{
// 要開始進行轉場特效
if (self.dataObj.startTransition)
{
[self startAnimation];
}
}
....
// step 07:
// 開始執行轉場特效
- (void)startAnimation
{
// 先讓濾鏡表單無法選取
[self.tsDelegate allowsTableSelection:NO];
// 如果, 目前不是正在進行 "轉場特效" 的話, 就開始進行
if(!self.transitioning)
{
CATransition *transition = [CATransition animation];
transition.duration = 1.50; // 轉場的時間 (秒)
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = @"pageCurl"; // 翻頁(由下往上)
self.transitioning = YES;
transition.delegate = self;
//[self.view.layer addAnimation:transition forKey:nil]; // 亦可
[self.glView.layer addAnimation:transition forKey:nil];
}
}
....
// step 08:
//@add for CATransition(轉場特效結束時, 會自動呼叫)
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
// "轉場特效" 進行完畢
self.transitioning = NO;
// 停止進行轉場特效(標示為否)
self.dataObj.startTransition = NO;
// 恢復濾鏡表單可選取狀態
[self.tsDelegate allowsTableSelection:YES];
}
....
-------------------------------------------------------------------------------
E. 延緩濾鏡套用
1. 開啟 HorizontalTableCell.h 檔案, 修改如下:
....
#import "GlobalDataClass.h"
#import "TableSelectionDelegate.h"
//@interface HorizontalTableCell : UITableViewCell <UITableViewDelegate, UITableViewDataSource>
//@update
@interface HorizontalTableCell : UITableViewCell <UITableViewDelegate, UITableViewDataSource, TableSelectionDelegate>
{
....
NSIndexPath *lastIndexPath;
NSIndexPath *currentIndexPath; // 目前所點選的 table 之 IndexPath
UITableView *currentTableView; // 目前所點選的 Table
GlobalDataClass *dataObj;
}
....
@property (nonatomic, strong) NSIndexPath *lastIndexPath;
@property (nonatomic, strong) NSIndexPath *currentIndexPath;
@property (nonatomic, strong) UITableView *currentTableView;
@property (nonatomic, strong) GlobalDataClass *dataObj;
//@add: 設定 "選取 / 取消選取 濾鏡 Cell" 的行程
- (void)setSelectProgress;
//@add: 進行 "選取 / 取消選取 濾鏡 Cell" 的行程
- (void)goSelectProgress:(NSTimer *)theTimer;
@end
2. 開啟 HorizontalTableCell.m 檔案, 修改如下:
....
// step 01:
@synthesize lastIndexPath = _lastIndexPath;
@synthesize currentIndexPath = _currentIndexPath;
@synthesize currentTableView = _currentTableView;
@synthesize dataObj = _dataObj;
....
// step 02:
#pragma mark Getter
....
- (NSIndexPath *)currentIndexPath
{
if (_currentIndexPath == nil) {
_currentIndexPath = [NSIndexPath indexPathForRow:-1 inSection:0];
}
return _currentIndexPath;
}
- (UITableView *)currentTableView
{
if (_currentTableView == nil) {
_currentTableView = [[UITableView alloc] init];
}
return _currentTableView;
}
....
// step 03:
- (void)dealloc
{
self.filterTableView = nil;
self.filters = nil;
self.lastIndexPath = nil;
self.dataObj = nil;
self.currentIndexPath = nil;
self.currentTableView = nil;
}
....
// step 04:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 說明:
// 1. GlobalDataClass 的變數 startTransition, 最初在 ViewController 的
// viewDidLoad 裡, 設值為 NO (即預設: 不進行轉場特效)
//
// 2. 當第一次點選濾鏡時, 會進入到本方法裡,
// a. 先設定: self.dataObj.startTransition = YES; 標示要作 "轉場特效".
// 在 ViewController 的
// captureOutput:didOutputSampleBuffer:fromConnection: 裡,
// 先檢查是否要作轉場特效(checkTransition), 接著開始執行轉場特效
// (startAnimation), 轉場特效結束時, 會自動呼叫 CATransition 的
// animationDidStop:finished: 方法
//
// b. 接著設定: "選取 / 取消選取 濾鏡 Cell" 的行程 (setSelectProgress),
// 來延遲套用濾鏡的時間.
//
// 3. 時間的設定: 轉場特效 1.50 秒 (ViewController -> startAnimation)
// 套用濾鏡的延遲時間: 0.75 秒 ([self setSelectProgress])
// 如果在進行的 "轉場特效" 尚未結束, 又點選了濾鏡
// (實際上, 此時是無法點選的, 只能對上層的 Table 作捲動)
if (self.dataObj.startTransition)
{
// 說明:
// 當 startTransition = YES, 在 ViewController 的 startAnimation 中,
// 呼叫了: [self.tsDelegate allowsTableSelection:NO];
//
// 即在此設定了 self.currentTableView.allowsSelection = NO;
// 就算去點選濾鏡 Cell, 也不會呼叫 tableView:didSelectRowAtIndexPath: 方法,
// 所以此區塊永遠不會執行到.
NSLog(@"You will never see this message ~");
return;
}
self.currentIndexPath = indexPath;
self.currentTableView = tableView;
// 標示要作 "轉場特效"
self.dataObj.startTransition = YES;
// 設定 "選取 / 取消選取 濾鏡 Cell" 的行程
[self setSelectProgress];
}
....
// step 05:
//@add: 設定 "選取 / 取消選取 濾鏡 Cell" 的行程
- (void)setSelectProgress
{
[NSTimer scheduledTimerWithTimeInterval:0.75f
target:self
selector:@selector(goSelectProgress:)
userInfo:nil
repeats:YES];
}
.....
// step 06:
//@add: 進行 "選取 / 取消選取 濾鏡 Cell" 的行程
- (void)goSelectProgress:(NSTimer *)theTimer {
// 設定 timer 過期 (所以, 只會執行此一次)
// 或可將 setSelectProgress 裡的 repeats: 設成 NO
[theTimer invalidate];
int newRow = [self.currentIndexPath row];
int oldRow = [self.lastIndexPath row];
//NSLog(@"oldRow = %d", oldRow);
//NSLog(@"newRow = %d", newRow);
// 選取濾鏡
if (newRow != oldRow)
{
self.lastIndexPath = self.currentIndexPath;
// 套用濾鏡功能
/* (此方式亦可)
FilterCell *cell = (FilterCell *)[tableView cellForRowAtIndexPath:indexPath];
NSLog(@"FilterID = %@, Title = %@", cell.filterID, cell.titleLabel.text);
*/
NSDictionary *currentFilter = [self.filters objectAtIndex:self.currentIndexPath.row];
/*
NSString *FilterID = [currentFilter objectForKey:@"FilterID"];
NSString *Title = [currentFilter objectForKey:@"Title"];
NSString *ImageName = [currentFilter objectForKey:@"ImageName"];
NSLog(@"FilterID = %@, Title = %@, ImageName = %@", FilterID, Title, ImageName);
*/
self.dataObj.isUsingFilter = YES;
self.dataObj.filterID = [NSMutableString stringWithString:[currentFilter objectForKey:@"FilterID"]];
}
// 取消選取濾鏡
else
{
//self.lastIndexPath = nil; // -> not work at section:0 row:0
self.lastIndexPath = [NSIndexPath indexPathForRow:-1 inSection:0];
[self.currentTableView deselectRowAtIndexPath:self.currentIndexPath animated:NO];
// 恢復原始影像
self.dataObj.isUsingFilter = NO;
self.dataObj.filterID = [NSMutableString stringWithString:@"NoID"];
}
NSLog(@"filterID = %@", self.dataObj.filterID);
}
....
// step 07:
//@add for <TableSelectionDelegate> protocol method
- (void)allowsTableSelection:(BOOL)allows
{
// 說明: 當 allows = NO 時, 即使去點選濾鏡 Cell, 也不會執行:
// tableView:didSelectRowAtIndexPath: 方法
self.currentTableView.allowsSelection = allows;
}
....
-------------------------------------------------------------------------------
F. 編譯並執行: