前言
在我們平時(shí)日常開發(fā)中,經(jīng)常會(huì)遇到各種樣式的彈框扬跋。你是否也經(jīng)常遇到呢?你是如何實(shí)現(xiàn)的凌节?
本文介紹使用UIPresentationController
钦听,結(jié)合自定義轉(zhuǎn)場(chǎng)動(dòng)效,實(shí)現(xiàn)一個(gè)高度自定義的彈框倍奢,這也是蘋果比較推薦的一種實(shí)現(xiàn)方式朴上。
預(yù)備知識(shí)
開始之前,我們要了解下幾個(gè)知識(shí)點(diǎn):
UIPresentationController
UIViewControllerTransitioningDelegate
UIViewControllerAnimatedTransitioning
1卒煞、UIPresentationController
是什么痪宰?官方文檔中介紹如下:
An object that manages the transition animations and the presentation of view controllers onscreen.
簡(jiǎn)單來說,它可以管理轉(zhuǎn)場(chǎng)動(dòng)畫和模態(tài)出來的窗口控制器畔裕。詳細(xì)信息可以參考:UIPresentationController文檔
2衣撬、UIViewControllerTransitioningDelegate
定義了轉(zhuǎn)場(chǎng)代理方法,可以指定Presented
和Dismissed
動(dòng)畫扮饶,以及UIPresentationController
具练。
3、UIViewControllerAnimatedTransitioning
就是轉(zhuǎn)場(chǎng)動(dòng)畫協(xié)議甜无,我們可以遵守該協(xié)議扛点,實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫。
實(shí)現(xiàn)
1岂丘、自定義UIPresentationController
陵究,并實(shí)現(xiàn)相應(yīng)方法
struct ZCXPopup {}
extension ZCXPopup {
class PresentationController: UIPresentationController {
override func presentationTransitionWillBegin() {
guard let containerView else { return }
dimmingView.frame = containerView.bounds
dimmingView.alpha = 0.0
containerView.insertSubview(dimmingView, at: 0)
// 背景蒙層淡入動(dòng)畫
presentedViewController.transitionCoordinator?.animate { _ in
self.dimmingView.alpha = 1.0
}
}
override func dismissalTransitionWillBegin() {
// 背景蒙層淡出動(dòng)畫,以及移除操作
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0.0
}, completion: { _ in
self.dimmingView.removeFromSuperview()
})
}
override var frameOfPresentedViewInContainerView: CGRect { UIScreen.main.bounds }
override func containerViewWillLayoutSubviews() {
guard let containerView else { return }
dimmingView.frame = containerView.bounds
guard let presentedView else { return }
presentedView.frame = frameOfPresentedViewInContainerView
}
// MARK: -
/// 背景蒙層
private lazy var dimmingView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
}
}
代碼比較簡(jiǎn)單奥帘,主要的工作就是添加了一個(gè)背景蒙層铜邮,以及蒙層的動(dòng)畫交互處理,加上子視圖尺寸的控制。
注:上面的
ZCXPopup
結(jié)構(gòu)體沒有實(shí)際作用松蒜,僅僅是為了區(qū)分命名空間扔茅。
2、UIViewControllerAnimatedTransitioning
實(shí)現(xiàn)類實(shí)現(xiàn)
extension ZCXPopup {
class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private var isOpen: Bool = false
convenience init(isOpen: Bool = false) {
self.init()
self.isOpen = isOpen
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
transitionContext?.isAnimated == true ? 0.5 : 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.viewController(forKey: .from)?.view else { return }
guard let toView = transitionContext.viewController(forKey: .to)?.view else { return }
if isOpen {
transitionContext.containerView.addSubview(toView)
toView.transform = .init(scaleX: 0.7, y: 0.7)
toView.alpha = 0
}
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: []) {
if self.isOpen {
toView.transform = .identity
toView.alpha = 1
} else {
fromView.transform = .init(scaleX: 0.7, y: 0.7)
fromView.alpha = 0
}
} completion: { _ in
let wasCancelled = transitionContext.transitionWasCancelled
transitionContext.completeTransition(!wasCancelled)
}
}
}
}
這個(gè)實(shí)現(xiàn)類的內(nèi)容也較簡(jiǎn)單牍鞠,主要是設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)長(zhǎng)咖摹,以及實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫,轉(zhuǎn)場(chǎng)動(dòng)畫分為進(jìn)場(chǎng)(present
)和出場(chǎng)(dismiss
)動(dòng)畫难述。
3萤晴、UIViewControllerTransitioningDelegate
實(shí)現(xiàn)類實(shí)現(xiàn)
extension ZCXPopup {
class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController
) -> UIPresentationController? {
PresentationController(presentedViewController: presented, presenting: presenting)
}
func animationController(
forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
TransitionAnimator(isOpen: true)
}
func animationController(
forDismissed dismissed: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
TransitionAnimator(isOpen: false)
}
}
}
在該實(shí)現(xiàn)類中,實(shí)現(xiàn)代理方法胁后,分別返回自定義的PresentationController
和TransitionAnimator
即可店读。
4、為控制器增加一個(gè)擴(kuò)展攀芯,方便使用彈框交互
extension UIViewController {
/// 轉(zhuǎn)場(chǎng)類型屯断,方便后續(xù)擴(kuò)展
@objc public enum TransitioningType: Int {
case none = 0
case popup = 1
}
/// 設(shè)置轉(zhuǎn)場(chǎng)類型
@objc public var transitioningType: TransitioningType {
get { getAssociatedObject() as? TransitioningType ?? .none }
set {
if newValue == .popup {
transitioningDelegate = self.popupTransitioningDelegate
modalPresentationStyle = .custom
}
setAssociatedObject(newValue)
}
}
/// transitioningDelegate 實(shí)現(xiàn)類,需要被持有
private var popupTransitioningDelegate: ZCXPopup.TransitioningDelegate {
lazyVarAssociatedObject { ZCXPopup.TransitioningDelegate() }
}
}
到這里侣诺,一個(gè)輕量級(jí)的彈窗管理就封裝好了殖演。我們就可以給任意一個(gè)控制器加上這個(gè)交互。
自定義彈框
上面只是封裝了彈框的交互年鸳,那么我們要怎么實(shí)現(xiàn)一個(gè)彈框呢趴久?
很簡(jiǎn)單,具體來說就是搔确,創(chuàng)建一個(gè)控制器彼棍,將其view
設(shè)置成透明,然后在其中間加上彈框內(nèi)容視圖contentView
膳算。然后座硕,設(shè)置控制器的transitioningType = .popup
涕蜂,使用present
方式打開即可华匾。
這里大家可能會(huì)問,為什么不直接修改控制器的
preferredContentSize
宇葱,而是弄了一個(gè)背景透明的全屏控制器瘦真。這個(gè)問題非常好,歡迎留言討論黍瞧。
設(shè)置轉(zhuǎn)場(chǎng)類型和打開彈框:
@IBAction func showPopup(_ sender: Any) {
let sb = UIStoryboard(name: "DemoViewController", bundle: nil)
guard let controller = sb.instantiateInitialViewController() else { return }
controller.transitioningType = .popup
present(controller, animated: true)
}
關(guān)閉彈框:
class DemoViewController: UIViewController {
@IBAction func dismiss(_ sender: Any) {
dismiss(animated: true)
}
}
總結(jié)
上述方法,可以將彈框的交互獨(dú)立封裝出來原杂,具體的業(yè)務(wù)彈框只需要實(shí)現(xiàn)好UI
和交互事件印颤,以及相應(yīng)功能即可,彈框的打開和關(guān)閉穿肄,使用present
和dismiss
即可年局。
可以看到际看,彈框交互和業(yè)務(wù)可以完全解耦,這也是能做到彈框的高度可定制的核心矢否。我們可以將這個(gè)交互沉淀到基礎(chǔ)庫(kù)仲闽,用來規(guī)范項(xiàng)目中彈框的統(tǒng)一交互。
思考題
點(diǎn)擊彈框空白區(qū)域關(guān)閉彈框僵朗,這個(gè)處理放在哪里實(shí)現(xiàn)更合適赖欣?歡迎留言討論。
源碼
參考
UIPresentationController
UIViewControllerAnimatedTransitioning
UIViewControllerTransitioningDelegate