こんにちは。きんくまです。
早いものでもう12月です。本年も大変お世話になりました、ありがとうございました。
、、って、まだ1年を締めるにはまだ早いか!
さて、今回はSwiftでCore Animationしたいです!
できたもの
マスクつきとか、ディレイとかいろいろ試してみました。
あと、Auto Layoutでうまくいくかわからなかったので、そのあたりもためしてみました。
赤い四角は画面のサイズに応じて画面の下端から浮かびあがるようにして、水色のところは真ん中から円形マスクで見えるようになります。
イージング
アニメーションといえばイージングが肝心かなめ!
というわけで以前にObjective-Cでイージングの記事を書いたことがあったのですが、それをSwiftにも移植してみました。
以前の記事
>>[iOS] Core AnimationのCAMediaTimingFunctionでRobert Pennerのイージングを近似
KKCustomMediaTimingFunction.swift
import UIKit enum KKCMTFEasingCurve{ case Lenear, EaseInSine, EaseOutSine, EaseInOutSine, EaseOutInSine, EaseInQuad, EaseOutQuad, EaseInOutQuad, EaseOutInQuad, EaseInCubic, EaseOutCubic, EaseInOutCubic, EaseOutInCubic, EaseInQuart, EaseOutQuart, EaseInOutQuart, EaseOutInQuart, EaseInQuint, EaseOutQuint, EaseInOutQuint, EaseOutInQuint, EaseInExpo, EaseOutExpo, EaseInOutExpo, EaseOutInExpo, EaseInCirc, EaseOutCirc, EaseInOutCirc, EaseOutInCirc } class KKCustomMediaTimingFunction : CAMediaTimingFunction{ init(easingCurve:KKCMTFEasingCurve){ let points:[Float] = KKCustomMediaTimingFunction.controlPointSettings(easingCurve) super.init(controlPoints: points[0], points[1], points[2], points[3]) } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } private class func controlPointSettings(easingCurve:KKCMTFEasingCurve)->[Float]{ var controlPoints:[Float] switch easingCurve { case .EaseInSine: controlPoints = [0.44, 0, 0.99, 0.98] case .EaseOutSine: controlPoints = [0, 0.44, 0.98, 0.99] case .EaseInOutSine: controlPoints = [0.36, 0, 0.64, 1] case .EaseOutInSine: controlPoints = [0, 0.36, 1, 0.64] case .EaseInQuad: controlPoints = [0.51, 0, 0.96, 0.9] case .EaseOutQuad: controlPoints = [0, 0.51, 0.9, 0.96] case .EaseInOutQuad: controlPoints = [0.43, 0, 0.57, 1] case .EaseOutInQuad: controlPoints = [0, 0.43, 1, 0.57] case .EaseInCubic: controlPoints = [0.55, 0, 0.7, 0.19] case .EaseOutCubic: controlPoints = [0, 0.55, 0.19, 0.7] case .EaseInOutCubic: controlPoints = [0.7, 0, 0.3, 1] case .EaseOutInCubic: controlPoints = [0, 0.7, 1, 0.3] case .EaseInQuart: controlPoints = [0.74, 0, 0.74, 0.19] case .EaseOutQuart: controlPoints = [0, 0.74, 0.19, 0.74] case .EaseInOutQuart: controlPoints = [0.85, 0, 0.13, 0.99] case .EaseOutInQuart: controlPoints = [0, 0.85, 0.99, 0.13] case .EaseInQuint: controlPoints = [0.79, 0, 0.75, 0.1] case .EaseOutQuint: controlPoints = [0, 0.79, 0.1, 0.75] case .EaseInOutQuint: controlPoints = [0.9, 0, 0.09, 1] case .EaseOutInQuint: controlPoints = [0, 0.9, 1, 0.09] case .EaseInExpo: controlPoints = [0.81, 0, 0.83, 0.11] case .EaseOutExpo: controlPoints = [0, 0.81, 0.11, 0.83] case .EaseInOutExpo: controlPoints = [0.97, 0, 0.02, 0.99] case .EaseOutInExpo: controlPoints = [0, 0.97, 0.99, 0.02] case .EaseInCirc: controlPoints = [0.67, 0, 0.99, 0.57] case .EaseOutCirc: controlPoints = [0, 0.67, 0.57, 0.99] case .EaseInOutCirc: controlPoints = [0.92, 0.15, 0.08, 0.82] case .EaseOutInCirc: controlPoints = [0.15, 0.92, 0.82, 0.08] case .Lenear: controlPoints = [0.00, 0.00, 1.00, 1.00] default: controlPoints = [0.00, 0.00, 1.00, 1.00] } return controlPoints } }
でこれをつかってアニメーションを作ります。
Storyboardで等間隔にあけるのをやってみました。
これで、横位置でも画面サイズが変わっても見た感じ同じようにできました。
やり方は調べたらCocoaの日々さんで書かれてました。ありがとうございます。
>> Autolayoutでビューを等間隔に並べる
アニメーションのコードです。
ViewController.swift
import UIKit class ViewController: UIViewController { @IBOutlet var square1:UIView? @IBOutlet var square2:UIView? @IBOutlet var square3:UIView? @IBOutlet var square4:UIView? @IBOutlet var square5:UIView? private var squares:[UIView]! @IBOutlet var blueRect:UIView? var maskCircleLayer:CALayer! override func viewDidLoad() { super.viewDidLoad() self.squares = [square1!, square2!, square3!, square4!, square5!] for square in squares{ square.layer.opacity = 0 } self.setupMaskLayer() self.blueRect?.layer.hidden = true NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "startAnimation", userInfo: nil, repeats: false) } override func touchesEnded(touches: NSSet, withEvent event: UIEvent) { startAnimation() } override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) { self.setupMaskLayer() } internal func startAnimation(){ CATransaction.begin() let duration = 1.0 for (index, square:UIView) in enumerate(squares){ addSquareAnimation(square, duration:duration, delay: 0.2 * Double(index)) } addMaskCircleAnimation() self.blueRect?.layer.hidden = false CATransaction.commit() } private func addSquareAnimation(targetView:UIView!, duration:CFTimeInterval, delay:CFTimeInterval){ let targetLayer:CALayer = targetView.layer targetLayer.opacity = 0 let group:CAAnimationGroup = CAAnimationGroup() group.duration = duration group.beginTime = CACurrentMediaTime() + delay let opacity:CABasicAnimation = CABasicAnimation(keyPath: "opacity") opacity.fromValue = NSNumber(float: 0.0) opacity.toValue = NSNumber(float: 1.0) let position:CABasicAnimation = CABasicAnimation(keyPath: "position") position.timingFunction = KKCustomMediaTimingFunction(easingCurve: KKCMTFEasingCurve.EaseInOutExpo) let containerRect:CGRect = self.view.frame let fromPoint:CGPoint = CGPointMake(targetLayer.position.x, containerRect.size.height - 20) position.fromValue = NSValue(CGPoint: fromPoint) group.animations = [opacity, position] group.fillMode = kCAFillModeBoth group.removedOnCompletion = false let viewConstraints = targetView.constraints() targetLayer.addAnimation(group, forKey: nil) } private func addMaskCircleAnimation(){ let scaleAnim = CABasicAnimation(keyPath: "transform.scale") scaleAnim.duration = 1.7 scaleAnim.timingFunction = KKCustomMediaTimingFunction(easingCurve: KKCMTFEasingCurve.EaseInOutCubic) scaleAnim.fromValue = NSNumber(float:0.0) scaleAnim.toValue = NSNumber(float:1.0) maskCircleLayer.addAnimation(scaleAnim, forKey: nil) } private func setupMaskLayer(){ self.maskCircleLayer = CALayer() maskCircleLayer.delegate = self let containerSize = self.view.frame.size let sw = containerSize.width maskCircleLayer.frame = CGRectMake(0, 0, sw * 2.0, sw * 2.0) let blueHeight:CGFloat! = blueRect?.frame.size.height maskCircleLayer.position = CGPointMake(sw * 0.5, blueHeight * 0.5) maskCircleLayer.setNeedsDisplay() blueRect?.layer.mask = maskCircleLayer } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } override func drawLayer(layer: CALayer!, inContext ctx: CGContext!) { if layer === maskCircleLayer{ CGContextSetFillColorWithColor(ctx, UIColor(red: 1, green: 0, blue: 0, alpha: 1).CGColor) CGContextFillEllipseInRect(ctx, CGRectMake(0, 0, layer.frame.size.width, layer.frame.size.height)) } } }
水色の四角をマスクする円形のマスクレイヤーは、今回はdrawLayerできちんと描画してますけど、cornerRadiusプロパティでも同じ効果が出せるみたいです。
Objectie-Cじゃなくって、Swiftでの書き方を調べながらやったので今回は時間がかかりました。でも、やり方がわかったので慣れればすぐに書けそうだと思いました。あとそれほどコード量も増えないから、こんな感じのアニメーションだったらライブラリなしでそのまま書いてもいいじゃないかなと思いました。
そうだ。1点だけ。アプリを立ち上げたあとViewDidLoadで何もせずにアニメーションをはじめると座標がずれてうまくアニメーションができませんでした。なので、起動時に見せるアニメーションは0.1秒のタイマーをいれてあります。
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ