复制 iOS 邮件应用程序的撰写功能的样式
Replicating the style of the iOS Mail App's Compose Function
我正在 iOS 8 上构建一个应用程序,并希望在创建新电子邮件/消息时复制 iOS 的邮件应用程序的功能。如下所示:撰写视图控制器显示在收件箱视图控制器的顶部,但撰写 vc 并没有占据整个屏幕。有没有比修改视图控制器的框架更简单的方法来做到这一点?谢谢!
这种效果可以通过 UIPresentationController
实现,在 iOS 8 中可用。Apple 有一个关于这个主题的 WWDC '14 视频以及在这个视频底部找到的一些有用的示例代码post(原来的 link 我已经 post 编辑在这里不再有效)。
*该演示名为 "LookInside: Presentation Controllers Adaptivity and Custom Animator Objects." Apple 代码中有几个错误对应于过时的 API 用法,可以通过将损坏的方法名称(在多个地方)更改为来解决以下:
initWithPresentedViewController:presentingViewController:
以下是在 iOS 8 邮件应用程序上复制动画的方法。为了达到预期的效果,下载我上面提到的项目,然后你所要做的就是改变一些东西。
首先,转到 AAPLOverlayPresentationController.m 并确保您已实施 frameOfPresentedViewInContainerView
方法。我的看起来像这样:
- (CGRect)frameOfPresentedViewInContainerView
{
CGRect containerBounds = [[self containerView] bounds];
CGRect presentedViewFrame = CGRectZero;
presentedViewFrame.size = CGSizeMake(containerBounds.size.width, containerBounds.size.height-40.0f);
presentedViewFrame.origin = CGPointMake(0.0f, 40.0f);
return presentedViewFrame;
}
关键是您希望 presentedViewController 的框架从屏幕顶部偏移,这样您就可以实现一个视图控制器与另一个视图控制器重叠的外观(无需模态完全覆盖 presentingViewController)。
接下来,找到AAPLOverlayTransitioner.m中的animateTransition:
方法,将代码替换为下面的代码。您可能想根据自己的代码进行一些调整,但总的来说,这似乎是解决方案:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *fromView = [fromVC view];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [toVC view];
UIView *containerView = [transitionContext containerView];
BOOL isPresentation = [self isPresentation];
if(isPresentation)
{
[containerView addSubview:toView];
}
UIViewController *bottomVC = isPresentation? fromVC : toVC;
UIView *bottomPresentingView = [bottomVC view];
UIViewController *topVC = isPresentation? toVC : fromVC;
UIView *topPresentedView = [topVC view];
CGRect topPresentedFrame = [transitionContext finalFrameForViewController:topVC];
CGRect topDismissedFrame = topPresentedFrame;
topDismissedFrame.origin.y += topDismissedFrame.size.height;
CGRect topInitialFrame = isPresentation ? topDismissedFrame : topPresentedFrame;
CGRect topFinalFrame = isPresentation ? topPresentedFrame : topDismissedFrame;
[topPresentedView setFrame:topInitialFrame];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
delay:0
usingSpringWithDamping:300.0
initialSpringVelocity:5.0
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
animations:^{
[topPresentedView setFrame:topFinalFrame];
CGFloat scalingFactor = [self isPresentation] ? 0.92f : 1.0f;
//this is the magic right here
bottomPresentingView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scalingFactor, scalingFactor);
}
completion:^(BOOL finished){
if(![self isPresentation])
{
[fromView removeFromSuperview];
}
[transitionContext completeTransition:YES];
}];
}
目前,我没有针对 iOS 8 之前的 OS 版本的解决方案,但如果您有答案,请随时添加。谢谢。
更新 (03/2016):
上面的 link 似乎不再有效。可以在此处找到相同的项目:https://developer.apple.com/library/ios/samplecode/LookInside/LookInsidePresentationControllersAdaptivityandCustomAnimatorObjects.zip
更新(12/2019):
在 iOS 13 上以模态方式呈现视图控制器时,似乎这种过渡样式现在是默认行为。我对 OS 的以前版本不满意,但如果你想要要在您自己的应用程序中复制此功能/转换而无需编写大量代码,您可以按原样在 iOS 13 上呈现一个视图控制器,或者您可以将该视图控制器的 modalPresentationStyle
设置为 .pageSheet
然后展示它。
更新 - 2018 年 6 月:
@ChristopherSwasey 更新了 repo 以与 Swift 4 兼容。谢谢 Christopher!
对于未来的旅行者,Brian 的 post 非常好,但是有很多关于 UIPresentationController(促进此动画的)的重要信息,我强烈建议您研究一下。我创建了一个包含 Swift 1.2 版 iOS 邮件应用程序撰写动画的回购协议。我还在自述文件中放入了大量相关资源。请在这里查看:
https://github.com/kbpontius/iOSComposeAnimation
对于 Swift 2,您可以按照本教程进行操作:http://dativestudios.com/blog/2014/06/29/presentation-controllers/ 并替换:
override func frameOfPresentedViewInContainerView() -> CGRect {
// We don't want the presented view to fill the whole container view, so inset it's frame
let frame = self.containerView!.bounds;
var presentedViewFrame = CGRectZero
presentedViewFrame.size = CGSizeMake(frame.size.width, frame.size.height - 40)
presentedViewFrame.origin = CGPointMake(0, 40)
return presentedViewFrame
}
和:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromVC?.view
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toVC?.view
let containerView = transitionContext.containerView()
if isPresenting {
containerView?.addSubview(toView!)
}
let bottomVC = isPresenting ? fromVC : toVC
let bottomPresentingView = bottomVC?.view
let topVC = isPresenting ? toVC : fromVC
let topPresentedView = topVC?.view
var topPresentedFrame = transitionContext.finalFrameForViewController(topVC!)
let topDismissedFrame = topPresentedFrame
topPresentedFrame.origin.y += topDismissedFrame.size.height
let topInitialFrame = isPresenting ? topDismissedFrame : topPresentedFrame
let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
topPresentedView?.frame = topInitialFrame
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 300.0,
initialSpringVelocity: 5.0,
options: [.AllowUserInteraction, .BeginFromCurrentState], //[.Alert, .Badge]
animations: {
topPresentedView?.frame = topFinalFrame
let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
bottomPresentingView?.transform = CGAffineTransformScale(CGAffineTransformIdentity, scalingFactor, scalingFactor)
}, completion: {
(value: Bool) in
if !self.isPresenting {
fromView?.removeFromSuperview()
}
})
if isPresenting {
animatePresentationWithTransitionContext(transitionContext)
}
else {
animateDismissalWithTransitionContext(transitionContext)
}
}
我无法评论 MariSa 的回答,但我更改了他们的代码以使其真正起作用(可以使用一些清理,但它对我有用)
(Swift 3)
这里又是link:http://dativestudios.com/blog/2014/06/29/presentation-controllers/
在CustomPresentationController.swift中:
更新 dimmingView(让它变成黑色而不是示例中的红色)
lazy var dimmingView :UIView = {
let view = UIView(frame: self.containerView!.bounds)
view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
view.alpha = 0.0
return view
}()
按照 MariSa 的指示更新 frameOfPresentedViewInContainerView:
override var frameOfPresentedViewInContainerView : CGRect {
// We don't want the presented view to fill the whole container view, so inset it's frame
let frame = self.containerView!.bounds;
var presentedViewFrame = CGRect.zero
presentedViewFrame.size = CGSize(width: frame.size.width, height: frame.size.height - 40)
presentedViewFrame.origin = CGPoint(x: 0, y: 40)
return presentedViewFrame
}
在 CustomPresentationAnimationController 中:
更新 animateTransition(starting/ending 帧与 MariSa 的回答不同)
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let fromView = fromVC?.view
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let toView = toVC?.view
let containerView = transitionContext.containerView
if isPresenting {
containerView.addSubview(toView!)
}
let bottomVC = isPresenting ? fromVC : toVC
let bottomPresentingView = bottomVC?.view
let topVC = isPresenting ? toVC : fromVC
let topPresentedView = topVC?.view
var topPresentedFrame = transitionContext.finalFrame(for: topVC!)
let topDismissedFrame = topPresentedFrame
topPresentedFrame.origin.y -= topDismissedFrame.size.height
let topInitialFrame = topDismissedFrame
let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
topPresentedView?.frame = topInitialFrame
UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 300.0,
initialSpringVelocity: 5.0,
options: [.allowUserInteraction, .beginFromCurrentState], //[.Alert, .Badge]
animations: {
topPresentedView?.frame = topFinalFrame
let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
bottomPresentingView?.transform = CGAffineTransform.identity.scaledBy(x: scalingFactor, y: scalingFactor)
}, completion: {
(value: Bool) in
if !self.isPresenting {
fromView?.removeFromSuperview()
}
})
if isPresenting {
animatePresentationWithTransitionContext(transitionContext)
}
else {
animateDismissalWithTransitionContext(transitionContext)
}
}
更新 animatePresentationWithTransitionContext(再次不同的帧位置):
func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to)
else {
return
}
// Position the presented view off the top of the container view
presentedControllerView.frame = transitionContext.finalFrame(for: presentedController)
presentedControllerView.center.y += containerView.bounds.size.height
containerView.addSubview(presentedControllerView)
// Animate the presented view to it's final position
UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: {
presentedControllerView.center.y -= containerView.bounds.size.height
}, completion: {(completed: Bool) -> Void in
transitionContext.completeTransition(completed)
})
}
我正在 iOS 8 上构建一个应用程序,并希望在创建新电子邮件/消息时复制 iOS 的邮件应用程序的功能。如下所示:撰写视图控制器显示在收件箱视图控制器的顶部,但撰写 vc 并没有占据整个屏幕。有没有比修改视图控制器的框架更简单的方法来做到这一点?谢谢!
这种效果可以通过 UIPresentationController
实现,在 iOS 8 中可用。Apple 有一个关于这个主题的 WWDC '14 视频以及在这个视频底部找到的一些有用的示例代码post(原来的 link 我已经 post 编辑在这里不再有效)。
*该演示名为 "LookInside: Presentation Controllers Adaptivity and Custom Animator Objects." Apple 代码中有几个错误对应于过时的 API 用法,可以通过将损坏的方法名称(在多个地方)更改为来解决以下:
initWithPresentedViewController:presentingViewController:
以下是在 iOS 8 邮件应用程序上复制动画的方法。为了达到预期的效果,下载我上面提到的项目,然后你所要做的就是改变一些东西。
首先,转到 AAPLOverlayPresentationController.m 并确保您已实施 frameOfPresentedViewInContainerView
方法。我的看起来像这样:
- (CGRect)frameOfPresentedViewInContainerView
{
CGRect containerBounds = [[self containerView] bounds];
CGRect presentedViewFrame = CGRectZero;
presentedViewFrame.size = CGSizeMake(containerBounds.size.width, containerBounds.size.height-40.0f);
presentedViewFrame.origin = CGPointMake(0.0f, 40.0f);
return presentedViewFrame;
}
关键是您希望 presentedViewController 的框架从屏幕顶部偏移,这样您就可以实现一个视图控制器与另一个视图控制器重叠的外观(无需模态完全覆盖 presentingViewController)。
接下来,找到AAPLOverlayTransitioner.m中的animateTransition:
方法,将代码替换为下面的代码。您可能想根据自己的代码进行一些调整,但总的来说,这似乎是解决方案:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *fromView = [fromVC view];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [toVC view];
UIView *containerView = [transitionContext containerView];
BOOL isPresentation = [self isPresentation];
if(isPresentation)
{
[containerView addSubview:toView];
}
UIViewController *bottomVC = isPresentation? fromVC : toVC;
UIView *bottomPresentingView = [bottomVC view];
UIViewController *topVC = isPresentation? toVC : fromVC;
UIView *topPresentedView = [topVC view];
CGRect topPresentedFrame = [transitionContext finalFrameForViewController:topVC];
CGRect topDismissedFrame = topPresentedFrame;
topDismissedFrame.origin.y += topDismissedFrame.size.height;
CGRect topInitialFrame = isPresentation ? topDismissedFrame : topPresentedFrame;
CGRect topFinalFrame = isPresentation ? topPresentedFrame : topDismissedFrame;
[topPresentedView setFrame:topInitialFrame];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
delay:0
usingSpringWithDamping:300.0
initialSpringVelocity:5.0
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
animations:^{
[topPresentedView setFrame:topFinalFrame];
CGFloat scalingFactor = [self isPresentation] ? 0.92f : 1.0f;
//this is the magic right here
bottomPresentingView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scalingFactor, scalingFactor);
}
completion:^(BOOL finished){
if(![self isPresentation])
{
[fromView removeFromSuperview];
}
[transitionContext completeTransition:YES];
}];
}
目前,我没有针对 iOS 8 之前的 OS 版本的解决方案,但如果您有答案,请随时添加。谢谢。
更新 (03/2016):
上面的 link 似乎不再有效。可以在此处找到相同的项目:https://developer.apple.com/library/ios/samplecode/LookInside/LookInsidePresentationControllersAdaptivityandCustomAnimatorObjects.zip
更新(12/2019):
在 iOS 13 上以模态方式呈现视图控制器时,似乎这种过渡样式现在是默认行为。我对 OS 的以前版本不满意,但如果你想要要在您自己的应用程序中复制此功能/转换而无需编写大量代码,您可以按原样在 iOS 13 上呈现一个视图控制器,或者您可以将该视图控制器的 modalPresentationStyle
设置为 .pageSheet
然后展示它。
更新 - 2018 年 6 月:
@ChristopherSwasey 更新了 repo 以与 Swift 4 兼容。谢谢 Christopher!
对于未来的旅行者,Brian 的 post 非常好,但是有很多关于 UIPresentationController(促进此动画的)的重要信息,我强烈建议您研究一下。我创建了一个包含 Swift 1.2 版 iOS 邮件应用程序撰写动画的回购协议。我还在自述文件中放入了大量相关资源。请在这里查看:
https://github.com/kbpontius/iOSComposeAnimation
对于 Swift 2,您可以按照本教程进行操作:http://dativestudios.com/blog/2014/06/29/presentation-controllers/ 并替换:
override func frameOfPresentedViewInContainerView() -> CGRect {
// We don't want the presented view to fill the whole container view, so inset it's frame
let frame = self.containerView!.bounds;
var presentedViewFrame = CGRectZero
presentedViewFrame.size = CGSizeMake(frame.size.width, frame.size.height - 40)
presentedViewFrame.origin = CGPointMake(0, 40)
return presentedViewFrame
}
和:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromVC?.view
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toVC?.view
let containerView = transitionContext.containerView()
if isPresenting {
containerView?.addSubview(toView!)
}
let bottomVC = isPresenting ? fromVC : toVC
let bottomPresentingView = bottomVC?.view
let topVC = isPresenting ? toVC : fromVC
let topPresentedView = topVC?.view
var topPresentedFrame = transitionContext.finalFrameForViewController(topVC!)
let topDismissedFrame = topPresentedFrame
topPresentedFrame.origin.y += topDismissedFrame.size.height
let topInitialFrame = isPresenting ? topDismissedFrame : topPresentedFrame
let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
topPresentedView?.frame = topInitialFrame
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 300.0,
initialSpringVelocity: 5.0,
options: [.AllowUserInteraction, .BeginFromCurrentState], //[.Alert, .Badge]
animations: {
topPresentedView?.frame = topFinalFrame
let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
bottomPresentingView?.transform = CGAffineTransformScale(CGAffineTransformIdentity, scalingFactor, scalingFactor)
}, completion: {
(value: Bool) in
if !self.isPresenting {
fromView?.removeFromSuperview()
}
})
if isPresenting {
animatePresentationWithTransitionContext(transitionContext)
}
else {
animateDismissalWithTransitionContext(transitionContext)
}
}
我无法评论 MariSa 的回答,但我更改了他们的代码以使其真正起作用(可以使用一些清理,但它对我有用)
(Swift 3)
这里又是link:http://dativestudios.com/blog/2014/06/29/presentation-controllers/
在CustomPresentationController.swift中:
更新 dimmingView(让它变成黑色而不是示例中的红色)
lazy var dimmingView :UIView = {
let view = UIView(frame: self.containerView!.bounds)
view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
view.alpha = 0.0
return view
}()
按照 MariSa 的指示更新 frameOfPresentedViewInContainerView:
override var frameOfPresentedViewInContainerView : CGRect {
// We don't want the presented view to fill the whole container view, so inset it's frame
let frame = self.containerView!.bounds;
var presentedViewFrame = CGRect.zero
presentedViewFrame.size = CGSize(width: frame.size.width, height: frame.size.height - 40)
presentedViewFrame.origin = CGPoint(x: 0, y: 40)
return presentedViewFrame
}
在 CustomPresentationAnimationController 中:
更新 animateTransition(starting/ending 帧与 MariSa 的回答不同)
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let fromView = fromVC?.view
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let toView = toVC?.view
let containerView = transitionContext.containerView
if isPresenting {
containerView.addSubview(toView!)
}
let bottomVC = isPresenting ? fromVC : toVC
let bottomPresentingView = bottomVC?.view
let topVC = isPresenting ? toVC : fromVC
let topPresentedView = topVC?.view
var topPresentedFrame = transitionContext.finalFrame(for: topVC!)
let topDismissedFrame = topPresentedFrame
topPresentedFrame.origin.y -= topDismissedFrame.size.height
let topInitialFrame = topDismissedFrame
let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
topPresentedView?.frame = topInitialFrame
UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 300.0,
initialSpringVelocity: 5.0,
options: [.allowUserInteraction, .beginFromCurrentState], //[.Alert, .Badge]
animations: {
topPresentedView?.frame = topFinalFrame
let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
bottomPresentingView?.transform = CGAffineTransform.identity.scaledBy(x: scalingFactor, y: scalingFactor)
}, completion: {
(value: Bool) in
if !self.isPresenting {
fromView?.removeFromSuperview()
}
})
if isPresenting {
animatePresentationWithTransitionContext(transitionContext)
}
else {
animateDismissalWithTransitionContext(transitionContext)
}
}
更新 animatePresentationWithTransitionContext(再次不同的帧位置):
func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to)
else {
return
}
// Position the presented view off the top of the container view
presentedControllerView.frame = transitionContext.finalFrame(for: presentedController)
presentedControllerView.center.y += containerView.bounds.size.height
containerView.addSubview(presentedControllerView)
// Animate the presented view to it's final position
UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: {
presentedControllerView.center.y -= containerView.bounds.size.height
}, completion: {(completed: Bool) -> Void in
transitionContext.completeTransition(completed)
})
}