如何实现自定义转场效果?请提供VC1到VC2的Logo转场简易示例
Hey there! I’ve done this exact kind of transition a bunch of times—let’s walk through a simple, native implementation to get your logo smoothly moving from VC1 to VC2, no third-party libraries required.
The trick is to create a snapshot of the logo from VC1, animate that snapshot to the position of the logo in VC2, then swap it out for VC2’s actual logo once the animation finishes. This keeps both VCs’ original logos intact while giving the illusion of a single moving element.
Let’s break this down into code (using Swift for iOS, but the logic translates to Objective-C too).
1. Create the Custom Transition Animator
This class handles all the animation logic. It conforms to UIViewControllerAnimatedTransitioning to define the animation duration and the actual animation steps.
import UIKit class LogoTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { // Define how long the animation lasts func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.6 // Adjust this to make the animation faster/slower } // The main animation logic func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // Safely get references to both view controllers and the container view guard let fromVC = transitionContext.viewController(forKey: .from) as? ViewController1, let toVC = transitionContext.viewController(forKey: .to) as? ViewController2, let containerView = transitionContext.containerView else { transitionContext.completeTransition(false) return } // 1. Take a snapshot of VC1's logo (this is what we'll animate) let logoSnapshot = fromVC.logoImageView.snapshotView(afterScreenUpdates: true)! let startFrame = containerView.convert(fromVC.logoImageView.frame, from: fromVC.view) logoSnapshot.frame = startFrame containerView.addSubview(logoSnapshot) // 2. Prepare VC2's view: add it to the container, but hide its logo temporarily toVC.view.frame = transitionContext.finalFrame(for: toVC) toVC.logoImageView.isHidden = true containerView.addSubview(toVC.view) // 3. Calculate where VC2's logo lives in the container view's coordinate system let targetFrame = containerView.convert(toVC.logoImageView.frame, from: toVC.view) // 4. Run the animation! UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, usingSpringWithDamping: 0.8, // Optional spring effect for a "bouncy" feel initialSpringVelocity: 0.3, options: .curveEaseInOut) { // Move the snapshot to VC2's logo position, add a slight scale for flair logoSnapshot.frame = targetFrame logoSnapshot.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) } completion: { _ in // Clean up: show VC2's real logo, remove the snapshot, mark transition as done toVC.logoImageView.isHidden = false logoSnapshot.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } } }
2. Set Up ViewController1 (Source VC)
VC1 will have your initial logo, and trigger the transition to VC2. We’ll also make it the transition delegate to use our custom animator.
import UIKit class ViewController1: UIViewController { // Your logo image view let logoImageView: UIImageView = { let iv = UIImageView(image: UIImage(named: "your-logo")) // Replace with your logo asset name iv.contentMode = .scaleAspectFit iv.translatesAutoresizingMaskIntoConstraints = false return iv }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white setupLogoLayout() } // Position the logo in VC1 (adjust constraints to match your layout) private func setupLogoLayout() { view.addSubview(logoImageView) NSLayoutConstraint.activate([ logoImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40), logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), logoImageView.widthAnchor.constraint(equalToConstant: 120), logoImageView.heightAnchor.constraint(equalToConstant: 120) ]) } // Trigger transition (connect this to a button tap, for example) @IBAction func navigateToVC2(_ sender: UIButton) { let vc2 = ViewController2() vc2.transitioningDelegate = self vc2.modalPresentationStyle = .fullScreen // Required for custom transitions present(vc2, animated: true) } } // MARK: - Transition Delegate extension ViewController1: UIViewControllerTransitioningDelegate { // Return our custom animator when presenting VC2 func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return LogoTransitionAnimator() } // Optional: Add a reverse animation for dismissing VC2 (mirror the logic above) func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return nil // Remove this and add reverse logic if you want a return animation } }
3. Set Up ViewController2 (Destination VC)
VC2 will have the logo in its final position—we just need to make sure it’s hidden until the animation finishes.
import UIKit class ViewController2: UIViewController { // Your logo image view (same asset as VC1, different position) let logoImageView: UIImageView = { let iv = UIImageView(image: UIImage(named: "your-logo")) iv.contentMode = .scaleAspectFit iv.translatesAutoresizingMaskIntoConstraints = false return iv }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .lightGray setupLogoLayout() } // Position the logo in VC2 (adjust constraints to match your target layout) private func setupLogoLayout() { view.addSubview(logoImageView) NSLayoutConstraint.activate([ logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), logoImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor), logoImageView.widthAnchor.constraint(equalToConstant: 200), logoImageView.heightAnchor.constraint(equalToConstant: 200) ]) } // Return to VC1 (connect to a button tap) @IBAction func goBackToVC1(_ sender: UIButton) { dismiss(animated: true) } }
- Snapshot vs. Moving the Original View: Using a snapshot ensures VC1’s logo stays in place—if you moved the original
UIImageView, VC1 would lose its logo after the transition. - Coordinate System Conversion: Always use
containerView.convert(_:from:)to get the correct frame positions, since each view controller’s view has its own coordinate system. - Customization: Tweak the animation duration, spring parameters, or add opacity changes to match your app’s style.
- Push/Pop Instead of Present/Dismiss: If you’re using a
UINavigationController, set the nav controller’s delegate and implementnavigationController(_:animationControllerFor:from:to:)to return your animator.
内容的提问来源于stack exchange,提问作者John

