【iOS】仿微信懸浮窗

關(guān)鍵詞

轉(zhuǎn)場動(dòng)畫,手勢監(jiān)聽,核心動(dòng)畫

運(yùn)行效果

浮窗運(yùn)行效果.gif

使用簡介

// []中存放需要懸浮的類,vcname指類名
FloatViewManager.manager.addFloatVcsClass(vcs: [vcname])

主要使用類目及功能

整體涉及以下幾個(gè)主要的類,并注明其功能點(diǎn)

  • FloatViewManager單例,用來管理懸浮窗信息以及在window上的視圖。
  • TransitionPush / TransitionPop自定義導(dǎo)航轉(zhuǎn)場動(dòng)畫
  • FloatBallView屏幕上圓形浮標(biāo),可拖動(dòng)
  • BottomFloatView底部繪制黑色或者紅色視圖

思路

  1. 首先初始化項(xiàng)目時(shí),為了監(jiān)聽手勢移動(dòng)變化
    ,自定義轉(zhuǎn)場,手勢代理
    交由FloatViewManager來管理。
currentNavtigationController()?.interactivePopGestureRecognizer?.delegate = self
        currentNavtigationController()?.delegate = self
  1. 當(dāng)進(jìn)入可支持懸浮的控制器時(shí),需要根據(jù)手勢的偏移來計(jì)算底部黑色半透明框的移動(dòng),這里我們使用以下來做監(jiān)聽,注意這里一定要進(jìn)行安全判斷。
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// 當(dāng)前導(dǎo)航控制器是否存在子集合
        guard  let vcs = currentNavtigationController()?.viewControllers else{
            return false
        }
        
// 如果是根控制器,不做處理
        guard vcs.count > 1 else {
            return false
        }
        
// 判斷當(dāng)前的控制器與開始數(shù)組中的支持懸浮的控制器是否一致,只有一致才執(zhí)行下一步,并開啟監(jiān)聽
        if  let currentVisiableVC = currentViewController() {
             let currentVCClassName = "\(currentVisiableVC.self)"
             if currentVCClassName.contains(floatVcClass.first!){
                startDisplayLink()
                edgeGesture = (gestureRecognizer as? UIScreenEdgePanGestureRecognizer) ?? nil
                tempCurrentFloatVC = currentVisiableVC
            }
        }
        return true
    }
}

  1. 根據(jù)監(jiān)聽的結(jié)果更新底部的半透明視圖,這里詳細(xì)代碼請參見源代碼。
  2. 在手勢結(jié)束完之后,判斷是否懸浮,若最終結(jié)束手勢在底部黑色透明內(nèi),懸浮并展示圓形浮標(biāo),反之隱藏。
@objc func displayLinkLoop() {
        if edgeGesture?.state == UIGestureRecognizerState.changed{
            guard let startP = edgeGesture?.location(in:kWindow) else {
                return
            }
    
            let orx : CGFloat =  max(screenWidth - startP.x, kBvfMinX)
            let ory : CGFloat = max(screenHeight - startP.x, kBvMinY)
            bFloatView.frame = CGRect(x: orx, y: ory, width: kBottomViewFloatWidth, height: kBottomViewFloatHeight)

            // 將點(diǎn)轉(zhuǎn)化到底部視圖上,計(jì)算是否在黑色圓內(nèi)
            guard  let transfomBottomP = kWindow?.convert(startP, to: bFloatView) else{
                return
            }
            
         //   print(transfomBottomP)
            if transfomBottomP.x > 0 && transfomBottomP.y > 0{
                let arcCenter = CGPoint(x: kBottomViewFloatWidth, y: kBottomViewFloatHeight)
                let distance = pow((transfomBottomP.x - arcCenter.x),2) + pow((transfomBottomP.y - arcCenter.y),2)
                let onArc = pow(arcCenter.x,2)
                if distance <= onArc{
                    if(!bFloatView.insideBottomSeleted){
                        bFloatView.insideBottomSeleted = true
                    }
                }else{
                    if(bFloatView.insideBottomSeleted){
                        bFloatView.insideBottomSeleted = false
                    }
                }
            }else{
                if(bFloatView.insideBottomSeleted){
                    bFloatView.insideBottomSeleted = false
                }
            }
        }else if(edgeGesture?.state == UIGestureRecognizerState.possible){
            
//結(jié)束的時(shí)候判斷最終手指的位置即黑色透明視圖是否是選中狀態(tài)。若選中,存儲(chǔ)當(dāng)前控制器,并暫停掉定時(shí)器(這里一定要暫停,不然浪費(fèi)資源)
            if(bFloatView.insideBottomSeleted){
                currentFloatVC = tempCurrentFloatVC
                tempCurrentFloatVC = nil
                ballView.show = true
                
                if let newDetailVC = currentFloatVC as? NewDetailController{
                    ballView.backgroundColor = newDetailVC.themeColor
                }
            }
            // 隱藏底部黑色透明視圖
            UIView.animate(withDuration: animationConst().animationDuration, animations: { 
                  self.bFloatView.frame = CGRect(x: screenWidth, y: screenHeight, width: kBottomViewFloatWidth, height:kBottomViewFloatHeight)
            }) { (_) in
                
            }
            stopDisplayLink()
        }
    }
  1. 圓形浮標(biāo)支持拖動(dòng),并且提供點(diǎn)擊,拖動(dòng)手勢代理方法供FloatViewManager使用更新相關(guān)視圖,參見源代碼
  2. 當(dāng)用戶返回到其它界面,只要保證能找到最頂部導(dǎo)航,就可以再次打開懸浮窗控制器。這里主要是自定義轉(zhuǎn)場動(dòng)畫push/pop。
  3. 當(dāng)用戶手指手動(dòng)懸浮窗取消懸浮時(shí),將單例中保存所有的數(shù)據(jù)清空,保證再次可以正常使用。

缺陷

微信此功能,手指側(cè)滑至大于0.5松開,也會(huì)執(zhí)行pop的轉(zhuǎn)場動(dòng)畫,但我一直沒有找到合適的有效的解決方案,如有解決或者知曉方案的可以一起交流一下。

個(gè)人理解

看到技術(shù)論壇有人仿寫,于是自己也好奇嘗試著用swift做了一下,主題功能不難,只是有點(diǎn)繁瑣。

源碼

Git源碼

感謝作者及其以下博客,如有問題歡迎私信批評(píng)指正

Customizing the Transition Animations
UINavigationController內(nèi)的轉(zhuǎn)場動(dòng)畫
iOS浮窗

世界杯,中國隊(duì),加油~~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容