tomoyaonishiのブログ

iOSのことを中心に・・・その他もあるよ!

Swift: AutoLayoutでUIVisualEffectviewをアニメーションさせてみた

iOS8からUIVisualEffectViewというものが追加されました。このビューは様々なエフェクトを自動で表示することができます。Appleの公式すりガラス処理を実現することができます。 今回は、Swiftを使って、このクラスの使い方とAutoLayoutによるアニメーションをまとめます。


スライドにもまとめています。

UIVisualEffectView

様々なエフェクトを自動で表示することができるビューです。Appleのすりガラス処理もしてくれます。具体的にどんなエフェクトにするかを決めるのはUIVisualEffectという別のクラスです。インスタンス生成時にこのクラスを渡します。

UIVisualEffect

エフェクトの種類を指定するクラスです。利用するのはそのサブクラスでUIBlurEffect, UIVibrancyEffectの2つが現在のところ用意されています。UIBlurEffectがすりガラス処理です。 UIBlurEffectにはスタイルが3つあります。非常に明るいブラー、明るいブラー、暗いブラーの3つです。

enum UIBlurEffectStyle : Int {
     case ExtraLight
     case Light
     case Dark
}

UIVisualEffectViewの生成方法

実際の生成コードは以下のようになります。

let effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
let effectView = UIVisualEffectView(effect: effect)
// set frame
view.addSubview(effectView)

これだけのコードでApple公式のすりガラス処理が入るビューを利用することができます。UIImage+ImageEffectのように画像ではなく、動的に描画されるのでビューを動かしても問題ありません。また、動作も早いです。

AutoLayoutでアニメーション

今回はさらに、このビューをAutoLayoutを使ってアニメーションさせてみます。 frameでのアニメーションは一般的なので問題無いと思いますが、AutoLayoutでのアニメーションはどのようにやるのかわかりますか?

AutoLayoutではframe, boundsは基本的には触りません。むしろ勝手にいじるとAutoLayoutの整合性が保てなくなりクラッシュすることもあります。

では、どうするか。AutoLayoutの実態であるNSLayoutConstraintクラス(AutoLayoutではこれを制約という)を操作します。 NSLayoutConstraintクラスにconstantというプロパティがありますが、このプロパティはその名の通り、距離を表します。例えば、画面の左端から10ptあけてビューを表示するという制約(NSLayoutConstraint)があったとき、constantは10です。ここでconstantに100を入れると、画面左端から100ptの場所に移動します。frameは意識しなくてよいです。例えば、幅は100ptで固定するという制約があった場合、そのconstantに200を入れると幅が自動で200ptになります。ここでもframe, boundsを直接操作する必要はありません。 このようにAutoLayoutでは、制約のconstantを変更することでレイアウトを動的に変えることができます。もちろん、制約の再生成でもよいですが。

では、具体的にアニメーションのコードを見てみましょう。一部省略しています。 まずはUIVisualEffectViewを生成し、コード上でAutoLayoutを適用します。

// viewDidLayoutSubviewsかviewDidAppear内で
// View
let effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
let effectView: UIVisualEffectView = UIVisualEffectView(effect: effect) effectView.clipsToBounds = false
// falseにすると AutoResizingMaskをAutoLayoutの制約に自動変換しないようになる
effectView.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(effectView)

// AutoLayout
// NSDictionaryOfVariableBindings関数はどこにいったのかよくわからないので自分で辞書を生成
let views = ["effectView" : effectView]
let metrics = ["marginZero" : 0, "marginTop" : 100]

// 水平方向の制約を追加:superviewに対してぴったり張り付く
let horizontalConstraints: AnyObject[] =
NSLayoutConstraint.constraintsWithVisualFormat("|-marginZero-[effectView]-marginZero-|",
                                                options: NSLayoutFormatOptions(0),
                                                metrics: metrics,
                                                views: views)
view.addConstraints(horizontalConstraints)

// 垂直方向の制約を追加:superviewに対して上は100ptあける、下はぴったり張り付く
let verticalConstraints: AnyObject[] =
NSLayoutConstraint.constraintsWithVisualFormat("V:|-marginTop-[effectView]-marginZero-|",
                                                options: NSLayoutFormatOptions(0),
                                                metrics: metrics,
                                                views: views)
view.addConstraints(verticalConstraints) 

// アニメーションのために上からの制約を保持
let constraint : AnyObject = verticalConstraints[0]
marginTopConstraint = constraint as? NSLayoutConstraint

これでUIVisualEffectViewは画面に対して、上から100あけて、左、下、右は画面にピッタリくっつくように表示されます。AutoLayoutなので横向き、iPad、どんなサイズの画面でも同じように表示されます。frameもboundsもいじっていません。

次に、アニメーション部分です。Storyboard上では、ブラーの効果がわかりやすくするため画面いっぱい広がるように適当な画像を表示しておき、ボタンを1つおいてIBActionで接続しておきます。そのボタンを押すたびにUIVisualEffectViewがアニメーションします。

var flag : Bool = false
@IBAction func didTapButton(sender: UIButton) {
    if let constraint = marginTopConstraint {
        UIView.animateWithDuration(1.0,
            delay: 0.0,
            usingSpringWithDamping: 0.5,
            initialSpringVelocity: 0.1,
            options: UIViewAnimationOptions(0),
            animations: {
          // frameのアニメーションと同じ考えだとアニメーションできない 
          // constantを変更するだけでは足りない 
         constraint.constant = self.flag ? 150 : 500
          // 画面の再描画を呼び出す必要あり 
         self.view.layoutIfNeeded()
            },
         completion: nil)
         flag = !flag 
    }
}

これを実行すればびよんびよんとアニメーションするはずです。 基本的にはAutoLayoutはStoryboard上で設定するはずです。その場合は、IBOutletと同じようにNSLayoutConstraintを接続すれば変数が作れます。

個人的にはframeによるレイアウトはiOS7のころから終わっていると思っています。Xcode5にも16:9, 4:3などのレイアウト設定がありました。Androidはとっくの昔に絶対座標での指定は非推奨になっています。Xcode6のことはあまり詳しく書けませんが、さらに多種多様な画面サイズを試すことができるようになっているので基本的にはAutoLayoutでやりましょうというスタンスがよいと思っています。