[iOS]電源オフみたいなスライダーのつくり方

ios_ui

まずはこちらをどうぞ

slider-poweroff

今回はこの電源オフスライダーの動きを再現してみたいと思います。

やりたいこと

  • つまみをドラッグしたい
  • つまみから指を離したらアニメーションで移動させたい
  • つまみが最後までいったらイベント処理したい

Viewの配置とGestureの接続

storyboard

slidersample-storyboard

スライダーの背景になるViewのSubViewとして、つまみとつまみの終わりの位置を取得するためのViewを配置しました。つまみの終わりは表示したくないのでhidden = YESとしています。また、電源オフスライダーのような見た目にするために、各Viewの角を丸めておきます。

スライダーのつまみの動きを監視、制御するためにViewControllerにViewを接続します。
Viewをドラッグしたいので、つまみのViewにPan Gesture Recognizerを設定してPan Gesture RecognizerをViewControllerに接続します。(TypeはUIPanGestureRecognizerを選択します)


slidersample-actioninsert.png

実装

ViewController.m

#import "ViewController.h"

@interface ViewController ()

// つまみのView
@property (weak, nonatomic) IBOutlet UIView *knob;
// つまみの終わりのView
@property (weak, nonatomic) IBOutlet UIView *placeholderView;

// つまみの初期X座標(Viewの中心)
@property (nonatomic) CGFloat startCenterX;
// つまみの移動限界X座標(Viewの中心)
@property (nonatomic) CGFloat endCenterX;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    // Viewの座標が決まったところでつまみの初期X座標と移動限界X座標を取得する
    self.startCenterX = self.knob.center.x;
    self.endCenterX = self.placeholderView.center.x;
}

// つまみがドラッグされたら呼ばれる
- (IBAction)slideKnob:(UIPanGestureRecognizer *)sender
{
    // 移動量を取得
    CGPoint point = [sender translationInView:self.view];
    // つまみの中心X座標
    CGFloat knobCenterX = sender.view.center.x + point.x;
    // 状態に応じて最適化されるつまみの中心X座標
    CGFloat newCenterX;

    if(sender.state == UIGestureRecognizerStateEnded) {
        // つまみから指が離れた時の処理
        
        /**
         * つまみの移動量が最大移動量の70%未満である場合初期位置にも移動する。
         * つまみの移動量が最大移動量の70%以上である場合移動限界位置に移動する。
         */
         
        // 判断に応じて最適化された座標を取得する 
        if (knobCenterX < self.endCenterX * 0.7) {
            newCenterX = self.startCenterX;
        } else {
            newCenterX = self.endCenterX;
        }
        
        // つまみのアニメーション処理
        [UIView animateWithDuration: 0.2
                              delay: 0
                            options: UIViewAnimationOptionCurveEaseInOut
                         animations:^{
                             // X座標の変更のみ反映する(水平移動のみ)
                             sender.view.center = CGPointMake(newCenterX, sender.view.center.y);        
                         }
                         completion:^(BOOL finished) {
                             // アニメーションの終了時につまみが移動限界位置にあった場合任意の処理を行う
                             if (sender.view.center.x == self.endCenterX) {
                                 [self action];
                             }
                         }];
        
        // つまみの移動を反映する
        [sender setTranslation:CGPointZero inView:self.view];
        
    } else {
        // つまみをドラッグ中の処理
        
        /**
         * スライダーの両端付近でつまみを素早く動かした時に
         * つまみの移動許容範囲をこえてしまうので、つまみの位置を両端に揃える。
         */
        if (self.startCenterX <= knobCenterX && self.endCenterX >= knobCenterX) {
            newCenterX = knobCenterX;
        } else if (self.startCenterX > knobCenterX) {
            newCenterX = self.startCenterX;
        } else if (self.endCenterX < knobCenterX) {
            newCenterX = self.endCenterX;
        }
        
        // X座標の変更のみ反映する(水平移動のみ)
        sender.view.center = CGPointMake(newCenterX, sender.view.center.y);
        
         // つまみの移動を反映する
        [sender setTranslation:CGPointZero inView:self.view];
        
        // つまみが移動限界位置にあった場合任意の処理を行う
        if (newCenterX == self.endCenterX) {
            [self action];
        }

    }
}
@end

簡単に言うとPan Gesture Recognizerから得られる移動量をつまみのViewに反映しているだけです。あとは、UIPanGestureRecognizer.stateで指を離した時とドラッグしている時の処理を分けています。

Demo

想像以上にスムーズに動きました。ここでは、つまみが最後まで移動したら弊社ロゴを表示する処理を行っています。


slidersample-demo

おわりに

今回はViewControllerになまでだらだら書きましたが、CustomViewとして切り出して使うのがいいと思います。とても単純な作りなので、カスタマイズも自由自在です。

どこで使うんだって話ですが、ユーザーにとって重要なアクションだったり後戻りができないようなアクションをさせるときにアラートで確認するようなことはしないで、いつもと違った操作で意識させるといったことができると思います。

なんでもかんでもボタン押してればいいでしょって感じではなくて、ユーザーを「ハッ」っとさせたいような場面でこのような「はずし」も面白いのではないでしょうか。