自定义ViewControllers演示

您可以在我的博客上阅读此文章

我们的应用程序中发生的大多数屏幕转换都属于以下类别:

  • 模态演示
  • 在导航堆栈上推送/弹出

尽管这足以完成我们需要做的大多数工作,但在某些极端情况下,我们可能希望采用一种“原生”方式少而定制化的解决方案。

对我们来说幸运的是,UIKit为这项工作提供了正确的工具。

与往常一样,我们不必离官方文档(View Controller编程指南)太远,更详细地说,我们正在寻找创建自定义演示文稿。 本指南在文档档案库中,并带有Objective-C示例,但它们仍然是您可以在其中找到的最佳内容。

阅读指南将揭示一些有趣的观点。 当将要显示视图控制器时,UIKit将执行以下操作:

  1. 调用过渡委托的presentationControllerForPresentedViewController:presentingViewController:sourceViewController:方法以检索您的自定义表示控制器。
  2. 向过渡委托者询问动画师和交互式动画师对象(如果有)。
  3. 调用演示文稿控制器的presentationTransitionWillBegin方法。
  4. 执行过渡动画。 在动画过程中,UIKit调用演示文稿控制器的containerViewWillLayoutSubviewscontainerViewDidLayoutSubviews方法,以便您可以根据需要调整自定义视图的布局。
  5. 过渡动画结束时,调用presentationTransitionDidEnd :方法。

因此,我们有几点可以操作和更改动画和演示样式。

展示横幅

假设我们要将文件上传到我们的服务器,并且我们希望在操作完成后通知用户:

 func upload(file: File, using uploader: FileUploader) { 
uploader.send(file, then: {
let banner = Banner(message: "File successfully uploaded ✅")
self.present(banner, animated: true)
})
}

现在这将以模态形式全屏显示我们的横幅,但是我们可能希望使其看起来像屏幕底部的横幅,以减少入侵。

我们将专注于自定义演示,同时使用底部的默认过渡。

首先,让我们创建我们的自定义表示控制器:

 class BannerPresentationController: UIPresentationController { 

override var frameOfPresentedViewInContainerView: CGRect {
//here we should compute the frame for the presented banner
}

override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
}

override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedView?.layer.cornerRadius = 12
}
}

这里的关键是frameOfPresentedViewInContainerView属性,我们将使用该属性来计算横幅的适当大小。

我们应该考虑安全区域,并在给定固定宽度(屏幕宽度减去插图)的情况下计算所需的高度。

这项工作的工具是UIView.systemLayoutSizeFitting

systemLayoutSizeFitting(targetSize:horizontalFittingPriority:verticalFittingPriority:)方法,该方法根据视图的约束或固有内容大小来计算视图的所需大小。

我们的实现应如下所示:

 override var frameOfPresentedViewInContainerView: CGRect { 

let safeBounds = containerView.bounds.inset(by: containerView.safeAreaInsets)
let inset: CGFloat = 16

let targetWidth = safeBounds.width - 2*16
let targetSize = CGSize(
width: targetWidth,
height: UIView.layoutFittingCompressedSize.height
)
let targetHeight = presentedView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height

return CGRect(x: inset, y: yPosition, width: targetWidth, height: targetHeight)
}

拼凑在一起

现在,我们需要做的就是为Banner分配全新的BannerPresentationController作为演示控制器:

 extension Banner: UIViewControllerTransitioningDelegate { 
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return BannerPresentationController(presentedViewController: presented, presenting: presenting)
}
}

//when presenting the banner
banner.transitioningDelegate = banner

并指定自定义演示样式:

 banner.modalPresentationStyle = .custom 

结果将是这样的:

结论

尽管与响应式概念等UI开发的最新趋势相去甚远,但UIKit已表明它是一个具有多个自定义点的功能强大的框架。

还有很多事情可以做,例如更改演示动画和时间安排,但我们将其保存以备将来使用。

希望您喜欢这个小实验😊,如果喜欢,请访问我的网站:http://marcocapano.vapor.cloud/