Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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")
}
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away