美文网首页iOS Developer
iOS -PageView父子控制器联动效果

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

作者: 黄晓坚 | 来源:发表于2017-07-13 21:44 被阅读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-父子控制器联动

    相关文章

      网友评论

        本文标题:iOS -PageView父子控制器联动效果

        本文链接:https://www.haomeiwen.com/subject/rneghxtx.html