iOS Developer

iOS -PageView父子控制器联动效果

2017-07-13  本文已影响134人  黄晓坚
效果图:
NoScrollViewEnable.gif ScrollViewEnable.gif

基本的思路:

首先将整个分解成两个视图 上面的titleView 和中间的contentViewtitleView我们一般遇到的效果都是要么是固定的无法拖动,要么是title标题太多一行无法放下需要拖动来完成
titleView我们所遇到比较多的样式一般都是:下划线移动、字体的放大、遮罩视图,所以我们将这些样式统一分类到titleStyle中进行管理,需要的时候在把效果打开

import UIKit

class HJTitleStyle {

    //是否可以滚动
    var isScrollEnable : Bool = false
    //titleView的高度
    var titleHeight : CGFloat = 44
    //默认的文字颜色
    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
    
    
    //是否显示底部滚动条
    var isShowBottomLine : Bool = false
    // 底部滚动条颜色
    var BottomLineColor : UIColor = UIColor(r: 255, g: 127, b: 0)
    //滚动条高度
    var BottomLineHeight : CGFloat = 2.0
    
    //是否进行缩放
    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的样式基本设置:

 // 设置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)
            
        }
        
    }
    
    // 设置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 {
                
                //字体的宽度来计算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 {//第一个Label
                    
                    titleX = style.Margin * 0.5
                    
                    if style.isShowBottomLine {
                        
                        BottomLine.frame.origin.x = titleX
                        BottomLine.frame.size.width = titleW
                    }
                    
                }else{
                    
            //如果不是第一个label  则labels数组则要减去刚刚的第一个已经设置好的label数量
                   let parlabel = labels[index - 1]
                   titleX = parlabel.frame.maxX + style.Margin
                }
            
            }else{//不能滚动
                
                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)
            }
            
        }
        
        //如果可以滚动 则设置scrollView的contenSize
        scrollView.contentSize = style.isScrollEnable ? CGSize(width:labels.last!.frame.maxX + style.Margin * 0.5 , height: 0) : CGSize.zero
    
    }

    // 设置底部滚动条
    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
    }
    
    //设置遮盖视图
    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的基本效果出来了,但是无法如果title过多的时候无法将所需要的titleLabel显示在中间,所以需要对titleLabel的位置进行设置:

//MARK: -设置被选中的Label自动滚动到中间
     func TitleLabelEnableScroll(){
    
        //判断样式是否需要滚动 如果样式需不需滚动 如果不需要则TitleLabel不需要滚动到中间
        guard  style.isScrollEnable else { return }
        
        //获取目标Label 
        let targetLabel = labels[currentIndex]
        
        //计算目标Label 和 中间位置的偏移量
        var offSetx = targetLabel.center.x - bounds.width * 0.5
        
         // 左边临界值 如果偏移量小于0 则把偏移量设置为0 这样第一个Label就无法滚动到中间
        if offSetx < 0 {
            
            offSetx = 0
            //右边临界值 最大偏移值=内容视图-宽度  这样不会导致最后一个滚到中间
        }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)
    }

}

基本的样式设置完成,最主要的效果还未实现,需要实现效果 我们需要几个参数:当前titleLabelindex 目标titleLabelindex 还需要一个进度值progress

//MARK: -对外调用的方法
extension HJTitleView {
    
    func setTitleWithProgress(progress : CGFloat, sourceIndex : Int, targetIndex:Int) {
        
        //取出当前Label 和 目标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
        
        //移动位置差值
        let moveToX = targetLabel.frame.origin.x - sourceLabel.frame.origin.x
        let moveToW = targetLabel.frame.width - sourceLabel.frame.width
        
        //计算 滚动条的移动范围
        if style.isShowBottomLine {
            
            BottomLine.frame.origin.x = sourceLabel.frame.origin.x + moveToX * progress
            BottomLine.frame.size.width = sourceLabel.frame.size.width + moveToW * progress
            
        }
        
        // 计算放大的效果
        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)
            
        }
        
        // 计算遮盖视图滚动
        
        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跟着联动,设置titleViewdelegate方法

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

通过代理方法告诉contentView我(titleView)现在在什么位置,你需要配合我(titleView)滚动到相应的控制器

ContentView中设置:
//MARK: -设置ContentView的Index对外方法
extension HJContentView {

    func contentViewSetupCurrentIndex(_ Currentindex : Int) {
        // 记录需要进行的点击事件
        isRepeatScrollDelegate = true
        
        //滚动到的位置
        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中的效果实现完成,那么需要实现的下一步则是拖动contentView实现titleView的动画效果,在实现效果的时候我们需要考虑好是左滑动还是右滑动,所以一般我们都是contentView都是使用UIScrollView,亦或者使用继承自UIScrollViewUICollectionView

    
            //定义目标Label的targetIndex 和 progress
           var targetIndex : Int = 0
           var progress : CGFloat = 0.0
          
            //当前位置的下标
          let currentIndex = Int(startOffsetX / scrollView.bounds.size.width)
            
            if startOffsetX < scrollView.contentOffset.x {//左滑动
             
                    targetIndex = currentIndex + 1
                
                // //防止过度滑动越界 最后一个子控制器的下标
                if targetIndex > ChildVC.count - 1 {
                    
                    targetIndex = ChildVC.count - 1
                }
                
              //进度值
              progress = (scrollView.contentOffset.x - startOffsetX) / scrollView.bounds.size.width
            }else{//右滑动
            
                targetIndex = currentIndex - 1
                
                //防止过度滑动越界 第一个子控制器的下标
                if targetIndex < 0 {
                    targetIndex = 0
                }
                //进度值
                progress = (startOffsetX - scrollView.contentOffset.x) / scrollView.bounds.size.width
            }
            
            delegate?.contentView(self, currentIndex: currentIndex, targetIndex: targetIndex, progress: progress)
            
        }


以上只是大概的思路解析,如若不懂的可以去下载Demo逐步解析
下载地址:HJPageView-父子控制器联动

上一篇下一篇

猜你喜欢

热点阅读