2014年2月21日 星期五

偵測 Pinch

Pinch 手勢最常見的地方就是用在將照片放大(兩只外撥)或縮小(兩指內撥)。

UIPinchGestureRecognizer 為一個連續性的手勢辨識器,因為它會在 Pinch 期間不斷的去呼叫它的動作方法。

我們來看看程式碼:
#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *lable;

@property (assign, nonatomic) CGFloat initialFontSize;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self 
                                                                                action:@selector(doPinch:)];
    [self.view addGestureRecognizer:pinch];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    _lable = nil;
}

#pragma mark - Added methods

- (void)doPinch:(UIPinchGestureRecognizer *)pinch {

    if (pinch.state == UIGestureRecognizerStateBegan) {
        _initialFontSize = _lable.font.pointSize;
    } else {
        _lable.font = [_lable.font fontWithSize:_initialFontSize * pinch.scale];
    }
}

@end
 viewDidLoad 裡,我們設定了一個 UIPinchGestureRecognizer,並告訴它發生此動作時透過 doPinch: 方法來通知我們。

 doPinch: 裡,我們先查看 Pinch 的狀態是否處於剛開始階段,如果是,就將目前的字體大小儲存起來。
若不是(Pinch 已在進行中),就用儲存的字體大小,以及目前 Pinch 的幅度(scale)來計算新的字體大小。

偵測多指 Tap

我們可以藉由 UITapGestureRecognizer 來實作多重點按這個手勢。
它可以設定成,發生特定點按次數時,執行指定的方法。

但這邊有個問題,假設我們想在一個 View 上實作 user 點按 1 次跟連點 2 次時會發生不同動作,
程式碼大概是這樣:
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap1)];
    singleTap.numberOfTapsRequired = 1;
    [self.view addGestureRecognizer:singleTap];
    
    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap2)];
    doubleTap.numberOfTapsRequired = 2;
    [self.view addGestureRecognizer:doubleTap];
問題在於兩個辨識器並不知道彼此的存在,所以當 user 執行連按 2 次這個動作時,
不只 tap2 這個 method 會被呼叫,連 tap1 也會被呼叫,而且是兩次,

解決方法是建立個失敗的(failure)的需求,告訴 tap1 只有在 tap2 無法辨識及回應 user 的輸入時,
才會去觸發其動作。

    [singleTap requireGestureRecognizerToFail:doubleTap];

實作:
#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *singleLabel;
@property (weak, nonatomic) IBOutlet UILabel *doubleLabel;
@property (weak, nonatomic) IBOutlet UILabel *tripleLabel;
@property (weak, nonatomic) IBOutlet UILabel *quadrupleLabel;

- (void)tap1;
- (void)tap2;
- (void)tap3;
- (void)tap4;

- (void)eraseMe:(UILabel *)label;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib
    
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap1)];
    singleTap.numberOfTapsRequired = 1;
    singleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:singleTap];
    
    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap2)];
    doubleTap.numberOfTapsRequired = 2;
    doubleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:doubleTap];
    [singleTap requireGestureRecognizerToFail:doubleTap];
    
    UITapGestureRecognizer *tripleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap3)];
    tripleTap.numberOfTapsRequired = 3;
    tripleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:tripleTap];
    [doubleTap requireGestureRecognizerToFail:tripleTap];
    
    UITapGestureRecognizer *quadrupleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap4)];
    quadrupleTap.numberOfTapsRequired = 4;
    quadrupleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:quadrupleTap];
    [tripleTap requireGestureRecognizerToFail:quadrupleTap];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    _singleLabel = nil;
    _doubleLabel = nil;
    _tripleLabel = nil;
    _quadrupleLabel = nil;
}

#pragma mark - Added methods

- (void)tap1 {

    _singleLabel.text = @"Single Tap Detected";
    [self performSelector:@selector(eraseMe:) withObject:_singleLabel afterDelay:1.6f];
}

- (void)tap2 {
    _doubleLabel.text = @"Double Tap Detected";
    [self performSelector:@selector(eraseMe:) withObject:_doubleLabel afterDelay:1.6f];
}

- (void)tap3 {
    _tripleLabel.text = @"Triple Tap Detected";
    [self performSelector:@selector(eraseMe:) withObject:_tripleLabel afterDelay:1.6f];
}

- (void)tap4 {
    _quadrupleLabel.text = @"Quadruple Tap Detected";
    [self performSelector:@selector(eraseMe:) withObject:_quadrupleLabel afterDelay:1.6f];
}

- (void)eraseMe:(UILabel *)label {
    label.text = @"";
}

@end
以上我們可看出,當 user 點按 1 下螢幕時,
singleTap 需要等到 doubleTap 的失敗才能觸發自己的事件,
如果 user 在系統設定的點按間隔時間內按下第二次,
則此手勢便會視為點按 2 次,並跳過 singleTap 而去執行 doubleTap 所指定的動作。
反之,若 user 沒在點按間隔時間內按下第二次,
則 doubleTap 會告訴 singleTap 它失敗了,singleTap 才會開始執行其動作。

以此類推,doubleTap 需要 tripleTap 的失敗、tripleTap 需要 quadrupleTap 的失敗。

偵測多指 Swipe

之前實作過的程式,都是使用單指滑動,我們只是從 touches 集合中抓出任一個物件來計算滑動期間 user 的手指位置。
但如果我們想偵測 2 指或 3 指的滑動呢?

UISwipeGestureRecognizer 可以設定任何數目的同時觸碰。

根據預設,每個實體都是預期一個手指,但我們可以設定它去找尋一次按在螢幕上的任何手指數目。

在實作三指滑動時,記得將 設定 -> 一般 -> 輔助使用 -> 縮放 給關閉,因為縮放預設是使用三指手勢去操控,且於系統裡有較高的優先順序。

程式碼:
#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *label;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    for (NSUInteger touchCount = 1; touchCount <= 5; touchCount++) {
        
        UISwipeGestureRecognizer *vertical = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                                       action:@selector(reportVerticalSwipe:)];
        vertical.direction = UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown;
        vertical.numberOfTouchesRequired = touchCount;
        [self.view addGestureRecognizer:vertical];
        
        UISwipeGestureRecognizer *horizontal = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                                         action:@selector(reportHorizontalSwipe:)];
        horizontal.direction = UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;
        horizontal.numberOfTouchesRequired = touchCount;
        [self.view addGestureRecognizer:horizontal];
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    _label = nil;
}

#pragma mark - Added methods

- (void)eraseText {
    _label.text = @"";
}

- (NSString *)descriptionForTouchCount:(NSUInteger)touchCount {
    
    switch (touchCount) {
        case 2:
            return @"Double";
        case 3:
            return @"Triple";
        case 4:
            return @"Quadruple";
        case 5:
            return @"Quintuple";
            
        default:
            return @"";
    }
}

- (void)reportHorizontalSwipe:(UIGestureRecognizer *)recognizer {

    _label.text = [NSString stringWithFormat:@"%@ Horizontal swipe detected”, [self descriptionForTouchCount:[recognizer numberOfTouches]]];
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

- (void)reportVerticalSwipe:(UIGestureRecognizer *)recognizer {
    
    _label.text = [NSString stringWithFormat:@"%@ Vertical swipe detected”, [self descriptionForTouchCount:[recognizer numberOfTouches]]];
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

@end
解釋:
首先,我們以 for 迴圈去指定 vertical、horizontal 實體會回應的切確數目。
ex.
     UISwipeGestureRecognizer *vertical = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                               action:@selector(reportVerticalSwipe:)];
     vertical.direction = UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown;
     vertical.numberOfTouchesRequired = 3;   <—— 注意這
     [self.view addGestureRecognizer:vertical];
其中 [recognizer numberOfTouches] 因為 user 使用 3 指做觸碰,所以其值為 3。
接著它會呼叫 descriptionForTouchCount: 方法,並將 3 傳入。
- (NSString *)descriptionForTouchCount:(NSUInteger)touchCount {
    switch (touchCount) {
        case 2:
            return @"Double";
        case 3:
            return @"Triple";
        case 4:
            return @"Quadruple";
        case 5:
            return @"Quintuple";
        
        default:
            return @"";
    }
}
descriptionForTouchCount: 因為接收到 3 這個 touchCount 參數,
所以會回傳 @“Triple” 字串,再回到 reportVerticalSwipe: 裡...

_label.text = [NSString stringWithFormat:@"%@ Vertical swipe detected”
                                        [self descriptionForTouchCount:[recognizer numberOfTouches]]];

此時 _label.text 所接受到的字串值為 Triple Vertical swipe detected,並將其顯示在螢幕上。

使用 iOS 內建的手勢辨識器

iOS 幫我們內建了好用的手勢辨識器UIGestureRecognizer 類別。
有了它就不需要觀察所有的事件來查看手指如何移動。

接下來我們要實作--偵測 Swipe(滑動)

先來看看沒有使用手勢辨識器的程式碼:
#import "ViewController.h"

#define kMinimumGestureLength   25
#define kMaximumVariance        5

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *label;
@property CGPoint gestureStartPoint;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    _label = nil;
}

#pragma mark - Added methods

- (void)eraseText {
    _label.text = @"";
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [touches anyObject];
    _gestureStartPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPosition = [touch locationInView:self.view];
    
    CGFloat deltaX = fabsf(_gestureStartPoint.x - currentPosition.x);
    CGFloat deltaY = fabsf(_gestureStartPoint.y - currentPosition.y);
    
    if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
        
        _label.text = @"Horizontal swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
        
    } else if (deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance) {
        
        _label.text = @"Vertical swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
    }
}

@end

#define kMinimumGestureLength   25
#define kMaximumVariance        5

首先,我們先定義一個以像素計算的最小手勢長度,user 必須滑超過這個距離,手勢才會被當成 Swipe 計算。
然後再定義一個變異量,它是 user 偏離直線的最大距離,只要手勢偏離的程度不超過它就會被當成水平或垂直滑動來計算。

以上我們所定義的最小手勢長度是 25 像素、變異值是 5 像素。
只要 user 水平移動超過 25 像素,則手勢和起點的垂直位置上下最多可差到 5 像素。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _gestureStartPoint = [touch locationInView:self.view];
}

以上method 主要是在抓取任何一個觸控,並儲存它的觸控點。
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPosition = [touch locationInView:self.view];
    
    CGFloat deltaX = fabsf(_gestureStartPoint.x - currentPosition.x);
    CGFloat deltaY = fabsf(_gestureStartPoint.y - currentPosition.y);
    
    if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
        
        _label.text = @"Horizontal swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
        
    } else if (deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance) {
        
        _label.text = @"Vertical swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
    }
}
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self.view];

取得 user 手指目前的位置。

CGFloat deltaX = fabsf(_gestureStartPoint.x - currentPosition.x);
CGFloat deltaY = fabsf(_gestureStartPoint.y - currentPosition.y);

計算 user 手指從其起點位置向水平和垂直方向個移動多少距離。

fabsf()函式是來自C算術程式庫,它會回傳一個 float 的絕對值。在此的用意是讓我們兩個值相減而不用擔心出現負值。
     if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
        
        _label.text = @"Horizontal swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
        
    } else if (deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance) {
        
        _label.text = @"Vertical swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
    }
檢查 user 是否在同一個方向移動超過最小值,且變異值小於另一個移動方向,已形成一個手勢。

[self performSelector:@selector(eraseText) withObject:nil afterDelay:2];

讓文字出現兩秒後將它清除。

手勢辨識器:
我們不會直接使用 UIGestureRecognizer,而是建立它的 subclass 的實體,這些實體每個都是為了特定的手勢所設計,
像是 Swipe、Tap、Pinch。

讓我們以手勢辨識器改寫上例程式:
#import "ViewController.h"

//#define kMinimumGestureLength   25
//#define kMaximumVariance        5

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *label;
//@property CGPoint gestureStartPoint;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UISwipeGestureRecognizer *vertical = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                                   action:@selector(reportVerticalSwipe:)];
    vertical.direction = UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown;
    [self.view addGestureRecognizer:vertical];
    
    UISwipeGestureRecognizer *horizontal = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                                     action:@selector(reportHorizontalSwipe:)];
    horizontal.direction = UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;
    [self.view addGestureRecognizer:horizontal];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
    _label = nil;
}

#pragma mark - Added methods

- (void)eraseText {
    _label.text = @"";
}

/*
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [touches anyObject];
    _gestureStartPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPosition = [touch locationInView:self.view];
    
    CGFloat deltaX = fabsf(_gestureStartPoint.x - currentPosition.x);
    CGFloat deltaY = fabsf(_gestureStartPoint.y - currentPosition.y);
    
    if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
        
        _label.text = @"Horizontal swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
        
    } else if (deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance) {
        
        _label.text = @"Vertical swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
    }
} */

- (void)reportHorizontalSwipe:(UIGestureRecognizer *)recognizer {

    _label.text = @"Horizontal swipe detected";
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

- (void)reportVerticalSwipe:(UIGestureRecognizer *)recognizer {
    
    _label.text = @"Vertical swipe detected";
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

@end
首先,我們將 touchesBegan:withEvent:、touchesMoved:withEvent: 刪除,
新增了 reportHorizontalSwipe:、reportVerticalSwipe:。
這兩個 method 實作了滑動手勢的功能,就像之前刪除的 touchesBegan:withEvent:、touchesMoved:withEvent: 一樣。

接著,在 viewDidLoad 中新增程式碼:
    UISwipeGestureRecognizer *vertical = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                                   action:@selector(reportVerticalSwipe:)];
    vertical.direction = UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown;
    [self.view addGestureRecognizer:vertical];
    
    UISwipeGestureRecognizer *horizontal = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                                                     action:@selector(reportHorizontalSwipe:)];
    horizontal.direction = UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight;
    [self.view addGestureRecognizer:horizontal];

以上我們可以看到,藉由 UISwipeGestureRecognizer 我們只需要建立並且設定一些手勢辨識器,
並將它增加到 View 中,即可完成跟上一個範例同樣的功能。
當 user 使用其中一種手勢辨識器認得的方法和螢幕互動時,我們指定的方法就會被呼叫。

2014年2月19日 星期三

點按、觸控與手勢

術語











gesture 手勢:

手勢是從我們以一根或多根手指觸碰螢幕到手指離開螢幕之間所發生的一連串事件。
不管時間過了多久,只要手指仍貼著螢幕,就還是在一個手勢動作中(除非因為來電這類的事件而中斷)。

touch 觸碰:

一根或多根手指放在螢幕上,然後拖離螢幕或提起來離開螢幕。
iPad 最多可同時處理 11 個觸控。

tap 點按:

以一根手指碰觸螢幕,然後沒有四處移動便立即將手指離開螢幕。
iOS 會記錄點按的次數,而且能判斷 user 是連點 2 下、3 下或甚至是 20 下。

Pinch 兩指撥動:

將照片放大(兩指外撥)或縮小(兩指內撥)的那種手勢。

gesture recognizer 手勢辨識器:

為一個物件。可幫助我們在使用者於預設手勢時的觸碰、拖曳時辨識出來。

UIGestureRecognizer 其子類別有:

UISwipeGestureRecognizer
UITapGestureRecognizer
UIPinchGestureRecognizer


四種觸控通知 method:

1.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

當 user 第一次接觸螢幕,系統會先尋找此方法回應程式。

我們可以藉由取得 touches 中的物件個數,來判斷目前 user 在螢幕上的手指個數。
在 touches 中每個物件都是一個 UITouch 事件,用來代表一根觸碰著螢幕的手指。

舉例:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSUInteger numTaps = [[touches anyObject] tapCount];
    NSUInteger numTouches = [touches count];
 }

如果 numTaps 的值是 2,代表螢幕很快的被連點兩次。
如果 numTouches 值是 2,代表 user 以兩根手指同時點按螢幕。
如果兩個值皆是 2,代表 user 以兩根手指連按 2 次。

2.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

當 user 的手指在螢幕上移動時,會呼叫的 method。

在一段很長的拖拉中,這個 method 會被呼叫很多次,而每次它被呼叫時,我們都可以獲得一個 touches 的集合、及一個 event。
除了可以從 UITouch 物件找到每根手指的目前位置外,還可以找出觸碰的前一個位置,
也就是上次 touchesMoved:withEvent: 或 touchesBegan:withEvent: 被呼叫時的手指位置。

3.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

當 user 完成一個手勢時,這個 method 會被呼叫。

4.
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

當 user 正在進行某個手勢,突然發生某事件而中斷他時(ex.來電),此 method 便會被呼叫。

我們可以在此進行任何清理動作,以便重新開始一個新的手勢。
當這個 method 被呼叫時,就不會為目前的手勢動作呼叫 touchesEnded:withEvent: 。

範例:https://github.com/HarrisFu/TouchExplorar

PS. 需要用到手勢的程式時,記得要開啟 View 的 Multiple touch 屬性。



2014年1月28日 星期二

iOS 的資料儲存方式


1. NSUserDefaults

2. plist

3. archiving (封存)

4. SQLite

5. Core Data


APP的 sandbox(沙箱):

除了 NSUserDefaults 外,其他資料保存方式都有同一共同點,也就是應用程式的 /Documents 資料夾。

每一個 APP 都有自己的 /Documents,而 APP 也只能從自己的 /Documents 進行讀寫。

此資料夾存在 /Library 中,在 Mac OS X 10.6 的版本後,Apple 將此資料夾預設為隱藏,

要進入此隱藏資料夾的方法為:

開啟 Finder -> 選擇 “前往” -> "前往資料夾”(快捷鍵:⇧⌘G)-> 輸入 ~/Library

-> Application Support / iPhone Simulator / 版本 / Applications / <UUID> / Documents


在程式中取得 Documents 路徑:

我們可以用以下程式碼來取得 Documents 的參考位址:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

NSDocumentDirectory:我們正在尋找至目錄 Documents 的路徑
NSUserDomainMask:將搜尋限制在我們的 APP sandbox 中

符合的路徑會以 Array 來回傳,且 Documents 目錄會在 Array 中 index 為 0 的位置,
因為每一個 APP 只會有一個 Documents 資料夾。

我們可在剛擷取的路徑後面加上一個字串來建立檔名,
可使用 stringByAppendingString 這個專為此用途設計的 NSString 方法,

NSString *filename = [documentsDirectory stringByAppendingPathComponent :@“theFile.txt”];

呼叫這個方法後,filename 將會包含一個我們 APP 的 Documents 目錄中,theFile.txt 檔案的完整路徑,
此時我們就可以利用 filename 來建立該檔案並進行讀取及寫入


取得 tmp 目錄:

tmp 為 APP 的暫存目錄,要取得其參考位址相對的比 Documents 的要簡單許多,
使用 NSTemporaryDirectory() 這個 Foundation 函式,就可以回傳我們 APP 的 tmp 目錄完整路徑的字串。

想要在暫存目錄建立檔案,我們可以用以下語法:

NSString *tmpfile = [NSTemporaryDirectory() stringByAppendingPathComponent :@"temp.txt”];


取得 JSON 格式的資料

iOS 5 後內建了對 JSON 的支援。
我們可以透過 NSJSONSerialization 這個類別將 JSON 格式的字串轉換成 NSArray 或 NSDictionary
也可以將這兩個類別的物件轉換成 JSON。

NSJSONSerialization 對於資料的格式有一些限制:

1. 只能轉換為 NSArray or NSDictionary
2. 每個 Array or Dictionary 內的元素只能為 NSStringNSNumberNSArrayNSDictionaryNSNull
3. NSDictionary 的 Key 只能為 NSString
4. NSNumber 不能為 Null

NSJSONSerialization 裡的方法都是靜態方法,大致上可分為兩類:

1. 將資料轉換為 JSON 物件
2. 將 JSON 物件轉換為資料

將資料轉換為 JSON 物件:
1. 將 NSData 物件轉換為 JSON 物件 NSDictionary or NSArray

+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

ex. 你可以讀取一個 JSON 的字串後,並將之轉換為 NSDictionary

NSString *str = [[NSString alloc] initWithFormat:@"You must got to find your love."];
NSData *data = [kJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];

NSJSONReadingOptions 為轉換時的選項,可以讓你指定轉換後所產生的物件特性。

NSJSONReadingMutableContainers:產生的 Array or Dictionary 為 Mutable 物件
NSJSONReadingMutableLeaves:產生的 String 為 Mutable 物件
NSJSONReadingAllowFragments:允許讀取進來的物件最上層不是 NSArray or NSDictionary

2. 讀取串流物件,並轉換為 JSON:

+ (id)JSONObjectWithStream:(NSInputStream *)stream options:(NSJSONReadingOptions)opt error:(NSError **)error;

此 method 可以讀取串流物件,並且轉換成 JSON,但必須自己負責開啓與關閉這個串流

ex.

NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://dl.dropbox.com/u/4223444/twLoto.json"]];
NSInputStream *inStream = [[NSInputStream alloc] initWithData:data];
[inStream open];
NSArray *array = [NSJSONSerialization JSONObjectWithStream:inStream options:NSJSONReadingAllowFragments error:nil];
[inStream close];


Question:
1. 同步、非同步使用時機?  

同步:當所有資料下載完時,使用者才能繼續動作,期間畫面無論如何都無法動作
非同步:使用者可以一邊下載資料一邊使用 APP,類似 Youtube

2. + (id)JSONObjectWithStream:(NSInputStream *)stream options:(NSJSONReadingOptions)opt error:(NSError **)error ;

  何謂串流物件? 

與非同步的觀念類似,適合用在需要下載大量資料時,如:音樂、影片等。
我們觀看 Youtube 時,它會讓我們先觀賞已下載好的資料(灰色那條),另一方面一邊下載未完成的資料,
避免使用者等待時間過久,造成負面觀感。
使用串流物件時,一定要使用非同步的方式。