iOS 自定义过渡动画
iOS custom transition animation
我已经开始使用 UIViewControllerAnimatedTransitioning
协议学习自定义过渡动画。我在 youtube 上找到的大部分视频都是基于当我们有新的 ViewController 呈现圆形动画或类似动画时的流程。
我在实施过渡方式时遇到问题。大多数情况下,我需要的是类似于 facebook 应用程序 以及它们如何打开全屏图像查看器。
所以,假设我们有 VC1
和VC2
。在 VC1
上,我们调用动作来呈现 VC2
。在两个 VC
上我们都有相同的 UI 元素。在我的例子中是 UIImageView
。就像您在 VC1
上单击 imageView
一样,它会打开某个对象的详细信息页面,其图像位于顶部。我想要动画,应该看起来像来自 VC1
的图像正在将帧更改为来自 VC2
的图像的最终帧,然后详细信息页面上的其他内容(如标签、按钮等)应该出现。
但是我在训练过程中遇到了一些问题。
1. 首先,我不明白transitionContext
的containerView
的意思。但正如我所见,它有点像过渡之间的中间状态视图。那是对的吗?但这很奇怪
对我来说,因为即使 backgroundColor
属性 也不适合 containerView
.
2. 我不明白在转换过程中我到底需要什么动画,以及 containerView
子视图的结构应该是什么。在我的示例中,当呈现 VC2
时,据我所知,我需要隐藏它的所有子视图。然后将imageView从VC1
动画化到VC2
的imageView
帧,然后再次使所有子View可见。那么,在这种情况下 imageView
应该添加到 containerView 中吗?如果是这样,那么它应该是 VC1
中的实际 imageView
,还是 imageView 的全新副本,具有相同的 frame/image,只是在过渡期间临时使用...
对link我examples/tutorial/code有类似动画
会有帮助
了解自定义过渡动画
就像你从 VCA
导航到 VCB
然后
- 首先你需要使用
UIViewControllerTransitioningDelegate
.
转换委托 负责提供用于自定义转换的动画控制器。您指定的委托对象必须符合 UIViewControllerTransitioningDelegate
协议。
- 现在你必须使用
UIViewControllerAnimatedTransitioning
它负责 持续时间 和动画视图的 实际逻辑 方面的转换。
这些代表的工作方式就像您在两个 VC's
之间并与他们一起玩一样。
要成功完成转换,您必须执行以下步骤:
所以要使用它首先你需要
- 设置
modalPresentationStyle = .custom
- 分配
transitonDelegate
属性.
在 func animateTransition(_ : )
你必须使用上下文 containerView
因为你在两个 VC's
之间所以你需要任何可以做任何动画的容器,所以上下文为您提供了可以制作动画的容器。
现在您需要 fromView
& toView
即 VCA.view
& VCB.view
resp。现在在containerView
中添加这两个视图,编写动画核心逻辑。
最后要注意的是在转换上下文对象上调用的 completeTransition(_:) 方法。动画完成后必须调用此方法,让系统知道您的视图控制器已完成转换。
这是过渡动画的核心基础。
我不知道 FB 动画,所以我只是解释了你剩下的问题。
参考
- https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html
- https://code.tutsplus.com/tutorials/how-to-create-custom-view-controller-transitions-and-animations--cms-25716
您可以询问任何进一步的信息。
Code Addition
关于图像选择
加入VC_A
var selectedImage: UIImageView?
let transition = PopAnimator()
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(
alongsideTransition: {context in
self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
self.positionListItems()
},
completion: nil
)
}
//position all images inside the list
func positionListItems() {
let listHeight = listView.frame.height
let itemHeight: CGFloat = listHeight * 1.33
let aspectRatio = UIScreen.main.bounds.height / UIScreen.main.bounds.width
let itemWidth: CGFloat = itemHeight / aspectRatio
let horizontalPadding: CGFloat = 10.0
for i in herbs.indices {
let imageView = listView.viewWithTag(i) as! UIImageView
imageView.frame = CGRect(
x: CGFloat(i) * itemWidth + CGFloat(i+1) * horizontalPadding, y: 0.0,
width: itemWidth, height: itemHeight)
}
listView.contentSize = CGSize(
width: CGFloat(herbs.count) * (itemWidth + horizontalPadding) + horizontalPadding,
height: 0)
}
// On image selection
VC_B.transitioningDelegate = self
present(VC_B, animated: true, completion: nil)
// add extension
extension VC_A: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)
transition.presenting = true
selectedImage!.isHidden = true
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
}
和动画class
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 1.0
var presenting = true
var originFrame = CGRect.zero
var dismissCompletion: (()->Void)?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let herbView = presenting ? toView : transitionContext.view(forKey: .from)!
let initialFrame = presenting ? originFrame : herbView.frame
let finalFrame = presenting ? herbView.frame : originFrame
let xScaleFactor = presenting ?
initialFrame.width / finalFrame.width :
finalFrame.width / initialFrame.width
let yScaleFactor = presenting ?
initialFrame.height / finalFrame.height :
finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
herbView.transform = scaleTransform
herbView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
herbView.clipsToBounds = true
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: herbView)
UIView.animate(withDuration: duration, delay:0.0, usingSpringWithDamping: 0.4,
initialSpringVelocity: 0.0,
animations: {
herbView.transform = self.presenting ?
CGAffineTransform.identity : scaleTransform
herbView.center = CGPoint(x: finalFrame.midX,
y: finalFrame.midY)
},
completion:{_ in
if !self.presenting {
self.dismissCompletion?()
}
transitionContext.completeTransition(true)
}
)
}
}
输出:
Git-集线器回购: https://github.com/thedahiyaboy/TDCustomTransitions
xcode : 9.2
swift : 4
UIViewControllerAnimatedTransitioning
的核心方法是animateTransition
。我在尝试解释基本思想时在这里添加了评论。
let duration = 0.5
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// Works like an empty scratchpad/slate.
// This is the view that will be shown on screen when animation starts and upto
// it ends.
// Any animations done here are visible to user.
// Nothing right now, in this container(*).
let containerView = transitionContext.containerView
// Grab the controller to animate from and to.
let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view
let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view
// We might need some other view's that need to be animated, like the UIImageView
// In your case this image view must exists on fromView and also in toView
let fromImageView = UIImageView() // We should get image from fromController
let toImageView = UIImageView() // We should get image from toController
// Since the containerView has no views as of now(*), we need to add our fromView first
containerView.addSubview(fromView!)
// We will also add, the to view but with alpha 0 so that is not visible initially
toView?.alpha = 0.0
// Add this to view to container
containerView.addSubview(toView!)
UIView.animate(withDuration: duration, animations: {
// We do animations here, something like,
fromImageView.frame = (toView?.frame)! // With some checking around the view relative frames
toView?.alpha = 1.0
}) { (completed) in
// Do clean up here, after this completeTransition(true) method,
// the comtainer will be removed from the screen and toView will be shown automatically
transitionContext.completeTransition(true)
}
}
我已经开始使用 UIViewControllerAnimatedTransitioning
协议学习自定义过渡动画。我在 youtube 上找到的大部分视频都是基于当我们有新的 ViewController 呈现圆形动画或类似动画时的流程。
我在实施过渡方式时遇到问题。大多数情况下,我需要的是类似于 facebook 应用程序 以及它们如何打开全屏图像查看器。
所以,假设我们有 VC1
和VC2
。在 VC1
上,我们调用动作来呈现 VC2
。在两个 VC
上我们都有相同的 UI 元素。在我的例子中是 UIImageView
。就像您在 VC1
上单击 imageView
一样,它会打开某个对象的详细信息页面,其图像位于顶部。我想要动画,应该看起来像来自 VC1
的图像正在将帧更改为来自 VC2
的图像的最终帧,然后详细信息页面上的其他内容(如标签、按钮等)应该出现。
但是我在训练过程中遇到了一些问题。
1. 首先,我不明白transitionContext
的containerView
的意思。但正如我所见,它有点像过渡之间的中间状态视图。那是对的吗?但这很奇怪
对我来说,因为即使 backgroundColor
属性 也不适合 containerView
.
2. 我不明白在转换过程中我到底需要什么动画,以及 containerView
子视图的结构应该是什么。在我的示例中,当呈现 VC2
时,据我所知,我需要隐藏它的所有子视图。然后将imageView从VC1
动画化到VC2
的imageView
帧,然后再次使所有子View可见。那么,在这种情况下 imageView
应该添加到 containerView 中吗?如果是这样,那么它应该是 VC1
中的实际 imageView
,还是 imageView 的全新副本,具有相同的 frame/image,只是在过渡期间临时使用...
对link我examples/tutorial/code有类似动画
会有帮助了解自定义过渡动画
就像你从 VCA
导航到 VCB
然后
- 首先你需要使用
UIViewControllerTransitioningDelegate
.
转换委托 负责提供用于自定义转换的动画控制器。您指定的委托对象必须符合 UIViewControllerTransitioningDelegate
协议。
- 现在你必须使用
UIViewControllerAnimatedTransitioning
它负责 持续时间 和动画视图的 实际逻辑 方面的转换。
这些代表的工作方式就像您在两个 VC's
之间并与他们一起玩一样。
要成功完成转换,您必须执行以下步骤:
所以要使用它首先你需要
- 设置
modalPresentationStyle = .custom
- 分配
transitonDelegate
属性.
- 设置
在
func animateTransition(_ : )
你必须使用上下文containerView
因为你在两个VC's
之间所以你需要任何可以做任何动画的容器,所以上下文为您提供了可以制作动画的容器。现在您需要
fromView
&toView
即VCA.view
&VCB.view
resp。现在在containerView
中添加这两个视图,编写动画核心逻辑。最后要注意的是在转换上下文对象上调用的 completeTransition(_:) 方法。动画完成后必须调用此方法,让系统知道您的视图控制器已完成转换。
这是过渡动画的核心基础。
我不知道 FB 动画,所以我只是解释了你剩下的问题。
参考
- https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html
- https://code.tutsplus.com/tutorials/how-to-create-custom-view-controller-transitions-and-animations--cms-25716
您可以询问任何进一步的信息。
Code Addition
关于图像选择
加入VC_A
var selectedImage: UIImageView?
let transition = PopAnimator()
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(
alongsideTransition: {context in
self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
self.positionListItems()
},
completion: nil
)
}
//position all images inside the list
func positionListItems() {
let listHeight = listView.frame.height
let itemHeight: CGFloat = listHeight * 1.33
let aspectRatio = UIScreen.main.bounds.height / UIScreen.main.bounds.width
let itemWidth: CGFloat = itemHeight / aspectRatio
let horizontalPadding: CGFloat = 10.0
for i in herbs.indices {
let imageView = listView.viewWithTag(i) as! UIImageView
imageView.frame = CGRect(
x: CGFloat(i) * itemWidth + CGFloat(i+1) * horizontalPadding, y: 0.0,
width: itemWidth, height: itemHeight)
}
listView.contentSize = CGSize(
width: CGFloat(herbs.count) * (itemWidth + horizontalPadding) + horizontalPadding,
height: 0)
}
// On image selection
VC_B.transitioningDelegate = self
present(VC_B, animated: true, completion: nil)
// add extension
extension VC_A: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)
transition.presenting = true
selectedImage!.isHidden = true
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
}
和动画class
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 1.0
var presenting = true
var originFrame = CGRect.zero
var dismissCompletion: (()->Void)?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let herbView = presenting ? toView : transitionContext.view(forKey: .from)!
let initialFrame = presenting ? originFrame : herbView.frame
let finalFrame = presenting ? herbView.frame : originFrame
let xScaleFactor = presenting ?
initialFrame.width / finalFrame.width :
finalFrame.width / initialFrame.width
let yScaleFactor = presenting ?
initialFrame.height / finalFrame.height :
finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
herbView.transform = scaleTransform
herbView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
herbView.clipsToBounds = true
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: herbView)
UIView.animate(withDuration: duration, delay:0.0, usingSpringWithDamping: 0.4,
initialSpringVelocity: 0.0,
animations: {
herbView.transform = self.presenting ?
CGAffineTransform.identity : scaleTransform
herbView.center = CGPoint(x: finalFrame.midX,
y: finalFrame.midY)
},
completion:{_ in
if !self.presenting {
self.dismissCompletion?()
}
transitionContext.completeTransition(true)
}
)
}
}
输出:
Git-集线器回购: https://github.com/thedahiyaboy/TDCustomTransitions
xcode : 9.2
swift : 4
UIViewControllerAnimatedTransitioning
的核心方法是animateTransition
。我在尝试解释基本思想时在这里添加了评论。
let duration = 0.5
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// Works like an empty scratchpad/slate.
// This is the view that will be shown on screen when animation starts and upto
// it ends.
// Any animations done here are visible to user.
// Nothing right now, in this container(*).
let containerView = transitionContext.containerView
// Grab the controller to animate from and to.
let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view
let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view
// We might need some other view's that need to be animated, like the UIImageView
// In your case this image view must exists on fromView and also in toView
let fromImageView = UIImageView() // We should get image from fromController
let toImageView = UIImageView() // We should get image from toController
// Since the containerView has no views as of now(*), we need to add our fromView first
containerView.addSubview(fromView!)
// We will also add, the to view but with alpha 0 so that is not visible initially
toView?.alpha = 0.0
// Add this to view to container
containerView.addSubview(toView!)
UIView.animate(withDuration: duration, animations: {
// We do animations here, something like,
fromImageView.frame = (toView?.frame)! // With some checking around the view relative frames
toView?.alpha = 1.0
}) { (completed) in
// Do clean up here, after this completeTransition(true) method,
// the comtainer will be removed from the screen and toView will be shown automatically
transitionContext.completeTransition(true)
}
}