なんとかするから、なんとかなる

エンジニア関係のことを書きます

iOS 画面遷移アニメーションにてUIViewで拡大して縮小したら戻らなかったときの解決方法

English version below URL

https://hopita.hatenablog.com/entry/2018/08/18/160658

はじめに

画面遷移時にカスタムアニメーションを使うことはよくあります。

カスタムアニメーションを使うことで、アプリの表現の幅はかなり広げられます。

しかし、このアニメーションで遷移時にUIViewを拡大、戻ってきたときに縮小したとき元のサイズに戻らなくて苦戦したのでまとめます。

解決方法

遷移時にUIView.animateメソッドでUIView.transformを変更したら、completionで元のサイズに戻しておく。

詳細

次のような画面遷移を記述した時に問題が起こりました。

やっていること以下です。

  1. UINavigationControllerにPushした時に遷移元の画面サイズ小さくするアニメーションと元に遷移する
  2. UINavigationConrollerでPopした時に遷移元の画面サイズを元に戻す

PushAnimation

class PushModalAnimation: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1.0
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let toViewController = transitionContext.viewController(forKey: .to),
            let fromViewController = transitionContext.viewController(forKey: .from) else {
                transitionContext.completeTransition(true)
                return
        }
        
        transitionContext.containerView.addSubview(toViewController.view)
        transitionContext.containerView.addSubview(fromViewController.view)
        
        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            fromViewController.view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
        }) { (_) in
            transitionContext.completeTransition(true)
        }
    }
}

PopAnimation

class PopModalAnimation: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1.0
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let toViewController = transitionContext.viewController(forKey: .to),
            let fromViewController = transitionContext.viewController(forKey: .from) else {
                transitionContext.completeTransition(true)
                return
        }
        
        transitionContext.containerView.addSubview(fromViewController.view)
        transitionContext.containerView.addSubview(toViewController.view)
        
        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            toViewController.view.transform = CGAffineTransform.identity
        }) { (_) in
            transitionContext.completeTransition(true)
        }
    }
}

結果

遷移前

f:id:hopita:20180818152404p:plain:h200

遷移後

f:id:hopita:20180818152417p:plain:h200

遷移から戻ってきた時

f:id:hopita:20180818152425p:plain:h200

遷移後の画面が一回り大きく表示されてしまっています。(赤い枠は画面よりも小さいはず)

しかも、この状態で一度ホームボタンを押してからアプリに戻ってくる、正しく表示されます。謎です。

解決方法

PushAnimationで縮小アニメーション後にCompletionの中でview.transformを元に戻すとこの現象は抑えられました。

その場合PopAnimationで画面を拡大する前に一度view.transformを設定し直す必要はあります。

従って正解のコードは次の通り。(コードの一部です)

PushAnimation

        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            fromViewController.view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
        }) { (_) in
            fromViewController.view.transform = CGAffineTransform.identity //ここで元transfromnに戻す
            transitionContext.completeTransition(true)
        }

PopAnimation

        toViewController.view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)// アニメーション前に小さくする
        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            toViewController.view.transform = CGAffineTransform.identity
        }) { (_) in
            transitionContext.completeTransition(true)
        }

最後に

今回のケースはtransformのscaleでしたが、もしかしらrotateやtransitionも同様に起きるかもしれないです。

ただし、toViewController.view.alpha を変更した場合はcompletionで直す必要がなかったです。

こういうところでハマると、調べ方がわからないですね。 本当に仮説と実行の繰り返しです。

今回の場合だとiOSのバグっぽい気がするのですが、調べても同様のケースの人がいなかったのでわからないです。

こういう部分をWWDCの時とかに聞いてみるといいのかもしれないと実感しました。