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

效果图:
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-父子控制器联动

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,983评论 4 60
  • 废话不多说,直接上干货 ---------------------------------------------...
    小小赵纸农阅读 3,321评论 0 15
  • 职场中,有些事情需要提交规划,否则追悔莫及。有些事情,明白越早越容易成功。 如今信息时代变化莫测,小公司平均寿命2...
    哈默老师阅读 410评论 1 4
  • 当我再次写下“微亮”这两个字时,时间又过去了三年。 微博还有着刷新不完的状态,微信还有着数不尽的推送,它们拼命的告...
    Tang_here阅读 339评论 0 1
  • 设计这几套图标已是半年前的事情了,是大三暑期的一份实习,还记得那短短一个月的时间硬是让人过出几分煎熬来,但现在看着...
    傻小猫阅读 2,443评论 6 8