为UITableView创建粘性标头

  • *本教程使用Swift 2.3

我是Rep的产品和工程负责人,Rep是一个有影响力的市场,品牌和有影响力的人可以在市场营销活动中进行协作。 我决定分享如何在应用程序中构建此功能。 (对于这里的任何产品猎人,请随时追捕我们:)!)

一段时间以来,我一直在寻找一种简单的解决方案,并且严格避免出现可疑的骇客程序。 在使用XCode几个小时之后,我想出了一个很棒的方法来解决这个问题。 我将为文章提要构建UI。 外观如下:

现在,进入一些代码。 让我们添加标题,背景图像视图,文章图标和背景Alpha层。 我以编程方式使用自动布局对所有视图进行布局,但是可以根据需要随意创建视图。

我们首先需要添加所需的初始化程序和属性:

 类CategoryHeaderView:UIView { 
  var imageView:UIImageView! 
var colorView:UIView!
var bgColor = UIColor(红色:235/255,绿色:96/255,蓝色:91/255,alpha:1)
var titleLabel = UILabel()
var articleIcon:UIImageView!
  init(frame:CGRect,title:String){ 
self.titleLabel.text = title.uppercaseString
super.init(frame:框架)
}
 需要初始化吗?(编码器aDecoder:NSCoder){ 
fatalError(“ init(coder :)尚未实现”)
}
  } 

接下来,使用autolayout以编程方式对所有视图进行布局,并在init()末尾调用此setUpView()函数。 我们开始从后到前布置视图。 顺序为imageView,colorView,titleLabel,articleIcon。

  func setUpView(){ 
  self.backgroundColor = UIColor.whiteColor() 
  imageView = UIImageView() 
imageView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(imageView)
  colorView = UIView() 
colorView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(colorView)
 让约束:[NSLayoutConstraint] = [ 
imageView.topAnchor.constraintEqualToAnchor(self.topAnchor),
imageView.leadingAnchor.constraintEqualToAnchor(self.leadingAnchor),
imageView.trailingAnchor.constraintEqualToAnchor(self.trailingAnchor),
imageView.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor),
colorView.topAnchor.constraintEqualToAnchor(self.topAnchor),
colorView.leadingAnchor.constraintEqualToAnchor(self.leadingAnchor),
colorView.trailingAnchor.constraintEqualToAnchor(self.trailingAnchor),
colorView.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor)
]
  NSLayoutConstraint.activateConstraints(约束) 
imageView.image = UIImage(名称:“ testBackground”)
imageView.contentMode = .ScaleAspectFill
  colorView.backgroundColor = bgColor 
colorView.alpha = 0.6
  titleLabel.translatesAutoresizingMaskIntoConstraints = false 
self.addSubview(titleLabel)
 让titlesConstraints:[NSLayoutConstraint] = [ 
titleLabel.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor),
titleLabel.topAnchor.constraintEqualToAnchor(self.topAnchor,常数:28),
]
  NSLayoutConstraint.activateConstraints(titlesConstraints) 
  titleLabel.font = UIFont.systemFontOfSize(15) 
titleLabel.textAlignment = .Center
  articleIcon = UIImageView() 
articleIcon.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(articleIcon)
 让imageConstraints:[NSLayoutConstraint] = [ 
articleIcon.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor),
articleIcon.centerYAnchor.constraintEqualToAnchor(self.centerYAnchor,常数:6),
articleIcon.widthAnchor.constraintEqualToConstant(40),
articleIcon.heightAnchor.constraintEqualToConstant(40)
]
NSLayoutConstraint.activateConstraints(imageConstraints)
  articleIcon.image = UIImage(名称:“文章”) 
  } 

以编程方式对任何视图使用自动布局时,通常的模式是首先初始化视图,将其translatesAutoresizingMaskIntoConstraints属性设置为false,将其添加到视图,设置约束,激活约束,然后设置其所有属性,字体,文本,图像等等

我们首先将imageView固定到视图的顶部,顶部,底部和底部边缘。 然后,将colorView添加到图像视图的顶部,并将其alpha设置为0.6。 然后,我们添加titleLabel和文章Icon并激活所有约束。 将图像视图内容模式设置为scaleAspectFill很重要,稍后您将看到原因。

不要忘记在我们的init()函数中添加此行,否则视图将无法构建:

  init(frame:CGRect,title:String){ 
self.titleLabel.text = title.uppercaseString
super.init(frame:框架)
setUpView()
}

好的,这就是我们的自定义视图。 现在,设置我们的视图控制器。 跳转到ViewController.swift文件,并添加一个tableview属性和一个自定义标头属性。

 类ViewController:UIViewController { 
  var tableView:UITableView! 
var headerView:CustomHeaderView!
var headerHeightConstraint:NSLayoutConstraint!
 覆盖func viewDidLoad(){ 
  super.viewDidLoad() 
  } 
  } 

我添加了headerHeightConstraint属性,因为我们将在滚动时更改表格视图的高度,并需要保留对其的引用。

接下来,使用autolayout设置自定义标头。

  func setUpHeader(){ 
  headerView = CustomHeaderView(框架:CGRectZero,标题:“文章”) 
headerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(headerView)
  headerHeightConstraint = headerView.heightAnchor.constraintEqualToConstant(150) 
headerHeightConstraint.active = true
 让约束:[NSLayoutConstraint] = [ 
headerView.topAnchor.constraintEqualToAnchor(view.topAnchor),
headerView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
headerView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
]
NSLayoutConstraint.activateConstraints(约束)
  } 

再次,我们遵循程序化自动布局约定。 我们将标头固定在视图控制器的顶部,前端和后端,并给我们的height约束属性提供一个恒定值150。

让我们布置tableView:

  func setUpTableView(){ 
  tableView = UITableView() 
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
 让约束:[NSLayoutConstraint] = [ 
tableView.topAnchor.constraintEqualToAnchor(headerView.bottomAnchor),
tableView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
tableView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
tableView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
]
  NSLayoutConstraint.activateConstraints(约束) 
  tableView.registerClass(UITableViewCell.self,forCellReuseIdentifier:“ cell”) 
tableView.dataSource =自我
  } 

让我们在viewDidLoad()中调用这两个函数

 覆盖func viewDidLoad(){ 
  super.viewDidLoad() 
view.backgroundColor = UIColor.whiteColor()
setUpHeader()
setUpTableView()
  } 

目前,由于我们尚未设置表格视图数据源,因此会收到错误消息。 让我们在类的末尾添加扩展名以摆脱这些错误。 您可以复制粘贴整个部分,因为它并不是本教程的真正部分,而只是基本设置。

 扩展ViewController:UITableViewDataSource { 
  func numberOfSectionsInTableView(tableView:UITableView)-> Int { 
 返回1 
  } 
  func tableView(tableView:UITableView,numberOfRowsInSection部分:Int)-> Int { 
 返回20 
  } 
  func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath)-> UITableViewCell { 
  let cell = tableView.dequeueReusableCellWithIdentifier(“ cell”,forIndexPath:indexPath) 
cell.textLabel?.text =“商品\(indexPath.row)”
返回单元
  } 
  } 

好的,现在就构建并运行该应用程序,它应该是这样的:

现在很好了,标题视图和表视图均已设置并且可以正常工作,以了解本教程的业务逻辑。

由于表视图本质上是滚动视图,因此它可以符合滚动视图委托方法。 让我们在setUpTableView()方法的末尾添加此行

  tableView.delegate =自我 

这将产生一个错误,非常需要添加UITableViewDelegate扩展以及UIScrollViewDelegate扩展。 复制并粘贴到课程末尾

 扩展ViewController:UIScrollViewDelegate { 
  func scrollViewDidScroll(scrollView:UIScrollView){ 
打印(scrollView.contentOffset.y)
}
  func scrollViewDidEndDragging(scrollView:UIScrollView,willDecelerate减速:Bool){ 
  } 
  func scrollViewDidEndDecelerating(scrollView:UIScrollView){ 
  } 
  } 
 扩展ViewController:UITableViewDelegate { 
  } 

我们实际上不需要UITableViewDelegate,我们只是添加了它来消除错误。 所有的魔术都发生在UIScrollViewDelegate中

每当滚动表视图时,都会调用scrollViewDidScroll方法。 这是操纵标头视图高度的好地方。 如果scrollView.contentOffset.y 0,则表示我们正在向上推。

如果我们向下拉,则要拉伸标题视图。 这真的很简单。 让我们将以下代码行添加到scrollViewDidScroll

  func scrollViewDidScroll(scrollView:UIScrollView){ 
 如果scrollView.contentOffset.y <0 { 
  self.headerHeightConstraint.constant + = abs(scrollView.contentOffset.y) 
  } 
  } 

我们将偏移量添加到headerViewConstraint的高度。 让我们尝试拉伸视图。

如您所见,当我们拉动时,标题会伸展。 我们还看到图像与标题视图的框架成比例。 这是因为我们将内容模式设置为宽高比填充,并且免费提供了不错的行为!

哦哦! 当我们放开时,我们会看到标题视图停留在那里。 如果我们继续将其拉低,则其延伸范围更大。

放开时,我们希望标题视图恢复到其原始高度。 委托方法scrollViewDidEndDeceleratingscrollViewDidEndDragging是将视图重新动画化的理想位置,但是首先让我们创建animateHeader方法。

setUpTableView方法下面添加此方法 在ViewController类中:

  func animateHeader(){ 
  self.headerHeightConstraint.constant = 150UIView.animateWithDuration(0.4,延迟:0.0,usingSpringWithDamping:0.7,initialSpringVelocity:0.5,选项:.CurveEaseInOut,动画:{ 
self.view.layoutIfNeeded()
},完成:无)
  } 

我们将高度约束设置回我们的原始高度150,并通过0.4秒的阻尼使视图动画化。

scrollViewDidEndDeceleratingscrollViewDidEndDragging中都调用animateHeader()

  func scrollViewDidEndDragging(scrollView:UIScrollView,willDecelerate减速:Bool){ 
 如果self.headerHeightConstraint.constant> 150 { 
  animateHeader() 
  } 
  } 
  func scrollViewDidEndDecelerating(scrollView:UIScrollView){ 
 如果self.headerHeightConstraint.constant> 150 { 
  animateHeader() 
  } 
  } 

现在,如果您构建并运行,则在向下拉动表格视图后,标题将弹回其位置!

如果您想要可折叠的标题视图,我们需要做更多的数学运算。 标准导航控制器高度为64点。 让我们添加到scrollViewDidScroll方法中,以使标题在向上滚动时可折叠

  func scrollViewDidScroll(scrollView:UIScrollView){ 
 如果scrollView.contentOffset.y <0 { 
  self.headerHeightConstraint.constant + = abs(scrollView.contentOffset.y) 
  }否则,如果scrollView.contentOffset.y> 0 && self.headerHeightConstraint.constant> = 65 { 
  self.headerHeightConstraint.constant-= scrollView.contentOffset.y / 100 
 如果self.headerHeightConstraint.constant <65 { 
  self.headerHeightConstraint.constant = 65 
  } 
  } 
  } 

因为我们不希望标题视图上移太快,所以我们将其除以100(如果需要,可以使用此值进行播放)。 最后检查确保如果我们向上滚动太快,标题视图将保持在64点(尽管我将其设置为65)。

生成并运行。 那真是太酷了,但是文章图标阻碍了标题的发展。 让我们添加一些alpha方法来操纵颜色视图和文章图标视图的alpha,以便在滚动时背景不再是半透明的,并且图标也消失了。

将这些方法添加到CustomHeaderView()类:

  func decrementColorAlpha(offset:CGFloat){ 
 如果self.colorView.alpha <= 1 { 
 让alphaOffset =(offset / 500)/ 85 
  self.colorView.alpha + = alphaOffset 
  } 
  } 
  func decrementArticleAlpha(offset:CGFloat){ 
 如果self.articleIcon.alpha> = 0 { 
 让alphaOffset = max((offset-65)/85.0,0) 
  self.articleIcon.alpha = alphaOffset 
  } 
  } 
  func增量ColorAlpha(偏移量:CGFloat){ 
 如果self.colorView.alpha> = 0.6 { 
 让alphaOffset =(offset / 200)/ 85 
  self.colorView.alpha-= alphaOffset 
  } 
  } 
  func增量ArticleAlpha(偏移量:CGFloat){ 
 如果self.articleIcon.alpha <= 1 { 
 让alphaOffset = max((offset-65)/ 85,0) 
  self.articleIcon.alpha = alphaOffset 
  } 
  } 

这些方法只是处理一些数学问题。 让我们在scrollViewDidScroll中调用它们。

  func scrollViewDidScroll(scrollView:UIScrollView){ 
 如果scrollView.contentOffset.y <0 { 
  self.headerHeightConstraint.constant + = abs(scrollView.contentOffset.y) 
  headerView.incrementColorAlpha(self.headerHeightConstraint.constant) 
  headerView.incrementArticleAlpha(self.headerHeightConstraint.constant) 
  }否则,如果scrollView.contentOffset.y> 0 && self.headerHeightConstraint.constant> = 65 { 
  self.headerHeightConstraint.constant-= scrollView.contentOffset.y / 100 
  headerView.decrementColorAlpha(scrollView.contentOffset.y) 
  headerView.decrementArticleAlpha(self.headerHeightConstraint.constant) 
 如果self.headerHeightConstraint.constant <65 { 
  self.headerHeightConstraint.constant = 65 
  } 
  } 
  } 
  func scrollViewDidEndDragging(scrollView:UIScrollView,willDecelerate减速:Bool){ 
 如果self.headerHeightConstraint.constant> 150 { 
  animateHeader() 
  } 
  } 
  func scrollViewDidEndDecelerating(scrollView:UIScrollView){ 
 如果self.headerHeightConstraint.constant> 150 { 
  animateHeader() 
  } 
  } 

而已! 您现在有了一个带有可折叠动画的漂亮的可伸缩页眉!

您可以在我的github jjjeeerrr111上找到该项目

jjjeeerrr111 / StickyHeader
StickyHeader –带有UITableView的Stickey标头 github.com

**使用的文章图标由Shane Herzog创建

在Twitter或Instagram上关注我
推特:@JeremySharvit
Instagram的:@JeremySh