图片方向不同时画廊转场动画异常的原因排查求助
我正在开发一款画廊应用,希望实现画廊与全屏图片间的平滑转场效果。原本动画运行完全正常,但当使用拍摄方向不同的图片时,动画出现失效问题。我已多次检查起始与结束帧参数,确认参数无误且不存在比例问题。
以下是我的动画代码:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toView = transitionContext.view(forKey: .to), let fromVC = transitionContext.viewController(forKey: .from)?.childViewControllers.first as? NavigationGalleryViewController, let fromView = fromVC.collectionView?.cellForItem(at: indexPath) as? InstallationViewCell else { transitionContext.completeTransition(true) return } let finalFrame = toView.frame let viewToAnimate = UIImageView(frame: originFrame) viewToAnimate.image = fromView.imageView.image viewToAnimate.contentMode = .scaleAspectFill viewToAnimate.clipsToBounds = true fromView.imageView.isHidden = true let containerView = transitionContext.containerView containerView.addSubview(toView) containerView.addSubview(viewToAnimate) toView.isHidden = true // Determine the final image height based on final frame width and image aspect ratio let imageAspectRatio = viewToAnimate.image!.size.width / viewToAnimate.image!.size.height var finalImageheight = finalFrame.width / imageAspectRatio if (finalImageheight > UIScreen.main.bounds.height) { finalImageheight = UIScreen.main.bounds.height } // Animate size and position UIView.animate(withDuration: duration, animations: { viewToAnimate.frame.size.width = finalFrame.width viewToAnimate.frame.size.height = finalImageheight viewToAnimate.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY) }, completion:{ _ in toView.isHidden = false fromView.imageView.isHidden = false viewToAnimate.removeFromSuperview() transitionContext.completeTransition(true) }) }
请问图片方向与动画之间是否存在我未注意到的关联机制?
嗨,这个问题我之前做相册转场时也踩过坑!核心原因是图片的EXIF方向元数据在偷偷影响你——你计算的宽高比是基于UIImage的size属性,但这个尺寸是图片的逻辑尺寸,不是它实际显示时的像素尺寸。
举个直观的例子:你用手机竖拍的照片,EXIF里会标记方向为“顺时针90度”,这时候UIImage的size会是(宽:像素高度,高:像素宽度),但系统显示时会自动把图片旋转回来,所以实际显示的宽高比和你计算的imageAspectRatio完全相反。这就导致动画里计算的finalImageheight完全不符合实际显示需求,转场自然就失效了。
解决方案1:计算图片实际显示的宽高比
你需要根据图片的imageOrientation属性修正宽高比,而不是直接用size.width / size.height。可以加个工具方法来获取正确的显示宽高比:
func getDisplayAspectRatio(for image: UIImage) -> CGFloat { // 右旋转/左旋转的图片,实际显示时宽高是颠倒的 let isPortraitOrientation = [.right, .left].contains(image.imageOrientation) let displayWidth = isPortraitOrientation ? image.size.height : image.size.width let displayHeight = isPortraitOrientation ? image.size.width : image.size.height return displayWidth / displayHeight }
然后把你代码里的这一行:
let imageAspectRatio = viewToAnimate.image!.size.width / viewToAnimate.image!.size.height
替换成:
let imageAspectRatio = getDisplayAspectRatio(for: viewToAnimate.image!)
额外优化:匹配过渡视图的初始旋转方向
另外,用来过渡的viewToAnimate也需要和原cell里的图片方向一致,不然可能会出现旋转错位。可以给它设置transform来匹配原图片的方向:
func transformForImageOrientation(_ orientation: UIImage.Orientation) -> CGAffineTransform { switch orientation { case .right: return CGAffineTransform(rotationAngle: .pi / 2) case .left: return CGAffineTransform(rotationAngle: -.pi / 2) case .down: return CGAffineTransform(rotationAngle: .pi) default: return .identity } }
创建viewToAnimate之后加上:
viewToAnimate.transform = transformForImageOrientation(viewToAnimate.image!.imageOrientation)
注意:设置transform后,初始originFrame的计算可能需要调整,因为transform会改变视图的bounds和center对应关系,如果原frame是来自cell的图片视图,可能需要先做坐标转换。
解决方案2:预处理图片统一方向
如果你不想在动画里处理复杂的方向逻辑,也可以在加载图片时就把它修正为“向上”的默认方向,这样UIImage的size就和实际显示尺寸一致了。可以用这个工具方法:
func fixImageOrientation(_ image: UIImage) -> UIImage { guard image.imageOrientation != .up else { return image } UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) image.draw(in: CGRect(origin: .zero, size: image.size)) let fixedImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return fixedImage }
在给cell或者过渡视图设置图片前,先调用这个方法修正方向。
这样处理后,不同拍摄方向的图片转场动画应该就能正常平滑运行了!
内容的提问来源于stack exchange,提问作者Sacha.R

