iOS -PageView父子控制器聯(lián)動(dòng)效果

效果圖:
NoScrollViewEnable.gif
ScrollViewEnable.gif

基本的思路:

首先將整個(gè)分解成兩個(gè)視圖 上面的titleView 和中間的contentViewtitleView我們一般遇到的效果都是要么是固定的無(wú)法拖動(dòng),要么是title標(biāo)題太多一行無(wú)法放下需要拖動(dòng)來(lái)完成
titleView我們所遇到比較多的樣式一般都是:下劃線移動(dòng)、字體的放大、遮罩視圖,所以我們將這些樣式統(tǒng)一分類到titleStyle中進(jìn)行管理,需要的時(shí)候在把效果打開(kāi)

import UIKit

class HJTitleStyle {

    //是否可以滾動(dòng)
    var isScrollEnable : Bool = false
    //titleView的高度
    var titleHeight : CGFloat = 44
    //默認(rèn)的文字顏色
    var normalColor : UIColor = UIColor(r: 0, g: 0, b: 0)
    //選中的文字顏色
    var selectColor : UIColor = UIColor(r: 255, g: 127, b: 0)
    //字體大小
    var font : UIFont = UIFont.systemFont(ofSize: 14)
    //間距
    var Margin : CGFloat = 20
    
    
    //是否顯示底部滾動(dòng)條
    var isShowBottomLine : Bool = false
    // 底部滾動(dòng)條顏色
    var BottomLineColor : UIColor = UIColor(r: 255, g: 127, b: 0)
    //滾動(dòng)條高度
    var BottomLineHeight : CGFloat = 2.0
    
    //是否進(jìn)行縮放
    var isNeedScale : Bool = false
    //縮放大小
    var ScaleRange : CGFloat = 1.2
    
    //是否顯示遮蓋
    var isShowCover : Bool = false
    //遮蓋的高度
    var CoverHeight : CGFloat = 25
    //遮蓋的顏色
    var CoverColor : UIColor = UIColor.white
    //遮蓋與文字的間隙
    var CoverMargin : CGFloat = 5
    //遮蓋的圓角
    var CoverRadius : CGFloat = 12
    
}

titleView的樣式基本設(shè)置:

 // 設(shè)置Label
    fileprivate func setupLabels(){
    
        for (i,title) in titles.enumerated() {
            
            let label = UILabel()
            label.tag = i
            label.text = title
            label.textAlignment = .center
            label.textColor = i == 0 ? style.selectColor : style.normalColor
            label.font = style.font
            
            label.isUserInteractionEnabled = true
            let tap = UITapGestureRecognizer(target: self, action: #selector(LabelClickTap(_:)))
            
            label.addGestureRecognizer(tap)
            
            labels.append(label)
            scrollView.addSubview(label)
            
        }
        
    }
    
    // 設(shè)置Label的Frame
    fileprivate func setupLaeblFrame(){
    
        var titleW : CGFloat = 0
        let titleH : CGFloat = bounds.height
        var titleX : CGFloat = 0
        let titleY : CGFloat = 0
        
        let count = titles.count
        
        for (index,titleLaebel) in labels.enumerated() {
           
            if style.isScrollEnable {
                
                //字體的寬度來(lái)計(jì)算label的寬度
                titleW = (titles[index] as NSString).boundingRect(with: CGSize(width:CGFloat(MAXFLOAT),height:0), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:style.font], context: nil).width
                
                
                if index == 0 {//第一個(gè)Label
                    
                    titleX = style.Margin * 0.5
                    
                    if style.isShowBottomLine {
                        
                        BottomLine.frame.origin.x = titleX
                        BottomLine.frame.size.width = titleW
                    }
                    
                }else{
                    
            //如果不是第一個(gè)label  則labels數(shù)組則要減去剛剛的第一個(gè)已經(jīng)設(shè)置好的label數(shù)量
                   let parlabel = labels[index - 1]
                   titleX = parlabel.frame.maxX + style.Margin
                }
            
            }else{//不能滾動(dòng)
                
                titleW = frame.width / CGFloat(count)
                titleX = CGFloat(index) * titleW
                
                if index == 0 && style.isShowBottomLine {
                    
                    BottomLine.frame.origin.x = titleX
                    BottomLine.frame.size.width = titleW
                    
                }
            }
            
        
            titleLaebel.frame = CGRect(x: titleX, y: titleY, width: titleW, height: titleH)
            
            //放大
            if index == 0 {
                
                let scale = self.style.isNeedScale ? style.ScaleRange : 1.0
                
                titleLaebel.transform = CGAffineTransform(scaleX:scale,y:scale)
            }
            
        }
        
        //如果可以滾動(dòng) 則設(shè)置scrollView的contenSize
        scrollView.contentSize = style.isScrollEnable ? CGSize(width:labels.last!.frame.maxX + style.Margin * 0.5 , height: 0) : CGSize.zero
    
    }

    // 設(shè)置底部滾動(dòng)條
    fileprivate func setupBottomLine(){
        scrollView.addSubview(BottomLine)
        BottomLine.frame = labels.first!.frame
        BottomLine.frame.size.height = style.BottomLineHeight
        BottomLine.frame.origin.y = bounds.height - style.BottomLineHeight
    }
    
    //設(shè)置遮蓋視圖
    fileprivate func setupCoverView(){
        
        scrollView.insertSubview(CoverView, at: 0)
        
        let firtLabel = labels[0]
        let coverH : CGFloat = self.style.CoverHeight
        var coverW : CGFloat = firtLabel.frame.size.width
        var coverX : CGFloat = firtLabel.frame.origin.x
        let coverY : CGFloat = (frame.size.height - self.style.CoverHeight) * 0.5
        
        if style.isScrollEnable {
           
            coverX -= style.CoverMargin
            coverW += style.CoverMargin * 2
            
        }
        
        CoverView.frame = CGRect(x: coverX, y: coverY, width: coverW, height: coverH)
        CoverView.layer.cornerRadius = style.CoverRadius
        CoverView.layer.masksToBounds = true
        
    }

titleView的基本效果出來(lái)了,但是無(wú)法如果title過(guò)多的時(shí)候無(wú)法將所需要的titleLabel顯示在中間,所以需要對(duì)titleLabel的位置進(jìn)行設(shè)置:

//MARK: -設(shè)置被選中的Label自動(dòng)滾動(dòng)到中間
     func TitleLabelEnableScroll(){
    
        //判斷樣式是否需要滾動(dòng) 如果樣式需不需滾動(dòng) 如果不需要?jiǎng)tTitleLabel不需要滾動(dòng)到中間
        guard  style.isScrollEnable else { return }
        
        //獲取目標(biāo)Label 
        let targetLabel = labels[currentIndex]
        
        //計(jì)算目標(biāo)Label 和 中間位置的偏移量
        var offSetx = targetLabel.center.x - bounds.width * 0.5
        
         // 左邊臨界值 如果偏移量小于0 則把偏移量設(shè)置為0 這樣第一個(gè)Label就無(wú)法滾動(dòng)到中間
        if offSetx < 0 {
            
            offSetx = 0
            //右邊臨界值 最大偏移值=內(nèi)容視圖-寬度  這樣不會(huì)導(dǎo)致最后一個(gè)滾到中間
        }else if offSetx > scrollView.contentSize.width - scrollView.bounds.width{
        
            offSetx = scrollView.contentSize.width - scrollView.bounds.width
        }
    
        scrollView.setContentOffset(CGPoint(x:offSetx,y:0), animated: true)
    }

}

基本的樣式設(shè)置完成,最主要的效果還未實(shí)現(xiàn),需要實(shí)現(xiàn)效果 我們需要幾個(gè)參數(shù):當(dāng)前titleLabelindex 目標(biāo)titleLabelindex 還需要一個(gè)進(jìn)度值progress

//MARK: -對(duì)外調(diào)用的方法
extension HJTitleView {
    
    func setTitleWithProgress(progress : CGFloat, sourceIndex : Int, targetIndex:Int) {
        
        //取出當(dāng)前Label 和 目標(biāo)Label
        let sourceLabel = labels[sourceIndex]
        let targetLabel = labels[targetIndex]
        
    
                
        //顏色差值
        let diffVulesColor = (selectColorRGB.0 - normalColorRGB.0,selectColorRGB.1 - normalColorRGB.1,selectColorRGB.2 - normalColorRGB.2)
        //顏色變化
        sourceLabel.textColor = UIColor(r:selectColorRGB.0 - diffVulesColor.0 * progress,g:selectColorRGB.1 - diffVulesColor.1 * progress, b:selectColorRGB.2 - diffVulesColor.2 * progress)
        targetLabel.textColor = UIColor(r:normalColorRGB.0 + diffVulesColor.0 * progress,g:normalColorRGB.1 + diffVulesColor.1 * progress ,b: normalColorRGB.2 + diffVulesColor.2 * progress)
        //記錄最新的Index
        currentIndex = targetIndex
        
        //移動(dòng)位置差值
        let moveToX = targetLabel.frame.origin.x - sourceLabel.frame.origin.x
        let moveToW = targetLabel.frame.width - sourceLabel.frame.width
        
        //計(jì)算 滾動(dòng)條的移動(dòng)范圍
        if style.isShowBottomLine {
            
            BottomLine.frame.origin.x = sourceLabel.frame.origin.x + moveToX * progress
            BottomLine.frame.size.width = sourceLabel.frame.size.width + moveToW * progress
            
        }
        
        // 計(jì)算放大的效果
        if style.isNeedScale {
            
             let diffScale = (style.ScaleRange - 1.0) * progress
            sourceLabel.transform = CGAffineTransform(scaleX: style.ScaleRange - diffScale, y: style.ScaleRange - diffScale)
            targetLabel.transform = CGAffineTransform(scaleX: 1.0 + diffScale,y: 1.0 + diffScale)
            
        }
        
        // 計(jì)算遮蓋視圖滾動(dòng)
        
        if style.isShowCover {
            
            CoverView.frame.origin.x = style.isScrollEnable ? (sourceLabel.frame.origin.x - style.CoverMargin + moveToX * progress) : (sourceLabel.frame.origin.x + moveToX * progress)
            CoverView.frame.size.width = style.isScrollEnable ? (sourceLabel.frame.size.width + 2 * style.CoverMargin + moveToW * progress) : (sourceLabel.frame.size.width + moveToW * progress)
            
        }
    
    }
    

樣式的完成,則下一步是如何讓我們的contentView跟著聯(lián)動(dòng),設(shè)置titleViewdelegate方法

protocol HJTitleViewDelegate : class {
    func titleView(_ titleView : HJTitleView,selectedIndex index:Int)
}

通過(guò)代理方法告訴contentView我(titleView)現(xiàn)在在什么位置,你需要配合我(titleView)滾動(dòng)到相應(yīng)的控制器

ContentView中設(shè)置:
//MARK: -設(shè)置ContentView的Index對(duì)外方法
extension HJContentView {

    func contentViewSetupCurrentIndex(_ Currentindex : Int) {
        // 記錄需要進(jìn)行的點(diǎn)擊事件
        isRepeatScrollDelegate = true
        
        //滾動(dòng)到的位置
        let offSetX = CGFloat(Currentindex) * collectionView.frame.size.width
        collectionView.setContentOffset(CGPoint(x:offSetX,y:0), animated: false)
        
    }

}

PageView中遵守TitleViewDelegate
//MARK: -遵守TitleViewDelegate
extension HJPageView : HJTitleViewDelegate {
    
    func titleView(_ titleView: HJTitleView, selectedIndex index: Int) {
        
        ContentView.contentViewSetupCurrentIndex(index)
        print(index)
    }
    
}

titleView中的效果實(shí)現(xiàn)完成,那么需要實(shí)現(xiàn)的下一步則是拖動(dòng)contentView實(shí)現(xiàn)titleView的動(dòng)畫(huà)效果,在實(shí)現(xiàn)效果的時(shí)候我們需要考慮好是左滑動(dòng)還是右滑動(dòng),所以一般我們都是contentView都是使用UIScrollView,亦或者使用繼承自UIScrollViewUICollectionView

    
            //定義目標(biāo)Label的targetIndex 和 progress
           var targetIndex : Int = 0
           var progress : CGFloat = 0.0
          
            //當(dāng)前位置的下標(biāo)
          let currentIndex = Int(startOffsetX / scrollView.bounds.size.width)
            
            if startOffsetX < scrollView.contentOffset.x {//左滑動(dòng)
             
                    targetIndex = currentIndex + 1
                
                // //防止過(guò)度滑動(dòng)越界 最后一個(gè)子控制器的下標(biāo)
                if targetIndex > ChildVC.count - 1 {
                    
                    targetIndex = ChildVC.count - 1
                }
                
              //進(jìn)度值
              progress = (scrollView.contentOffset.x - startOffsetX) / scrollView.bounds.size.width
            }else{//右滑動(dòng)
            
                targetIndex = currentIndex - 1
                
                //防止過(guò)度滑動(dòng)越界 第一個(gè)子控制器的下標(biāo)
                if targetIndex < 0 {
                    targetIndex = 0
                }
                //進(jìn)度值
                progress = (startOffsetX - scrollView.contentOffset.x) / scrollView.bounds.size.width
            }
            
            delegate?.contentView(self, currentIndex: currentIndex, targetIndex: targetIndex, progress: progress)
            
        }


以上只是大概的思路解析,如若不懂的可以去下載Demo逐步解析
下載地址:HJPageView-父子控制器聯(lián)動(dòng)

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

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,141評(píng)論 4 61
  • 廢話不多說(shuō),直接上干貨 ---------------------------------------------...
    小小趙紙農(nóng)閱讀 3,646評(píng)論 0 15
  • 職場(chǎng)中,有些事情需要提交規(guī)劃,否則追悔莫及。有些事情,明白越早越容易成功。 如今信息時(shí)代變化莫測(cè),小公司平均壽命2...
    哈默老師閱讀 500評(píng)論 1 4
  • 當(dāng)我再次寫(xiě)下“微亮”這兩個(gè)字時(shí),時(shí)間又過(guò)去了三年。 微博還有著刷新不完的狀態(tài),微信還有著數(shù)不盡的推送,它們拼命的告...
    Tang_here閱讀 361評(píng)論 0 1
  • 設(shè)計(jì)這幾套圖標(biāo)已是半年前的事情了,是大三暑期的一份實(shí)習(xí),還記得那短短一個(gè)月的時(shí)間硬是讓人過(guò)出幾分煎熬來(lái),但現(xiàn)在看著...
    傻小貓閱讀 2,601評(píng)論 6 8

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