LoginSignup
79
78

More than 5 years have passed since last update.

Swiftでバネのようにバウンスするメニューを作ってみた

Posted at

SkypeのiOSアプリで見るバウンドするアニメーションを実装してみた。
その要点をまとめておく。

デモ

https://github.com/ytakzk/Hokusai
Demo

実装方法

CADisplayLink

アニメーションにはCADisplayLinkを使用した。
ディスプレイが更新されるタイミングで関数が走るため、
NSTimerよりもアニメーション適している。

UIBezierPath

波のようなアニメーションを作るために、 ベジェ曲線を利用する。
具体的にはUIBezierPathで毎フレームごとにCALayerを描画。

// レイヤーの開始点に移動
UIBezierPath().moveToPoint(開始点)

// 現在の点からベジェ曲線を描いて次の点へ移動
UIBezierPath().addQuadCurveToPoint(次の点,
        controlPoint:曲率を作るための制御点)

// 現在の点から開始点までのパスを閉じる
UIBezierPath().closePath()

ベジェ曲線については、これを見ればなんとなくイメージできるはず。
ベジェ曲線
出典:sigbus.info

該当するソースコード

// アニメーション開始時に呼ばれる
func positionAnimationWillStart() {
    // displayLinkでループ開始
    if displayLink == nil {
        displayLink = CADisplayLink(target: self, selector: "tick:")
        displayLink!.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
    }
}

// アニメーションの関数
func updatePath() {
    // 四角形の幅と高さを取得
    let width  = CGRectGetWidth(shapeLayer.bounds)
    let height = CGRectGetHeight(shapeLayer.bounds)

    // パスを描く
    // bendableOffsetが肝で、画面からせり出してくる距離に応じて大きさを変化させ、
    // メニュー上部の波を作る。
    let path = UIBezierPath()
    path.moveToPoint(CGPoint(x: 0, y: 0))
    path.addQuadCurveToPoint(CGPoint(x: width, y: 0),
        controlPoint:CGPoint(x: width * 0.5, y: 0 + bendableOffset.vertical))
    path.addQuadCurveToPoint(CGPoint(x: width, y: height + 100.0),
        controlPoint:CGPoint(x: width + bendableOffset.horizontal, y: height * 0.5))
    path.addQuadCurveToPoint(CGPoint(x: 0, y: height + 100.0),
        controlPoint: CGPoint(x: width * 0.5, y: height + 100.0))
    path.addQuadCurveToPoint(CGPoint(x: 0, y: 0),
        controlPoint: CGPoint(x: bendableOffset.horizontal, y: height * 0.5))
    path.closePath()

    shapeLayer.path = path.CGPath
}

func tick(displayLink: CADisplayLink) {
    if let presentationLayer = layer.presentationLayer() as? CALayer {
        // メニューの位置によってoffsetの大きさを変化させる。
        var verticalOffset = self.layer.frame.origin.y - presentationLayer.frame.origin.y

        bendableOffset = UIOffset(
            horizontal: 0.0,
            vertical: verticalOffset
        )
        // アニメーション用の関数を呼ぶ
        updatePath()

        // offsetが0になったタイミングでループを終了
        if verticalOffset == 0 {
            self.displayLink!.invalidate()
            self.displayLink = nil
        }
    }
}

ライブラリ

せっかくなのでライブラリ化してgithubに公開した。
cocoapods、Carthageどちらにも対応している。
https://github.com/ytakzk/Hokusai

使い方は簡単。
クロージャーでもセレクターにも対応しているので色んな用途に使えるはず。

let hokusai = Hokusai()

// クロージャーでコールバックを定義する
hokusai.addButton("Button 1") {
    println("Rikyu")
}

// セレクターでもコールバックを定義できる
hokusai.addButton("Button 2", target: self, selector: Selector("button2Pressed"))

// メニューを出す
hokusai.show()

func button2Pressed() {
    println("Oribe")
}
79
78
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
78