swift/制作一个简单的tableHeaderview+_navigationbar渐变效果(二)

接着上一篇swift/制作一个简单的tableheaderview+_navigationbar渐变效果(一)继续完成没有做完的任务

编码

上一篇已经完成了对于UINavigationBar的扩展,仅仅只用了24行代码变达成想要的效果。接下来编写tableheaderview的效果部分。


ParallaxHeaderView模糊.gif

2:ParallaxHeaderView(1在上篇%>_<%)

虽然只是类似于一个图片下拉放大的效果,但请允许我使用Parallax来命名。

示例图给出两种效果,一种有模糊效果一种无模糊效果。先来实现第一种无模糊效果的。

目标分析

要求:

1:tableHeaderView需要有张图片(废话)
2:图片能够根据滚动的不同而展示不一样的样式(大小、显示范围)

在分析方法之前先弄清楚两点

UIImageView的contentMode属性的作用效果
        /**
         UIViewContentModeScaleToFill : 图片拉伸至填充整个UIImageView(图片可能会变形)
         
         UIViewContentModeScaleAspectFit : 图片拉伸至完全显示在UIImageView里面为止(图片不会变形)
         
         UIViewContentModeScaleAspectFill : 
         图片拉伸至 图片的宽度等于UIImageView的宽度 或者 图片的高度等于UIImageView的高度 为止
         
         UIViewContentModeRedraw : 调用了setNeedsDisplay方法时,就会将图片重新渲染
         
         UIViewContentModeCenter : 居中显示
         UIViewContentModeTop,
         UIViewContentModeBottom,
         UIViewContentModeLeft,
         UIViewContentModeRight,
         UIViewContentModeTopLeft,
         UIViewContentModeTopRight,
         UIViewContentModeBottomLeft,
         UIViewContentModeBottomRight,
         
         经验规律:
         1.凡是带有Scale单词的,图片都会拉伸
         2.凡是带有Aspect单词的,图片都会保持原来的宽高比,图片不会变形
         */

上面是以前用oc的时候对于UIImageView的contentMode的一些解释,在这利用UIViewContentModeScaleAspectFill这一效果进行演示说明(因为项目中使用的就是这个模式)。

红色的框框是UIImageView的大小320X100,而它的image大小是320X320,如果未设置clipsToBounds属性的话,还是能看到整张图片即使UIImageView的大小不够。(注意:使用UIImageVIew的init?(named name: String)方法创建出的imageView大小默认是和image一样大的)

tableView的contentOffset以及contentInset属性(其实是UIScrollView的属性)

网上有很多解释的文章,这里就不详细讲解,做个简单解释.

正如上图所示,UITableViewController有导航栏的情况。默认是不会被导航栏遮挡的,这并不是因为tableView的y值是64的原因,而是因为automaticallyAdjustsScrollViewInsets属性的存在,会将tableViewcontentInset。Top设置为64,从而导致未"被遮挡"(其实TableView还是被遮挡了一部分,只是显示UI的contentView位置偏移了)。那么现在我想问tableViewcontentOffset值是多少?答案是-64。contentOffset 是scrollview当前显示区域顶点相对于frame顶点的偏移量,contentInset 是scrollview中contentView.frame.origin与scrollview.frame.origin的关系

编码实现:

任然一步一步的来

1:设计构造方法

创建一个ParallaxHeaderView.swift并添加

class ParallaxHeaderView: UIView {
    
    var subView: UIView
    var contentView: UIView = UIView()
    
    
    init(subView: UIView, headerViewSize: CGSize) {
        
        self.subView = subView
        super.init(frame: CGRectMake(0, 0, headerViewSize.width, headerViewSize.height))
        //这里是自动布局的设置,大概意思就是subView与它的superView拥有一样的frame
        subView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin, .FlexibleWidth, .FlexibleHeight]
        self.clipsToBounds = false;  //必须得设置成false
        
        self.contentView.frame = self.bounds
        self.contentView.addSubview(subView)
        self.contentView.clipsToBounds = true
        self.addSubview(contentView)
        
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

然后创建一个ParallaxHeaderView并设置为tableHeaderView

             override func viewDidLoad() {
        super.viewDidLoad()

         self.navigationController?.navigationBar.setMyBackgroundColor(UIColor(red: 0/255.0, green: 130/255.0, blue: 210/255.0, alpha: 0))
        
        let imageView = UIImageView(frame: CGRectMake(0, 0, self.tableView.bounds.width, 100))
        imageView.image = UIImage(named: "ba1ec0437cc8d5367a516ff69b01ea89")
        imageView.contentMode = .ScaleAspectFill
        
        let heardView = ParallaxHeaderView(subView: imageView, headerViewSize: CGSizeMake(self.tableView.frame.width, 100))
        self.tableView.tableHeaderView = heardView
        
    }

结果效果是这样的

发现图片压根就没从(0,0)点显示,因为我们仅仅值是创建了,啥都没有处理的。这里我们目标很明确,要让图片从(0,0)开始显示并重新设置大小。所以我们更改ParallaxHeaderViewcontentViewframe就好了。还记得前面说的automaticallyAdjustsScrollViewInsets属性让tableViewontentInset。Top自动变成64的吗?这里自动设置的过程系统会自动调用scrollViewDidScroll,于是我顺水推舟,顺便让它也帮我重新设置contentViewframe

2:滚动计算contentView的frame

先贴代码再解释,在ParallaxHeaderView.swift中添加如下方法

    func layoutHeaderViewWhenScroll(let offset: CGPoint) {

        var delta:CGFloat = 0.0
        var rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)
        
        delta = offset.y
        rect.origin.y += delta ;
        rect.size.height -= delta;
        
        self.contentView.frame = rect;
 
    }
    

在控制器中继续添加

    override func scrollViewDidScroll(scrollView: UIScrollView) {
        let heardView = self.tableView.tableHeaderView as! ParallaxHeaderView
        heardView.layoutHeaderViewWhenScroll(scrollView.contentOffset)
    }

Bingo!在ParallaxHeaderView.swift里面添加的是滑动设置contentViewframe的方法。在scrollViewDidScroll中便是调用。那为什么只修改这两个地方,一开始图片大小也变了呢?没错,在automaticallyAdjustsScrollViewInsets属性为true的情况下,系统在加载的时候便会自动调用scrollViewDidScroll方法。至于layoutHeaderViewWhenScroll里面的计算方式,自己体会体会,很简单。

3:锁定最大滑动位置。

按照上面那样做是不完善的,因为我不断的下拉,图片会不断的变大。而实际需求往往是,下拉到一定的位置便不能继续下拉了。接下来便继续完善。

  • 修改构造方法
    /// 最大的下拉限度(因为是下拉所以总是为负数),超过(小于)这个值,下拉将不会有效果
    var maxOffsetY: CGFloat
   init(subView: UIView, headerViewSize: CGSize, maxOffsetY: CGFloat) {
        
        ...
        self.maxOffsetY = maxOffsetY < 0 ? maxOffsetY : -maxOffsetY
        ....
   }
   

添加了一个最大下拉的y值,其他未修改的地方没有贴出

  • 定义协议
protocol ParallaxHeaderViewDelegate: class {
    func LockScorllView(maxOffsetY: CGFloat)
}
//这个的意思是,对协议进行扩展,任何遵守此协议的UITableViewController都由默认的实现方法
extension ParallaxHeaderViewDelegate where Self : UITableViewController {
    func LockScorllView(maxOffsetY: CGFloat) {
        self.tableView.contentOffset.y = maxOffsetY
    }
}

这里用到swift2.0的新特性了。因为只要是使用ParallaxHeaderView这个的类的,我总是希望他能锁定最大的位置,所以必定都会去实现LockScorllView这个协议方法,又因为这个方法的实现是固定的,所以我直接给了它一个默认的实现,这样就不要总是去写重复的协议了。

然后修改一下layoutHeaderViewWhenScroll方法

    func layoutHeaderViewWhenScroll(let offset: CGPoint) {

        if offset.y < maxOffsetY {
            self.delegate.LockScorllView(maxOffsetY)
            
        }else {
            var delta:CGFloat = 0.0
            var rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)
            
            delta = offset.y
            rect.origin.y += delta ;
            rect.size.height -= delta;
            
            self.contentView.frame = rect;
            
        }
    }

再次修改构造方法,添加一个参数。

    weak var delegate: ParallaxHeaderViewDelegate!
    
    init(subView: UIView, headerViewSize: CGSize, maxOffsetY: CGFloat, delegate: ParallaxHeaderViewDelegate) {
    ...
        self.delegate = delegate
    ...
    
    }

这时候再在控制器里完善初始化,并添在scrollViewDidScroll加上上篇博客写的扩展,就ok了

    override func scrollViewDidScroll(scrollView: UIScrollView) {
        let heardView = self.tableView.tableHeaderView as! ParallaxHeaderView
        heardView.layoutHeaderViewWhenScroll(scrollView.contentOffset)
        
        let color = UIColor(red: 0/255.0, green: 130/255.0, blue: 210/255.0, alpha: 1)
        let offsetY = scrollView.contentOffset.y
        let prelude: CGFloat = 50
        
        if offsetY >= -64 {
            let alpha = min(1, (64 + offsetY) / (64 + prelude))
            //NavBar透明度渐变
            self.navigationController?.navigationBar.setMyBackgroundColor(color.colorWithAlphaComponent(alpha))
            
        } else {
            self.navigationController?.navigationBar.setMyBackgroundColor(color.colorWithAlphaComponent(0))
        }
    }

运行效果图就不贴了。

模糊效果

因为原理相似,而且时间比较紧就不写了。最终版代码已经上传到GitHub[ParallaxHeaderView][id]
[id]: https://github.com/SmallLang/ParallaxHeaderView "ParallaxHeaderView"

进一步优化

代码还有很多不足,比如我还得在scrollViewDidScroll中添加大量计算导航栏透明度的代码。这种计算是通用的,因为总是希望在第一个cell滑动到导航栏下,导航栏就不透明了。完整代码在GitHub[ParallaxHeaderView][id]

总结

虽然重复造轮子不是一种好的习惯,在正式项目开发中也会严重拖缓项目开发进度。但是对于初学者,可以说是一种不错的学习方式。文章中如果出现什么错误或者好的建议欢迎大家提出,我们一起进行学习。要上课了就不啰嗦了。

最后还是附上GitHub[ParallaxHeaderView][id]

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

推荐阅读更多精彩内容