UIView.animate
は引数が多く、animation
とcompletion
で2つクロージャーを引数に取り、ネストも深くなりやすいため、メソッドチェーンで書けるように改良してみました。
サンプル
ViewAnimator.start() .delay(1.0) .duration(0.8) .animate { self.subview.transform = self.subview.transform.translatedBy(x: 0, y: 200.0) } .duration(0.8) .animate { self.subview.transform = self.subview.transform.translatedBy(x: 200.0, y: 0) } .duration(0.8) .animate { self.subview.transform = self.subview.transform.translatedBy(x: 0, y: -200.0) } .duration(0.8) .animate { self.subview.transform = self.subview.transform.translatedBy(x: -200.0, y: 0) } .resolve()
実装
難しいことはせず、Builderパターンと再帰を利用しています。
AnimationBuilder
アニメーションのパラメーターを拾うBuilderです。
typealias AnimationHandler = () -> Void typealias ResolvingAnimation = (@escaping AnimationHandler) -> Void final class AnimationBuilder { var delay: TimeInterval? = nil // (1) var options: UIView.AnimationOptions? = nil // (1) let resolvingAnimation: ResolvingAnimation? // (2) init(resolvingAnimation: ResolvingAnimation?) { self.resolvingAnimation = resolvingAnimation } func delay(_ value: TimeInterval) -> AnimationBuilder { delay = value return self } func duration(_ value: TimeInterval) -> AnimationExecutor { // (3) return AnimationExecutor(resolvingAnimation: resolvingAnimation, duration: value, delay: delay, options: options) } func options(_ value: UIView.AnimationOptions) -> AnimationBuilder { options = value return self } func resolve() { // (4) resolvingAnimation?({}) } }
UiView.animate
のオプショナルな設定値の例として、delay
をプロパティとしています。- 再帰的に、
completion
ハンドラを解決するため、途中までのアニメーションのブロックを(() -> Void) -> Void
で保持しています。 duration
は必須の値なので、こちらが終端でAnimationExecutor
をbuildするようなインターフェースにしました。- 最後に
resolve
を呼ぶとアニメーションを実行します。
AnimationExecutor
メソッドチェーンを実現するため、Builderのduration
の直後のみanimate
出来るようにしています。
final class AnimationExecutor { let resolvingAnimation: ResolvingAnimation? let delay: TimeInterval? let duration: TimeInterval var options: UIView.AnimationOptions? = nil init(resolvingAnimation: ResolvingAnimation?, duration: TimeInterval, delay: TimeInterval? = nil, options: UIView.AnimationOptions?) { self.resolvingAnimation = resolvingAnimation self.duration = duration self.delay = delay self.options = options } func animate(handler: @escaping AnimationHandler) -> AnimationBuilder { let resolving: (@escaping AnimationHandler) -> Void = { [duration, delay, options] completion in // (1) let animation = { UIView.animate( withDuration: duration, delay: delay ?? TimeInterval.zero, options: options ?? [], animations: handler, completion: { _ in completion() } )} if let resolvingAnimation = self.resolvingAnimation { resolvingAnimation(animation) } else { animation() // (2) } } return AnimationBuilder(resolvingAnimation: resolving) } }
- 弱参照では解放されてしまうため、強参照と値キャプチャを利用します。
- 最初は
resolvingAnimation
がnil
なので、単純にブロックを実行します。
アニメーション開始用のstaticメソッド
アニメーション開始用にstatic factoryを用意します。
final class ViewAnimator { static func start() -> AnimationBuilder { return AnimationBuilder(resolvingAnimation: nil) } }
サンプルソース
こちらです。