Swift之模態(tài)彈窗自定義二 2024-09-23 周一

簡(jiǎn)介

上一篇文章蝴蜓,我們討論了UIPresentationController鞭缭,實(shí)現(xiàn)了初步的過場(chǎng)動(dòng)畫自定義受神,也就是背景逐漸顯示抛猖,逐漸影藏。還有就是點(diǎn)擊蒙板退出鼻听,內(nèi)容高度自定義(不是全屏)财著。不過用下來還有以下幾個(gè)問題:
(1)加載是,從下到上的彈出很快就結(jié)束了撑碴,而背景蒙板的逐漸顯示時(shí)間較長(zhǎng)撑教。這是因?yàn)閺南碌缴系膹棾鰟?dòng)畫還是系統(tǒng)默認(rèn)的行為,時(shí)間較短醉拓,大約0.3秒左右伟姐。而蒙板的逐漸顯示是自定義的動(dòng)畫行為,我設(shè)置了較長(zhǎng)時(shí)間(3秒)亿卤,想看清楚一點(diǎn)愤兵。
(2)退出時(shí),更加明顯排吴,從上到下的退出很快秆乳,而背景蒙板的逐漸隱藏幾乎看不到。隨著系統(tǒng)默認(rèn)動(dòng)畫較快地退出,containerView都被系統(tǒng)收回了屹堰,其子視圖的動(dòng)畫當(dāng)然消失了肛冶。

實(shí)現(xiàn)自定義動(dòng)畫

  • 現(xiàn)在不想要系統(tǒng)的從下到上的動(dòng)畫,而是實(shí)現(xiàn)從右到左的動(dòng)畫扯键。

  • 統(tǒng)一時(shí)間長(zhǎng)度淑趾,讓視圖的進(jìn)場(chǎng)動(dòng)畫和背景蒙板顯示動(dòng)畫同步。

統(tǒng)一參數(shù)

(1) 動(dòng)畫結(jié)束后忧陪,最終vc的frame需要確定扣泊,這個(gè)當(dāng)做參數(shù)放入公共的代理對(duì)象中(就是那個(gè)default單例)

(2)動(dòng)畫的時(shí)長(zhǎng),幾個(gè)相關(guān)的對(duì)象都要用統(tǒng)一的參數(shù)嘶摊,這個(gè)也放在代理對(duì)象中

class TempTransitionDelegate: NSObject {
    /// 默認(rèn)單例
    public static let `default`: TempTransitionDelegate = {
        LogUtil.doPrint("TempTransitionDelegate `default` 實(shí)例被創(chuàng)建")
        return TempTransitionDelegate()
    }()
    
    /// 設(shè)置彈窗視圖的高度
    public var sheetHeight: CGFloat = 500
    let phoneWidth = UIScreen.main.bounds.width
    let phoneHeight = UIScreen.main.bounds.height
    var contentFrame: CGRect {
        CGRect(origin: CGPoint(x: 0, y: (phoneHeight - sheetHeight)), size: CGSize(width: phoneWidth, height: sheetHeight))
    }
    
    /// 設(shè)置動(dòng)畫時(shí)間(秒)
    public var animateDuration: TimeInterval = 3
}

自定義動(dòng)畫

  • 自定義對(duì)話延蟹,需要一個(gè)實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning的對(duì)象
@MainActor public protocol UIViewControllerAnimatedTransitioning : NSObjectProtocol {

    // This is used for percent driven interactive transitions, as well as for
    // container controllers that have companion animations that might need to
    // synchronize with the main animation.
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval

    // This method can only be a no-op if the transition is interactive and not a percentDriven interactive transition.
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
  • 這兩個(gè)函數(shù)都涉及到一個(gè)參數(shù)UIViewControllerContextTransitioning,這是動(dòng)畫過程中叶堆,系統(tǒng)給的一些有用信息
@MainActor public protocol UIViewControllerContextTransitioning : NSObjectProtocol {
    // The view in which the animated transition should take place.
    @available(iOS 2.0, *)
    var containerView: UIView { get }

    // This must be called whenever a transition completes (or is cancelled.)
    // Typically this is called by the object conforming to the
    // UIViewControllerAnimatedTransitioning protocol that was vended by the transitioning
    // delegate.  For purely interactive transitions it should be called by the
    // interaction controller. This method effectively updates internal view
    // controller state at the end of the transition.
    func completeTransition(_ didComplete: Bool)
}

    // Currently only two keys are defined by the
    // system - UITransitionContextToViewControllerKey, and
    // UITransitionContextFromViewControllerKey. 
    // Animators should not directly manipulate a view controller's views and should
    // use viewForKey: to get views instead.
    @available(iOS 2.0, *)
    func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?

containerView可以和UIPresentationController中的一樣理解阱飘,簡(jiǎn)單講就是一個(gè)全屏的容器。
completeTransition在動(dòng)畫結(jié)束的時(shí)候調(diào)用一下虱颗,告訴系統(tǒng)過場(chǎng)動(dòng)畫結(jié)束了沥匈。
viewController比較特殊,通過from和to兩個(gè)key忘渔,得到不同的VC高帖。進(jìn)場(chǎng)時(shí),我們顯然是需要toVC畦粮;而消失時(shí)散址,我們是需要fromVC。(多么腦殘的人才能想出這種API宣赔?)

  • 所以预麸,除了frame和動(dòng)畫時(shí)長(zhǎng),我們還需要一個(gè)參數(shù)isPresent來區(qū)分是載入還是退出儒将。這兩者的動(dòng)畫方向是不一樣的吏祸。

  • 考慮了以上因素后,代碼大致如下:

class TempAnimate: NSObject {
    /// 載入還是退出的標(biāo)志
    public var isPresent = true
    
    /// 視圖最終的frame
    public var frame: CGRect = UIScreen.main.bounds
    
    /// 動(dòng)畫時(shí)長(zhǎng)
    public var duration: TimeInterval = 0.3
}

extension TempAnimate: UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        /// 獲取源視圖控制器和目標(biāo)視圖控制器的視圖
        let fromVC = transitionContext.viewController(forKey: .from)!
        let toVC = transitionContext.viewController(forKey: .to)!
  
        /// 獲取容器視圖钩蚊,即動(dòng)畫發(fā)生的視圖
        let containerView = transitionContext.containerView
        
        if isPresent {
            /// 進(jìn)場(chǎng)動(dòng)畫從右向左進(jìn)入屏幕贡翘,取toVC
            containerView.addSubview(toVC.view)
            toVC.view.frame = frame.offsetBy(dx: UIScreen.main.bounds.width, dy: 0)
            UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
                toVC.view.frame = self.frame
            } completion: { _ in
                transitionContext.completeTransition(true)
            }
        } else {
            /// 退出動(dòng)畫從右向左離開屏幕,取fromVC
            containerView.addSubview(fromVC.view)
            fromVC.view.frame = frame
            UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
                fromVC.view.frame = self.frame.offsetBy(dx: -UIScreen.main.bounds.width, dy: 0)
            } completion: { _ in
                transitionContext.completeTransition(true)
            }
        }
    }
}

在代理方法中統(tǒng)一傳參

/// 代理方法
extension TempTransitionDelegate: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        LogUtil.doPrint("TempTransitionDelegate `animationController` forPresented 被調(diào)用")
        let animate = TempAnimate()
        animate.isPresent = true
        animate.duration = animateDuration
        animate.frame = contentFrame
        return animate
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        LogUtil.doPrint("TempTransitionDelegate `animationController` forDismissed 被調(diào)用")
        let animate = TempAnimate()
        animate.isPresent = false
        animate.duration = animateDuration
        animate.frame = contentFrame
        return animate
    }
    
    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        LogUtil.doPrint("TempTransitionDelegate `interactionControllerForPresentation` 被調(diào)用")
        return nil
    }
    
    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        LogUtil.doPrint("TempTransitionDelegate `interactionControllerForDismissal` 被調(diào)用")
        return nil
    }
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        presented.modalPresentationStyle = .custom
        LogUtil.doPrint("TempTransitionDelegate `presentationController` 被調(diào)用")
        let present = TempPresentation(presentedViewController: presented, presenting: presenting)
        present.duration = animateDuration
        present.frame = contentFrame
        return present
    }
}

大致的效果

動(dòng)畫同步了

同時(shí)放棄了系統(tǒng)從下到上的默認(rèn)彈出效果两疚,而是實(shí)現(xiàn)了從右到左的彈出效果床估,并且速度慢很多含滴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诱渤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谈况,更是在濱河造成了極大的恐慌勺美,老刑警劉巖递胧,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赡茸,居然都是意外死亡缎脾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門占卧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遗菠,“玉大人,你說我怎么就攤上這事华蜒≌尬常” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵叭喜,是天一觀的道長(zhǎng)贺拣。 經(jīng)常有香客問我,道長(zhǎng)捂蕴,這世上最難降的妖魔是什么譬涡? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮啥辨,結(jié)果婚禮上涡匀,老公的妹妹穿的比我還像新娘。我一直安慰自己溉知,他們只是感情好渊跋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著着倾,像睡著了一般拾酝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卡者,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天蒿囤,我揣著相機(jī)與錄音,去河邊找鬼崇决。 笑死材诽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恒傻。 我是一名探鬼主播脸侥,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼盈厘!你這毒婦竟也來了睁枕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎外遇,沒想到半個(gè)月后注簿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跳仿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年诡渴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菲语。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妄辩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出山上,到底是詐尸還是另有隱情恩袱,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布胶哲,位于F島的核電站畔塔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鸯屿。R本人自食惡果不足惜澈吨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寄摆。 院中可真熱鬧谅辣,春花似錦、人聲如沸婶恼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勾邦。三九已至蚣录,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眷篇,已是汗流浹背萎河。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕉饼,地道東北人虐杯。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像昧港,于是被迫代替她去往敵國(guó)和親擎椰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 iOS系統(tǒng)提供的模態(tài)彈窗已經(jīng)足夠好用了创肥,所以這方面一直不用操心达舒。另外值朋,自定義彈窗的實(shí)現(xiàn)方式過于復(fù)雜,很不好學(xué)...
    勇往直前888閱讀 230評(píng)論 0 1
  • 在實(shí)際開發(fā)中經(jīng)常會(huì)出現(xiàn)的效果圖 : 如何實(shí)現(xiàn)這種效果呢? 下面直接上代碼 ```objc //設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的代理 ...
    saiGo閱讀 2,141評(píng)論 2 1
  • 近期在flutter開發(fā)過程中休弃,遇到一些需要自定義轉(zhuǎn)場(chǎng)動(dòng)畫的情況,就想研究一下相關(guān)的知識(shí)圈膏,有興趣的小伙伴可以跟我一...
    percivals閱讀 1,863評(píng)論 0 1
  • 1. 自定義彈框 如上圖塔猾,常見的實(shí)現(xiàn)方式是把模態(tài)框作為一個(gè)View,需要的時(shí)候通過動(dòng)畫從底部彈出來稽坤。這樣做起來很方...
    Dariel閱讀 1,523評(píng)論 0 9
  • 最近把項(xiàng)目中使用到的部分組件和一些平時(shí)寫的組件整理了一下丈甸,放在一個(gè)小程序中,僅供參考尿褪!文章底部有g(shù)ithub鏈接睦擂。...
    杰銘的博客閱讀 1,520評(píng)論 0 3