月別アーカイブ: 2013年2月

プライベートメソッドなのに

objective-cはインターフェースに書かれていないプライベートメソッドもオーバーライドできちゃうYO!
自由度が高いっちゃー高いんでしょうけど、これはバグの温床っぽいなぁ。

だからメソッドの頭にプリフィックスを書く事が一部で流行ってるのか・・
嫌だな・・

UIScrollViewの慣性スクロールをピタッと止める

幾つか方法があるようなんですけど、UIScrollViewDelegateのscrollViewWillEndDraggingかscrollViewWillBeginDeceleratingでcontentOffsetに現在の値を設定すれば止まってくれました。

//scrollViewWillBeginDecelerating


-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
   //↓これで止まる	
   [scrollView setContentOffset:scrollView.contentOffset animated:NO];

}

//scrollViewWillEndDragging


- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
   //↓これで止まる	
   *targetContentOffset = scrollView.contentOffset;

}

ちょっと不思議なのは上記のようにscrollView.contentOffsetであれば止まってくれるのですが、
CGPointMake(scrollView.contentOffset.x, 0)
のように新たに生成したCGPointでは止まってくれないのです。
(最終的には指定した位置で止まるのですが、ピタッととまらない)

UIViewをUIPopoverControllerで表示する

UIPopoverControllerでなにかしらポップアップさせる時、一々UIViewControllerを新規クラスで作成するのが面倒な時ありますよね。
UIViewでの実装がすでにあるものをポップアップさせたい時とか。

こんな時はUIViewControllerを直接インスタンス化してそのviewにUIViewを割り当てればOKっぽいです。
(この辺がobjective-cらしいなーと思う所です)


UIViewController* popupContents 
  = [[[UIViewController alloc]init]autorelease];
popupContents.view 
  = [[XXXXView alloc]initWithFrame:CGRectMake(0, 0, 400, 400)]autorelease];
popupContents.contentSizeForViewInPopover = CGSizeMake(400, 400); //※1

UIPopoverController* popup 
  = [[[UIPopoverController alloc]initWithContentViewController:popupContents]autorelease];

//アニメーション付きで表示する(ここは適当)  
[popup presentPopoverFromRect:CGRectMake(0, 0, 10, 10) 
       inView:self permittedArrowDirections:UIPopoverArrowDirectionAny 
       animated:YES];

※1でcontentSizeForViewInPopoverを指定していますが、これが表示されるポップアップのサイズになります。

UIColorの混合

cocoaの場合、描画関係のAPIが充実しているので自分でグラデーションを描画する事は少ないと思われます。
が、まれーにグラデーションの中間色、色と色とを特定の割合で混合した色を求めたい状況があるわけです。

そんな時はこれ
weightが0の時はcolorFrom、100の時はcolorToの色となるように色成分を混合して
返してくれます。


// weight: 0~100
-(UIColor*)makeBlendColorFrom:(UIColor*)colorFrom colorTo:(UIColor*)colorTo weight:(float)weight
{
    float r1,g1,b1,r2,g2,b2,alpha;

    //RGB値を求める
    [colorFrom getRed:&r1 green:&g1 blue:&b1 alpha:&alpha];
    [colorTo getRed:&r2 green:&g2 blue:&b2 alpha:&alpha];

    //色成分を混合
    float red = (r2*weight + r1*(100.0 - weight))/100.0;
    float green = (g2*weight + g1*(100.0 - weight))/100.0;
    float blue = (b2*weight + b1*(100.0 - weight))/100.0;

    return [UIColor colorWithRed:red green:green blue:blue alpha:1];
}

UIViewの透過(見た目とイベント)

表示上の透過
方法1 alphaを0にする

view.alpha = 0;

これで透明になりますが、これではviewの上にのせた他のSubviewたちも表示されません。

方法2 背景色をクリアにする

view.backgroundcolor = [UIColor clearColor];

これで背景のみが透過されます。

イベントの透過
方法1 isUserInteractionEnabledを設定する

view. isUserInteractionEnabled = NO;

これでview に対してのイベントが透過されますが、subviewについても反応しなくなります。

方法2 hitTestをオーバーライド

こんな感じ


- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView* view= [super hitTest:point withEvent:event];
    if(view==self){
         return nil;
    }
    return view;
}

hitTestでnilを返すと後ろのviewへイベントが透過されます。
これを使うと、状況によってイベントの発生状況をコントロールする事が可能となります。

3本指のピンチジェスチャーを作る 3

touchesMovedの実装

次にtouchesMovedを実装していきます。

ここでやることは単純でタッチポイント間の距離を求めて、touchsBegan時点で保存した距離との比率を
プロパティscaleに保存すればOK

ただし、念のため3点以外でタッチされた時はジェスチャーを失敗させます。
また、stateがUIGestureRecognizerStatePossibleの時はUIGestureRecognizerStateBeganに書き換えておきます。


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    
    int numberOfTouches =  event.allTouches.count;
    if (numberOfTouches != 3)
    {
        if (self.state == UIGestureRecognizerStatePossible)
        {
            self.state = UIGestureRecognizerStateFailed;
        } else {
            self.state = UIGestureRecognizerStateCancelled;
        }
        return;
    }
    
    UITouch *first = [[event.allTouchesallObjects] objectAtIndex:0];
    UITouch *second = [[event.allTouchesallObjects] objectAtIndex:1];
    UITouch *third = [[event.allTouchesallObjects] objectAtIndex:2];
    
    float distance = [self calcDistance:[first locationInView:self.view]
                                     p2:[second locationInView:self.view]
                                     p3:[third locationInView:self.view]];
    
    self.scale = distance / _distance_initial;
    
    if (self.state == UIGestureRecognizerStatePossible)
    {
        self.state = UIGestureRecognizerStateBegan;
    } 
    
    
}

後処理の実装

あとは終了処理とキャンセルの処理を実装すれば完成です

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    
    switch (self.state) {
        caseUIGestureRecognizerStatePossible: 
        caseUIGestureRecognizerStateFailed:    
            self.state = UIGestureRecognizerStateFailed;
            break;
        caseUIGestureRecognizerStateBegan:
        caseUIGestureRecognizerStateChanged:
            // 正常に終了
            self.state = UIGestureRecognizerStateEnded;
            break;
        default:
            // 上記以外はキャンセル扱い
            self.state = UIGestureRecognizerStateCancelled;
            break;
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [supertouchesCancelled:touches withEvent:event];
    self.state = UIGestureRecognizerStateFailed;
    self.scale = 1;
}


使い方

使い方は通常のUIPinchGestureRecognizerと大体同じ

ジェスチャーをUIViewに登録して

ThreeFingersPinchGestureRecognizer *pinch = [[[ThreeFingersPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)] autorelease];
[self.view addGestureRecognizer:pinch];

ハンドリング

- (void)pinchAction : (ThreeFingersPinchGestureRecognizer *)sender 
{
  //sender.scale を使って拡大縮小などを行う
}

ファイル

3本指のピンチジェスチャーを作る 2

3点間の距離の求め方

3点間の距離の合計を求めるメソッドを作りましょう。
中学の時にならったアレです。

2点A(a,b),B(c,d)間の距離ABは
AB = √((c-a)の二乗 + (d-b)の二乗)

ってやつ
これを使って下記のようなメソッドを作成します。


//3点間の距離の合計を求める
- (float)calcDistance:(CGPoint)p1 p2:(CGPoint)p2 p3:(CGPoint)p3
{
    float distance = 0;

    float x;
    float y;

    x = fabs( p1.x - p2.x);
    y = fabs( p1.y - p2.y);
    distance += sqrt((x * x) + (y * y));

    x = fabs( p2.x - p3.x);
    y = fabs( p2.y - p3.y);
    distance += sqrt((x * x) + (y * y));

    x = fabs( p3.x - p1.x);
    y = fabs( p3.y - p1.y);
    distance += sqrt((x * x) + (y * y));

    return distance;
}

touchesBeganの実装

touchesBeganではジェスチャーの起動を判定する必要がありますが、
今回はタッチポイントが3点あるかどうかで判定します。

3点以外の時はstateにUIGestureRecognizerStateFailedを設定して、
ジェスチャーが失敗した事を通知します。


    //3本指の判定
    int numberOfTouches = event.allTouches.count;
    if (numberOfTouches != 3)

    {
        self.state = UIGestureRecognizerStateFailed;
        return;
    }

次にジェスチャー開始時点でのタップポイント間の距離を求めて保存します


    UITouch *first = [[event.allTouchesallObjects] objectAtIndex:0];
    UITouch *second = [[event.allTouchesallObjects] objectAtIndex:1];
    UITouch *third = [[event.allTouchesallObjects] objectAtIndex:2];

    _distance_initial = [self calcDistance:[first locationInView:self.view]
                        p2:[second locationInView:self.view]
                         p3:[third locationInView:self.view]];

    self.scale = 1;

※_distance_initialはインスタンス変数
scaleはプロパティです (例えば @property (nonatomic,assign) float scale; )

続きは次回

3本指のピンチジェスチャーを作る 1

画像の拡大縮小で頻繁にピンチジェスチャー(UIPinchGestureRecognizer)を使うと思いますが、
通常の2本指ではなく3本指で操作したいと思いカスタムジェスチャーを作ってみました。

その作り方を公開したいと思います。

ジェスチャーの仕様は次の通り
・クラス名は ThreeFingersPinchGestureRecognizer
・3本指でジェスチャーを認識し始める
・3点間の距離の合計が変化した割合をプロパティ scale として示す

まずは、カスタムジェスチャーの基本から
カスタムジェスチャーの作り方は意外と簡単で、UIGestureRecognizer を継承して下記の5メソッドをオーバーライドすればOK

- (void)reset;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

それぞれ必要なことは

reset
ジェスチャーの状態を初期状態に戻す。
今回の実装ではプロパティscaleを初期値に戻します。

touchesBegan
ジェスチャーの開始判定を行います。
今回はタッチの数が3点であるか判定します。
また、scaleを算出するために開始時点のタッチポイント間の距離を求めます

touchesMoved
タッチ位置の変更に対しての処理を行います。今回はtouchBeganで求めたタッチポイント間の距離と今の距離からプロパティscaleを算出します

touchesEnded
ジェスチャーの終了処理

touchesCancelled
キャンセルされた場合の処理

あ、それと <UIKit/UIGestureRecognizerSubclass.h>をインポートしておく必要があります。

それぞれの詳細は次回で。

インスタンスに特定のメソッドが実装されているか確認する

クラスのイベントを通知するためにid型のdelegateをプロパティで公開すると思いますが、そのdelegateが特定のメソッドを実装しているかを確認する方法です。

例えば
itemTouched:・・ item:・・
というようなメソッドが存在するか確認する場合には下記の通り


if (self.delegate && [self.delegate respondsToSelector:@selector(itemTouched:item:)])
{
  [self.delegate itemTouched:self item:item];
}