接着上一篇swift/制作一个简单的tableheaderview+_navigationbar渐变效果(一)继续完成没有做完的任务
编码
上一篇已经完成了对于UINavigationBar的扩展,仅仅只用了24行代码变达成想要的效果。接下来编写tableheaderview的效果部分。
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属性的存在,会将tableView的contentInset。Top设置为64,从而导致未"被遮挡"(其实TableView还是被遮挡了一部分,只是显示UI的contentView位置偏移了)。那么现在我想问tableView的contentOffset值是多少?答案是-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)开始显示并重新设置大小。所以我们更改ParallaxHeaderView中contentView的frame就好了。还记得前面说的automaticallyAdjustsScrollViewInsets属性让tableView的ontentInset。Top自动变成64的吗?这里自动设置的过程系统会自动调用scrollViewDidScroll,于是我顺水推舟,顺便让它也帮我重新设置contentView的frame!
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]